feat: tablet responsiveness (#1369)

* feat: "Notes" column responsiveness for tablets

* feat: ability to expand/collapse the navigation column, move some styles to tailwind

* feat: ability to expand/collapse the navigation column, move some styles to tailwind

* feat: icon for collapsing/expanding navigation column, style improvements

* fix: restore vertical panels resizability on large screens

* feat: tags/folders section for collapsed state

* feat: notes subsection styling in navigation section

* feat: hide rename & delete links of smart views when in collapsed mode

* fix: show notes count in bold when in collapsed mode

* fix: better padding for folders in collapsed state

* fix: smaller left padding for collapsed case

* fix: better view of expand/collapse control

* fix: hide search bar when collapsed

* fix: adjust position of "expand" control when in collapsed state

* chore: remove comments

* refactor: use `classNames` util when applying conditional css classes

* chore: shorter class names syntax

* chore: shorter syntax for conditional classes

* fix: better way of setting background color with opacity

* fix: use variable colors according to SN themes

* chore: move "colors" from "extend" section and thus override Tailwind's colors (so only SN colors will exist)

* fix: correct hover style in collapsed mode

* fix: correct upper boundary for extra small screens
This commit is contained in:
Vardan Hakobyan
2022-08-08 11:07:15 +04:00
committed by GitHub
parent 8332a70384
commit f555fbcaaa
13 changed files with 369 additions and 288 deletions

View File

@@ -172,7 +172,7 @@ const ContentListView: FunctionComponent<Props> = ({
return (
<div
id="items-column"
className="sn-component section app-column app-column-second flex flex-col border-b border-solid border-border"
className="sn-component section app-column flex flex-col border-b border-solid border-border xl:w-87.5 xsm-only:!w-full sm-only:!w-full md-only:!w-52 lg-only:!w-52"
aria-label={'Notes & Files'}
ref={itemsViewPanelRef}
>

View File

@@ -10,6 +10,7 @@ import SearchBar from '@/Components/SearchBar/SearchBar'
import ResponsivePaneContent from '@/Components/ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '@/Components/ResponsivePane/AppPaneMetadata'
import { classNames } from '@/Utils/ConcatenateClassNames'
import Icon from '@/Components/Icon/Icon'
type Props = {
application: WebApplication
@@ -19,6 +20,7 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
const viewControllerManager = useMemo(() => application.getViewControllerManager(), [application])
const ref = useRef<HTMLDivElement>(null)
const [panelWidth, setPanelWidth] = useState<number>(0)
const [isPanelExpanded, setIsPanelExpanded] = useState(true)
useEffect(() => {
const removeObserver = application.addEventObserver(async () => {
@@ -47,19 +49,39 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
}, [viewControllerManager])
return (
<div id="navigation" className="sn-component section app-column app-column-first" ref={ref}>
<div
id="navigation"
className={classNames(
'sn-component section app-column xl:w-[220px] xsm-only:!w-full sm-only:!w-full md-only:transition-width lg-only:transition-width',
isPanelExpanded ? 'md-only:!w-[220px] lg-only:!w-[220px]' : 'md-only:!w-18 lg-only:!w-18',
)}
ref={ref}
>
<ResponsivePaneContent paneId={AppPaneId.Navigation} contentElementId="navigation-content">
<SearchBar
itemListController={viewControllerManager.itemListController}
searchOptionsController={viewControllerManager.searchOptionsController}
selectedViewTitle={viewControllerManager.navigationController.selected?.title}
/>
<div className="section-title-bar">
<div className="section-title-bar-header">
<div className="title text-sm">
<span className="font-bold">Views</span>
{isPanelExpanded && (
<SearchBar
itemListController={viewControllerManager.itemListController}
searchOptionsController={viewControllerManager.searchOptionsController}
selectedViewTitle={viewControllerManager.navigationController.selected?.title}
/>
)}
<div className={'flex justify-end'}>
<div className={classNames('section-title-bar block w-full xl:block', !isPanelExpanded && 'hidden')}>
<div className="section-title-bar-header">
<div className="title text-sm">
<span className="font-bold">Views</span>
</div>
</div>
</div>
<div
className={classNames(
'hidden items-end self-end rounded-full rounded-tr-none rounded-br-none bg-default p-1 text-foreground md:flex xl:hidden',
isPanelExpanded ? 'mb-1 w-fit' : 'mt-4.5 mb-3 ml-3 w-full',
)}
onClick={() => setIsPanelExpanded(!isPanelExpanded)}
>
<Icon type="chevron-down" className={isPanelExpanded ? 'rotate-90' : '-rotate-90'} />
</div>
</div>
<div
className={classNames(
@@ -68,8 +90,8 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
'md:hover:[overflow-y:_overlay]',
)}
>
<SmartViewsSection viewControllerManager={viewControllerManager} />
<TagsSection viewControllerManager={viewControllerManager} />
<SmartViewsSection viewControllerManager={viewControllerManager} isCollapsed={!isPanelExpanded} />
<TagsSection viewControllerManager={viewControllerManager} isCollapsed={!isPanelExpanded} />
</div>
</ResponsivePaneContent>
{ref.current && (

View File

@@ -5,9 +5,10 @@ import SmartViewsListItem from './SmartViewsListItem'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) => {
const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }: Props) => {
const allViews = viewControllerManager.navigationController.smartViews
return (
@@ -19,6 +20,7 @@ const SmartViewsList: FunctionComponent<Props> = ({ viewControllerManager }: Pro
view={view}
tagsState={viewControllerManager.navigationController}
features={viewControllerManager.featuresController}
isCollapsed={isCollapsed}
/>
)
})}

View File

@@ -15,11 +15,13 @@ import {
} from 'react'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
view: SmartView
tagsState: NavigationController
features: FeaturesController
isCollapsed: boolean
}
const PADDING_BASE_PX = 14
@@ -45,7 +47,7 @@ const smartViewIconType = (view: SmartView, isSelected: boolean): IconType => {
return 'hashtag'
}
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState, isCollapsed }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(view.title || '')
@@ -111,18 +113,30 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
return (
<>
<div
className={`tag ${isSelected ? 'selected' : ''} ${isFaded ? 'opacity-50' : ''}`}
className={classNames(
'tag',
isSelected && 'selected',
isFaded && 'opacity-50',
isCollapsed && '!bg-transparent',
)}
onClick={selectCurrentTag}
style={{
paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
}}
>
<div className="tag-info">
<div className={'tag-icon mr-2'}>
<Icon type={iconType} className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
<div className="tag-info relative">
<div
className={classNames(
isCollapsed
? `relative flex h-[40px] w-[40px] items-center justify-center
${isSelected ? 'transparent-info-color-background' : 'transparent-info-color-background-hover'}`
: 'tag-icon mr-2',
)}
>
<Icon type={iconType} className={isSelected ? 'text-info' : 'text-neutral'} />
</div>
<input
className={`title ${isEditing ? 'editing' : ''}`}
className={classNames('title', isEditing ? 'editing' : '', isCollapsed ? 'hidden' : 'block')}
disabled={!isEditing}
id={`react-tag-${view.uuid}`}
onBlur={onBlur}
@@ -132,10 +146,12 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
spellCheck={false}
ref={inputRef}
/>
<div className="count">{view.uuid === SystemViewId.AllNotes && tagsState.allNotesCount}</div>
<div className={classNames('count', isCollapsed ? 'absolute top-0 right-0' : '')}>
{view.uuid === SystemViewId.AllNotes && tagsState.allNotesCount}
</div>
</div>
{!isSystemView(view) && (
{!isSystemView(view) && !isCollapsed && (
<div className="meta">
{view.conflictOf && (
<div className="danger text-[0.625rem] font-bold">Conflicted Copy {view.conflictOf}</div>

View File

@@ -5,12 +5,13 @@ import SmartViewsList from './SmartViewsList'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const SmartViewsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
const SmartViewsSection: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }) => {
return (
<section>
<SmartViewsList viewControllerManager={viewControllerManager} />
<SmartViewsList viewControllerManager={viewControllerManager} isCollapsed={isCollapsed} />
</section>
)
}

View File

@@ -9,9 +9,10 @@ import { TagsListItem } from './TagsListItem'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) => {
const TagsList: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }: Props) => {
const tagsState = viewControllerManager.navigationController
const allTags = tagsState.allLocalRootTags
@@ -52,6 +53,7 @@ const TagsList: FunctionComponent<Props> = ({ viewControllerManager }: Props) =>
tagsState={tagsState}
features={viewControllerManager.featuresController}
onContextMenu={onContextMenu}
isCollapsed={isCollapsed}
/>
)
})}

View File

@@ -22,6 +22,7 @@ import { useDrag, useDrop } from 'react-dnd'
import { DropItem, DropProps, ItemTypes } from './DragNDrop'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
tag: SNTag
@@ -29,273 +30,290 @@ type Props = {
features: FeaturesController
level: number
onContextMenu: (tag: SNTag, posX: number, posY: number) => void
isCollapsed: boolean
}
const PADDING_BASE_PX = 14
const PADDING_BASE_WHEN_COLLAPSED_PX = 6
const PADDING_PER_LEVEL_PX = 21
const PADDING_PER_LEVEL_WHEN_COLLAPSED_PX = 10
export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features, tagsState, level, onContextMenu }) => {
const { toggleAppPane } = useResponsiveAppPane()
export const TagsListItem: FunctionComponent<Props> = observer(
({ tag, features, tagsState, level, onContextMenu, isCollapsed }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const subtagInputRef = useRef<HTMLInputElement>(null)
const menuButtonRef = useRef<HTMLAnchorElement>(null)
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
const subtagInputRef = useRef<HTMLInputElement>(null)
const menuButtonRef = useRef<HTMLAnchorElement>(null)
const isSelected = tagsState.selected === tag
const isEditing = tagsState.editingTag === tag
const isAddingSubtag = tagsState.addingSubtagTo === tag
const noteCounts = computed(() => tagsState.getNotesCount(tag))
const isSelected = tagsState.selected === tag
const isEditing = tagsState.editingTag === tag
const isAddingSubtag = tagsState.addingSubtagTo === tag
const noteCounts = computed(() => tagsState.getNotesCount(tag))
const childrenTags = computed(() => tagsState.getChildren(tag)).get()
const hasChildren = childrenTags.length > 0
const childrenTags = computed(() => tagsState.getChildren(tag)).get()
const hasChildren = childrenTags.length > 0
const hasFolders = features.hasFolders
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder
const hasFolders = features.hasFolders
const hasAtLeastOneFolder = tagsState.hasAtLeastOneFolder
const premiumModal = usePremiumModal()
const premiumModal = usePremiumModal()
const [showChildren, setShowChildren] = useState(tag.expanded)
const [hadChildren, setHadChildren] = useState(hasChildren)
const [showChildren, setShowChildren] = useState(tag.expanded)
const [hadChildren, setHadChildren] = useState(hasChildren)
useEffect(() => {
if (!hadChildren && hasChildren) {
setShowChildren(true)
}
setHadChildren(hasChildren)
}, [hadChildren, hasChildren])
useEffect(() => {
setTitle(tag.title || '')
}, [setTitle, tag])
const toggleChildren: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation()
setShowChildren((x) => {
tagsState.setExpanded(tag, !x)
return !x
})
},
[setShowChildren, tag, tagsState],
)
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(tag)
toggleAppPane(AppPaneId.Items)
}, [tagsState, tag, toggleAppPane])
const onBlur = useCallback(() => {
tagsState.save(tag, title).catch(console.error)
setTitle(tag.title)
}, [tagsState, tag, title, setTitle])
const onInput: FormEventHandler = useCallback(
(e) => {
const value = (e.target as HTMLInputElement).value
setTitle(value)
},
[setTitle],
)
const onKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
inputRef.current?.blur()
e.preventDefault()
useEffect(() => {
if (!hadChildren && hasChildren) {
setShowChildren(true)
}
},
[inputRef],
)
setHadChildren(hasChildren)
}, [hadChildren, hasChildren])
useEffect(() => {
if (isEditing) {
inputRef.current?.focus()
}
}, [inputRef, isEditing])
useEffect(() => {
setTitle(tag.title || '')
}, [setTitle, tag])
const onSubtagInput = useCallback((e) => {
const value = (e.target as HTMLInputElement).value
setSubtagTitle(value)
}, [])
const onSubtagInputBlur = useCallback(() => {
tagsState.createSubtagAndAssignParent(tag, subtagTitle).catch(console.error)
setSubtagTitle('')
}, [subtagTitle, tag, tagsState])
const onSubtagKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
e.preventDefault()
subtagInputRef.current?.blur()
}
},
[subtagInputRef],
)
useEffect(() => {
if (isAddingSubtag) {
subtagInputRef.current?.focus()
}
}, [subtagInputRef, isAddingSubtag])
const [, dragRef] = useDrag(
() => ({
type: ItemTypes.TAG,
item: { uuid: tag.uuid },
canDrag: () => {
return true
const toggleChildren: MouseEventHandler = useCallback(
(e) => {
e.stopPropagation()
setShowChildren((x) => {
tagsState.setExpanded(tag, !x)
return !x
})
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}),
[tag],
)
[setShowChildren, tag, tagsState],
)
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
() => ({
accept: ItemTypes.TAG,
canDrop: (item) => {
return tagsState.isValidTagParent(tag, item as SNTag)
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(tag)
toggleAppPane(AppPaneId.Items)
}, [tagsState, tag, toggleAppPane])
const onBlur = useCallback(() => {
tagsState.save(tag, title).catch(console.error)
setTitle(tag.title)
}, [tagsState, tag, title, setTitle])
const onInput: FormEventHandler = useCallback(
(e) => {
const value = (e.target as HTMLInputElement).value
setTitle(value)
},
drop: (item) => {
if (!hasFolders) {
premiumModal.activate(TAG_FOLDERS_FEATURE_NAME)
return
}
tagsState.assignParent(item.uuid, tag.uuid).catch(console.error)
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[tag, tagsState, hasFolders, premiumModal],
)
[setTitle],
)
const readyToDrop = isOver && canDrop
const toggleContextMenu = useCallback(() => {
if (!menuButtonRef.current) {
return
}
const contextMenuOpen = tagsState.contextMenuOpen
const menuButtonRect = menuButtonRef.current?.getBoundingClientRect()
if (contextMenuOpen) {
tagsState.setContextMenuOpen(false)
} else {
onContextMenu(tag, menuButtonRect.right, menuButtonRect.top)
}
}, [onContextMenu, tagsState, tag])
return (
<>
<button
className={`tag focus:shadow-inner ${isSelected ? 'selected' : ''} ${readyToDrop ? 'is-drag-over' : ''}`}
onClick={selectCurrentTag}
ref={dragRef}
style={{
paddingLeft: `${level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
}}
onContextMenu={(e) => {
const onKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
inputRef.current?.blur()
e.preventDefault()
onContextMenu(tag, e.clientX, e.clientY)
}}
>
<div className="tag-info" title={title} ref={dropRef}>
{hasAtLeastOneFolder && (
<div className="tag-fold-container">
<a
role="button"
className={`tag-fold focus:shadow-inner ${showChildren ? 'opened' : 'closed'} ${
!hasChildren ? 'invisible' : ''
}`}
onClick={hasChildren ? toggleChildren : undefined}
>
<Icon className={'text-neutral'} type={showChildren ? 'menu-arrow-down-alt' : 'menu-arrow-right'} />
</a>
</div>
)}
<div className={'tag-icon draggable mr-2'} ref={dragRef}>
<Icon type="hashtag" className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
</div>
<input
className={`title focus:shadow-none focus:outline-none ${isEditing ? 'editing' : ''}`}
id={`react-tag-${tag.uuid}`}
disabled={!isEditing}
onBlur={onBlur}
onInput={onInput}
value={title}
onKeyDown={onKeyDown}
spellCheck={false}
ref={inputRef}
/>
<div className="flex items-center">
<a
role="button"
className={`mr-2 cursor-pointer border-0 bg-transparent hover:bg-contrast focus:shadow-inner ${
isSelected ? 'visible' : 'invisible'
}`}
onClick={toggleContextMenu}
ref={menuButtonRef}
>
<Icon type="more" className="text-neutral" />
</a>
<div className="count">{noteCounts.get()}</div>
</div>
</div>
}
},
[inputRef],
)
<div className={`meta ${hasAtLeastOneFolder ? 'with-folders' : ''}`}>
{tag.conflictOf && <div className="danger text-[0.625rem] font-bold">Conflicted Copy {tag.conflictOf}</div>}
</div>
</button>
{isAddingSubtag && (
<div
className="tag overflow-hidden"
useEffect(() => {
if (isEditing) {
inputRef.current?.focus()
}
}, [inputRef, isEditing])
const onSubtagInput = useCallback((e) => {
const value = (e.target as HTMLInputElement).value
setSubtagTitle(value)
}, [])
const onSubtagInputBlur = useCallback(() => {
tagsState.createSubtagAndAssignParent(tag, subtagTitle).catch(console.error)
setSubtagTitle('')
}, [subtagTitle, tag, tagsState])
const onSubtagKeyDown: KeyboardEventHandler = useCallback(
(e) => {
if (e.key === KeyboardKey.Enter) {
e.preventDefault()
subtagInputRef.current?.blur()
}
},
[subtagInputRef],
)
useEffect(() => {
if (isAddingSubtag) {
subtagInputRef.current?.focus()
}
}, [subtagInputRef, isAddingSubtag])
const [, dragRef] = useDrag(
() => ({
type: ItemTypes.TAG,
item: { uuid: tag.uuid },
canDrag: () => {
return true
},
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
}),
}),
[tag],
)
const [{ isOver, canDrop }, dropRef] = useDrop<DropItem, void, DropProps>(
() => ({
accept: ItemTypes.TAG,
canDrop: (item) => {
return tagsState.isValidTagParent(tag, item as SNTag)
},
drop: (item) => {
if (!hasFolders) {
premiumModal.activate(TAG_FOLDERS_FEATURE_NAME)
return
}
tagsState.assignParent(item.uuid, tag.uuid).catch(console.error)
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
}),
}),
[tag, tagsState, hasFolders, premiumModal],
)
const readyToDrop = isOver && canDrop
const toggleContextMenu = useCallback(() => {
if (!menuButtonRef.current) {
return
}
const contextMenuOpen = tagsState.contextMenuOpen
const menuButtonRect = menuButtonRef.current?.getBoundingClientRect()
if (contextMenuOpen) {
tagsState.setContextMenuOpen(false)
} else {
onContextMenu(tag, menuButtonRect.right, menuButtonRect.top)
}
}, [onContextMenu, tagsState, tag])
return (
<>
<button
className={`tag focus:shadow-inner ${isSelected ? 'selected' : ''} ${readyToDrop ? 'is-drag-over' : ''}`}
onClick={selectCurrentTag}
ref={dragRef}
style={{
paddingLeft: `${(level + 1) * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
paddingLeft: `${
isCollapsed
? level * PADDING_PER_LEVEL_WHEN_COLLAPSED_PX + PADDING_BASE_WHEN_COLLAPSED_PX
: level * PADDING_PER_LEVEL_PX + PADDING_BASE_PX
}px`,
}}
onContextMenu={(e) => {
e.preventDefault()
onContextMenu(tag, e.clientX, e.clientY)
}}
>
<div className="tag-info">
<div className="flex h-full min-w-[22px] items-center border-0 bg-transparent p-0" />
<div className="tag-icon mr-1">
<Icon type="hashtag" className="mr-1 text-neutral" />
<div className="tag-info" title={title} ref={dropRef}>
{hasAtLeastOneFolder && (
<div className="tag-fold-container">
<a
role="button"
className={`tag-fold focus:shadow-inner ${showChildren ? 'opened' : 'closed'} ${
!hasChildren ? 'invisible' : ''
}`}
onClick={hasChildren ? toggleChildren : undefined}
>
<Icon className={'text-neutral'} type={showChildren ? 'menu-arrow-down-alt' : 'menu-arrow-right'} />
</a>
</div>
)}
<div
className={classNames('tag-icon draggable mr-2', isCollapsed ? 'md-only:!hidden lg-only:!hidden' : '')}
ref={dragRef}
>
<Icon type="hashtag" className={`${isSelected ? 'text-info' : 'text-neutral'}`} />
</div>
<input
className="title w-full focus:shadow-none focus:outline-none"
type="text"
ref={subtagInputRef}
onBlur={onSubtagInputBlur}
onKeyDown={onSubtagKeyDown}
value={subtagTitle}
onInput={onSubtagInput}
className={classNames(
'title focus:shadow-none focus:outline-none',
isEditing ? 'editing' : '',
isCollapsed ? 'md-only:!w-min lg-only:!w-min' : '',
)}
id={`react-tag-${tag.uuid}`}
disabled={!isEditing}
onBlur={onBlur}
onInput={onInput}
value={title}
onKeyDown={onKeyDown}
spellCheck={false}
ref={inputRef}
/>
<div className="flex items-center">
<a
role="button"
className={`mr-2 cursor-pointer border-0 bg-transparent hover:bg-contrast focus:shadow-inner ${
isSelected ? 'visible' : 'invisible'
}`}
onClick={toggleContextMenu}
ref={menuButtonRef}
>
<Icon type="more" className="text-neutral" />
</a>
<div className="count">{noteCounts.get()}</div>
</div>
</div>
</div>
)}
{showChildren && (
<>
{childrenTags.map((tag) => {
return (
<TagsListItem
level={level + 1}
key={tag.uuid}
tag={tag}
tagsState={tagsState}
features={features}
onContextMenu={onContextMenu}
<div className={`meta ${hasAtLeastOneFolder ? 'with-folders' : ''}`}>
{tag.conflictOf && <div className="danger text-[0.625rem] font-bold">Conflicted Copy {tag.conflictOf}</div>}
</div>
</button>
{isAddingSubtag && (
<div
className="tag overflow-hidden"
style={{
paddingLeft: `${(level + 1) * PADDING_PER_LEVEL_PX + PADDING_BASE_PX}px`,
}}
>
<div className="tag-info">
<div className="flex h-full min-w-[22px] items-center border-0 bg-transparent p-0" />
<div className="tag-icon mr-1">
<Icon type="hashtag" className="mr-1 text-neutral" />
</div>
<input
className="title w-full focus:shadow-none focus:outline-none"
type="text"
ref={subtagInputRef}
onBlur={onSubtagInputBlur}
onKeyDown={onSubtagKeyDown}
value={subtagTitle}
onInput={onSubtagInput}
/>
)
})}
</>
)}
</>
)
})
</div>
</div>
)}
{showChildren && (
<>
{childrenTags.map((tag) => {
return (
<TagsListItem
level={level + 1}
key={tag.uuid}
tag={tag}
tagsState={tagsState}
features={features}
onContextMenu={onContextMenu}
isCollapsed={isCollapsed}
/>
)
})}
</>
)}
</>
)
},
)
TagsListItem.displayName = 'TagsListItem'

View File

@@ -5,12 +5,14 @@ import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import TagsSectionAddButton from './TagsSectionAddButton'
import TagsSectionTitle from './TagsSectionTitle'
import { classNames } from '@/Utils/ConcatenateClassNames'
type Props = {
viewControllerManager: ViewControllerManager
isCollapsed: boolean
}
const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
const TagsSection: FunctionComponent<Props> = ({ viewControllerManager, isCollapsed }) => {
const [hasMigration, setHasMigration] = useState<boolean>(false)
const checkIfMigrationNeeded = useCallback(() => {
@@ -53,7 +55,7 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
return (
<section>
<div className="section-title-bar">
<div className={classNames('section-title-bar', isCollapsed ? 'md-only:hidden lg-only:hidden' : '')}>
<div className="section-title-bar-header">
<TagsSectionTitle
features={viewControllerManager.featuresController}
@@ -66,7 +68,13 @@ const TagsSection: FunctionComponent<Props> = ({ viewControllerManager }) => {
/>
</div>
</div>
<TagsList viewControllerManager={viewControllerManager} />
<div
className={classNames(
'hidden',
isCollapsed ? 'mt-6 mb-7 border border-border md-only:block lg-only:block' : '',
)}
/>
<TagsList viewControllerManager={viewControllerManager} isCollapsed={isCollapsed} />
</section>
)
}

View File

@@ -7,15 +7,16 @@ import { FunctionComponent } from 'react'
type Props = {
tags: NavigationController
features: FeaturesController
className?: string
}
const TagsSectionAddButton: FunctionComponent<Props> = ({ tags }) => {
const TagsSectionAddButton: FunctionComponent<Props> = ({ tags, className = '' }) => {
return (
<IconButton
focusable={true}
icon="add"
title="Create a new tag"
className="p-0 text-neutral"
className={`p-0 text-neutral ${className}`}
onClick={() => tags.createNewTemplate()}
/>
)

View File

@@ -9,9 +9,10 @@ type Props = {
features: FeaturesController
hasMigration: boolean
onClickMigration: () => void
className?: string
}
const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, onClickMigration }) => {
const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, onClickMigration, className = '' }) => {
const entitledToFolders = features.hasFolders
const modal = usePremiumModal()
@@ -22,7 +23,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
if (entitledToFolders) {
return (
<>
<div className="title text-sm">
<div className={`title text-sm ${className}`}>
<span className="font-bold">Folders</span>
{hasMigration && (
<label className="ml-1 cursor-pointer font-bold text-info" onClick={onClickMigration}>
@@ -36,7 +37,7 @@ const TagsSectionTitle: FunctionComponent<Props> = ({ features, hasMigration, on
return (
<>
<div className="title text-sm">
<div className={`title text-sm ${className}`}>
<span className="font-bold">Tags</span>
<Tooltip label={TAG_FOLDERS_FEATURE_TOOLTIP}>
<label className="ml-1 cursor-pointer font-bold text-passive-2" onClick={showPremiumAlert}>