feat: initial implementation of responsive app panes (#1198)

This commit is contained in:
Aman Harwara
2022-07-04 21:20:28 +05:30
committed by GitHub
parent 38725daeb9
commit 21ea2ec7a1
20 changed files with 336 additions and 186 deletions

View File

@@ -25,6 +25,7 @@ import PermissionsModalWrapper from '@/Components/PermissionsModal/PermissionsMo
import { PanelResizedData } from '@/Types/PanelResizedData'
import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper'
import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvider'
import ResponsivePaneProvider from '../ResponsivePane/ResponsivePaneProvider'
type Props = {
application: WebApplication
@@ -182,19 +183,21 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
>
<Navigation application={application} />
<ContentListView
application={application}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
/>
<NoteGroupView application={application} />
<ResponsivePaneProvider>
<Navigation application={application} />
<ContentListView
application={application}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
/>
<NoteGroupView application={application} />
</ResponsivePaneProvider>
</FileDragNDropProvider>
</div>

View File

@@ -17,6 +17,8 @@ import { NotesController } from '@/Controllers/NotesController'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { ElementIds } from '@/Constants/ElementIDs'
import ContentListHeader from './Header/ContentListHeader'
import ResponsivePaneContent from '../ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
type Props = {
accountMenuController: AccountMenuController
@@ -168,11 +170,11 @@ const ContentListView: FunctionComponent<Props> = ({
return (
<div
id="items-column"
className="sn-component section app-column app-column-second"
className="sn-component section app-column app-column-second border-b border-solid border-border"
aria-label={'Notes & Files'}
ref={itemsViewPanelRef}
>
<div className="content">
<ResponsivePaneContent paneId={AppPaneId.Items}>
<div id="items-title-bar" className="section-title-bar border-b border-solid border-border">
<div id="items-title-bar-container">
<ContentListHeader
@@ -204,7 +206,7 @@ const ContentListView: FunctionComponent<Props> = ({
selectionController={selectionController}
/>
) : null}
</div>
</ResponsivePaneContent>
{itemsViewPanelRef.current && (
<PanelResizer
collapsable={true}

View File

@@ -7,6 +7,8 @@ import ListItemFlagIcons from './ListItemFlagIcons'
import ListItemTags from './ListItemTags'
import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
@@ -20,6 +22,8 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
sortBy,
tags,
}) => {
const { toggleAppPane } = useResponsiveAppPane()
const openFileContextMenu = useCallback(
(posX: number, posY: number) => {
filesController.setFileContextMenuLocation({
@@ -41,9 +45,12 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
[selectionController, item.uuid, openFileContextMenu],
)
const onClick = useCallback(() => {
void selectionController.selectItem(item.uuid, true)
}, [item.uuid, selectionController])
const onClick = useCallback(async () => {
const { didSelect } = await selectionController.selectItem(item.uuid, true)
if (didSelect) {
toggleAppPane(AppPaneId.Editor)
}
}, [item.uuid, selectionController, toggleAppPane])
const IconComponent = () =>
getFileIconComponent(

View File

@@ -1,13 +1,15 @@
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import { FunctionComponent, useCallback } from 'react'
import Icon from '@/Components/Icon/Icon'
import ListItemConflictIndicator from './ListItemConflictIndicator'
import ListItemFlagIcons from './ListItemFlagIcons'
import ListItemTags from './ListItemTags'
import ListItemMetadata from './ListItemMetadata'
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
application,
@@ -22,6 +24,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
sortBy,
tags,
}) => {
const { toggleAppPane } = useResponsiveAppPane()
const editorForNote = application.componentManager.editorForNote(item as SNNote)
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
@@ -43,15 +47,20 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
}
}
const onClick = useCallback(async () => {
const { didSelect } = await selectionController.selectItem(item.uuid, true)
if (didSelect) {
toggleAppPane(AppPaneId.Editor)
}
}, [item.uuid, selectionController, toggleAppPane])
return (
<div
className={`content-list-item flex w-full cursor-pointer items-stretch text-text ${
selected && 'selected border-l-2 border-solid border-info'
}`}
id={item.uuid}
onClick={() => {
void selectionController.selectItem(item.uuid, true)
}}
onClick={onClick}
onContextMenu={(event) => {
event.preventDefault()
void openContextMenu(event.clientX, event.clientY)

View File

@@ -4,9 +4,11 @@ import { WebApplication } from '@/Application/Application'
import { PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react'
import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import SearchBar from '@/Components/SearchBar/SearchBar'
import ResponsivePaneContent from '@/Components/ResponsivePane/ResponsivePaneContent'
import { AppPaneId } from '@/Components/ResponsivePane/AppPaneMetadata'
type Props = {
application: WebApplication
@@ -14,7 +16,7 @@ type Props = {
const Navigation: FunctionComponent<Props> = ({ application }) => {
const viewControllerManager = useMemo(() => application.getViewControllerManager(), [application])
const [ref, setRef] = useState<HTMLDivElement | null>()
const ref = useRef<HTMLDivElement>(null)
const [panelWidth, setPanelWidth] = useState<number>(0)
useEffect(() => {
@@ -44,13 +46,8 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
}, [viewControllerManager])
return (
<div
id="navigation"
className="sn-component section app-column app-column-first"
data-aria-label="Navigation"
ref={setRef}
>
<div id="navigation-content" className="content">
<div id="navigation" className="sn-component section app-column app-column-first" ref={ref}>
<ResponsivePaneContent paneId={AppPaneId.Navigation} contentElementId="navigation-content">
<SearchBar
itemListController={viewControllerManager.itemListController}
searchOptionsController={viewControllerManager.searchOptionsController}
@@ -66,12 +63,12 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
<SmartViewsSection viewControllerManager={viewControllerManager} />
<TagsSection viewControllerManager={viewControllerManager} />
</div>
</div>
{ref && (
</ResponsivePaneContent>
{ref.current && (
<PanelResizer
collapsable={true}
defaultWidth={150}
panel={ref}
panel={ref.current}
hoverable={true}
side={PanelSide.Right}
type={PanelResizeType.WidthOnly}

View File

@@ -7,6 +7,8 @@ import MultipleSelectedFiles from '../MultipleSelectedFiles/MultipleSelectedFile
import { ElementIds } from '@/Constants/ElementIDs'
import FileView from '@/Components/FileView/FileView'
import { FileDnDContext } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import ResponsivePaneContent from '../ResponsivePane/ResponsivePaneContent'
type State = {
showMultipleSelectedNotes: boolean
@@ -88,49 +90,48 @@ class NoteGroupView extends PureComponent<Props, State> {
return (
<div id={ElementIds.EditorColumn} className="app-column app-column-third h-full">
{this.state.showMultipleSelectedNotes && (
<MultipleSelectedNotes
application={this.application}
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
/>
)}
{this.state.showMultipleSelectedFiles && (
<MultipleSelectedFiles
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
/>
)}
{this.viewControllerManager.navigationController.isInFilesView && fileDragNDropContext?.isDraggingFiles && (
<div className="absolute bottom-8 left-1/2 z-dropdown-menu -translate-x-1/2 rounded bg-info px-5 py-3 text-info-contrast shadow-main">
Drop your files to upload them
</div>
)}
{shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && (
<>
{this.state.controllers.map((controller) => {
return controller instanceof NoteViewController ? (
<NoteView key={controller.item.uuid} application={this.application} controller={controller} />
) : (
<FileView
key={controller.item.uuid}
application={this.application}
viewControllerManager={this.viewControllerManager}
file={controller.item}
/>
)
})}
</>
)}
<ResponsivePaneContent paneId={AppPaneId.Editor}>
{this.state.showMultipleSelectedNotes && (
<MultipleSelectedNotes
application={this.application}
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
featuresController={this.viewControllerManager.featuresController}
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
/>
)}
{this.state.showMultipleSelectedFiles && (
<MultipleSelectedFiles
filesController={this.viewControllerManager.filesController}
selectionController={this.viewControllerManager.selectionController}
/>
)}
{this.viewControllerManager.navigationController.isInFilesView && fileDragNDropContext?.isDraggingFiles && (
<div className="absolute bottom-8 left-1/2 z-dropdown-menu -translate-x-1/2 rounded bg-info px-5 py-3 text-info-contrast shadow-main">
Drop your files to upload them
</div>
)}
{shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && (
<>
{this.state.controllers.map((controller) => {
return controller instanceof NoteViewController ? (
<NoteView key={controller.item.uuid} application={this.application} controller={controller} />
) : (
<FileView
key={controller.item.uuid}
application={this.application}
viewControllerManager={this.viewControllerManager}
file={controller.item}
/>
)
})}
</>
)}
</ResponsivePaneContent>
</div>
)
}

View File

@@ -17,7 +17,7 @@ const NoteTagsContainer = ({ viewControllerManager }: Props) => {
return (
<div
className="-mt-1 -mr-2 flex min-w-80 flex-wrap bg-transparent"
className="flex min-w-80 flex-wrap bg-transparent md:-mr-2"
style={{
maxWidth: tagsContainerMaxWidth,
}}

View File

@@ -908,11 +908,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
)}
{this.note && (
<div
id="editor-title-bar"
className="content-title-bar section-title-bar section-title-bar z-editor-title-bar w-full"
>
<div className="flex h-8 items-center justify-between">
<div id="editor-title-bar" className="content-title-bar section-title-bar z-editor-title-bar w-full">
<div className="mb-2 flex flex-wrap items-center justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-0">
<div className={(this.state.noteLocked ? 'locked' : '') + ' flex-grow'}>
<div className="title overflow-auto">
<input
@@ -930,22 +927,26 @@ class NoteView extends PureComponent<NoteViewProps, State> {
/>
</div>
</div>
<div className="flex items-center">
<div id="save-status-container">
<div id="save-status">
<div
className={
(this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') +
(this.state.saveError ? 'font-bold text-danger ' : '') +
'message text-xs'
}
>
{this.state.noteStatus?.message}
<div className="flex flex-col flex-wrap items-start gap-3 md:flex-row md:flex-nowrap md:items-center">
{this.state.noteStatus?.message?.length && (
<div id="save-status-container">
<div id="save-status">
<div
className={
(this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') +
(this.state.saveError ? 'font-bold text-danger ' : '') +
'message text-xs'
}
>
{this.state.noteStatus?.message}
</div>
{this.state.noteStatus?.desc && (
<div className="desc text-xs">{this.state.noteStatus.desc}</div>
)}
</div>
{this.state.noteStatus?.desc && <div className="desc text-xs">{this.state.noteStatus.desc}</div>}
</div>
</div>
<div className="mr-3">
)}
<div className="flex items-center gap-3">
<AttachedFilesButton
application={this.application}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
@@ -956,28 +957,24 @@ class NoteView extends PureComponent<NoteViewProps, State> {
notesController={this.viewControllerManager.notesController}
selectionController={this.viewControllerManager.selectionController}
/>
</div>
<div className="mr-3">
<ChangeEditorButton
application={this.application}
viewControllerManager={this.viewControllerManager}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div>
<div className="mr-3">
<PinNoteButton
notesController={this.viewControllerManager.notesController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
<NotesOptionsPanel
application={this.application}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div>
<NotesOptionsPanel
application={this.application}
navigationController={this.viewControllerManager.navigationController}
notesController={this.viewControllerManager.notesController}
noteTagsController={this.viewControllerManager.noteTagsController}
historyModalController={this.viewControllerManager.historyModalController}
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
/>
</div>
</div>
<NoteTagsContainer viewControllerManager={this.viewControllerManager} />

View File

@@ -1,6 +1,6 @@
import { Component, createRef, MouseEventHandler } from 'react'
import { debounce } from '@/Utils'
import styled from 'styled-components'
import { classNames } from '@/Utils/ConcatenateClassNames'
export type ResizeFinishCallback = (
lastWidth: number,
@@ -39,50 +39,6 @@ type State = {
pressed: boolean
}
const StyledPanelResizer = styled.div<{
hoverable?: boolean
alwaysVisible?: boolean
pressed: boolean
collapsed: boolean
}>`
background-color: var(--panel-resizer-background-color);
border-bottom: none;
border-top: none;
cursor: col-resize;
height: 100%;
opacity: 0;
position: absolute;
right: 0;
top: 0;
width: 4px;
z-index: var(--z-index-panel-resizer);
@keyframes fade {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
&.left {
left: 0;
right: none;
}
${(props) => (props.alwaysVisible || props.collapsed || props.pressed) && 'opacity: 1;'}
&:hover {
${(props) => props.hoverable && 'opacity: 1;'}
}
`
class PanelResizer extends Component<Props, State> {
private overlay?: HTMLDivElement
private resizerElementRef = createRef<HTMLDivElement>()
@@ -351,13 +307,14 @@ class PanelResizer extends Component<Props, State> {
override render() {
return (
<StyledPanelResizer
hoverable={this.props.hoverable}
alwaysVisible={this.props.alwaysVisible}
pressed={this.state.pressed}
collapsed={this.state.collapsed}
className={`panel-resizer ${this.props.side}`}
onMouseDown={this.onMouseDown}
<div
className={classNames(
'absolute right-0 top-0 z-panel-resizer',
'hidden h-full w-[4px] cursor-col-resize border-y-0 bg-[color:var(--panel-resizer-background-color)] md:block',
this.props.alwaysVisible || this.state.collapsed || this.state.pressed ? ' opacity-100' : 'opacity-0',
this.props.hoverable && 'hover:opacity-100',
this.props.side === PanelSide.Left && 'left-0 right-auto',
)}
ref={this.resizerElementRef}
/>
)

View File

@@ -0,0 +1,19 @@
import { IconType } from '@standardnotes/snjs'
export enum AppPaneId {
Navigation = 'NavigationColumn',
Items = 'ItemsColumn',
Editor = 'EditorColumn',
}
export const AppPaneTitles = {
[AppPaneId.Navigation]: 'Navigation',
[AppPaneId.Items]: 'Notes & Files',
[AppPaneId.Editor]: 'Editor',
}
export const AppPaneIcons: Record<AppPaneId, IconType> = {
[AppPaneId.Navigation]: 'hashtag',
[AppPaneId.Items]: 'notes',
[AppPaneId.Editor]: 'plain-text',
}

View File

@@ -0,0 +1,44 @@
import { useMemo, ReactNode } from 'react'
import Icon from '@/Components/Icon/Icon'
import { AppPaneIcons, AppPaneId, AppPaneTitles } from './AppPaneMetadata'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { useResponsiveAppPane } from './ResponsivePaneProvider'
type Props = {
children: ReactNode
contentClassName?: string
contentElementId?: string
paneId: AppPaneId
}
const ResponsivePaneContent = ({ children, contentClassName, contentElementId, paneId }: Props) => {
const { selectedPane, toggleAppPane: togglePane } = useResponsiveAppPane()
const isSelectedPane = useMemo(() => selectedPane === paneId, [paneId, selectedPane])
return (
<>
<button
className={classNames(
'flex w-full items-center justify-between border-b border-solid border-border px-4 py-2 focus:shadow-none focus:outline-none md:hidden',
isSelectedPane ? 'bg-contrast' : 'bg-default',
)}
onClick={() => togglePane(paneId)}
>
<div className="flex items-center gap-2 font-semibold">
<Icon type={AppPaneIcons[paneId]} />
<span>{AppPaneTitles[paneId]}</span>
</div>
<Icon type="chevron-down" />
</button>
<div
id={contentElementId}
className={classNames('content', isSelectedPane ? 'h-full' : 'hidden flex-col md:flex', contentClassName)}
>
{children}
</div>
</>
)
}
export default ResponsivePaneContent

View File

@@ -0,0 +1,64 @@
import { ElementIds } from '@/Constants/ElementIDs'
import { useEffect, ReactNode, useMemo, createContext, useCallback, useContext, useState } from 'react'
import { AppPaneId } from './AppPaneMetadata'
type ResponsivePaneData = {
selectedPane: AppPaneId
toggleAppPane: (paneId: AppPaneId) => void
}
const ResponsivePaneContext = createContext<ResponsivePaneData | undefined>(undefined)
export const useResponsiveAppPane = () => {
const value = useContext(ResponsivePaneContext)
if (!value) {
throw new Error('Component must be a child of <ResponsivePaneProvider />')
}
return value
}
type Props = {
children: ReactNode
}
const ResponsivePaneProvider = ({ children }: Props) => {
const [currentSelectedPane, setCurrentSelectedPane] = useState<AppPaneId>(AppPaneId.Editor)
const [previousSelectedPane, setPreviousSelectedPane] = useState<AppPaneId>(AppPaneId.Editor)
const toggleAppPane = useCallback(
(paneId: AppPaneId) => {
if (paneId === currentSelectedPane) {
setCurrentSelectedPane(previousSelectedPane ? previousSelectedPane : AppPaneId.Editor)
setPreviousSelectedPane(paneId)
} else {
setPreviousSelectedPane(currentSelectedPane)
setCurrentSelectedPane(paneId)
}
},
[currentSelectedPane, previousSelectedPane],
)
useEffect(() => {
if (previousSelectedPane) {
const previousPaneElement = document.getElementById(ElementIds[previousSelectedPane])
previousPaneElement?.classList.remove('selected')
}
const currentPaneElement = document.getElementById(ElementIds[currentSelectedPane])
currentPaneElement?.classList.add('selected')
}, [currentSelectedPane, previousSelectedPane])
const contextValue = useMemo(
() => ({
selectedPane: currentSelectedPane,
toggleAppPane,
}),
[currentSelectedPane, toggleAppPane],
)
return <ResponsivePaneContext.Provider value={contextValue}>{children}</ResponsivePaneContext.Provider>
}
export default ResponsivePaneProvider

View File

@@ -13,6 +13,8 @@ import {
useRef,
useState,
} from 'react'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
type Props = {
view: SmartView
@@ -44,6 +46,8 @@ const smartViewIconType = (view: SmartView, isSelected: boolean): IconType => {
}
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(view.title || '')
const inputRef = useRef<HTMLInputElement>(null)
@@ -55,9 +59,10 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
setTitle(view.title || '')
}, [setTitle, view])
const selectCurrentTag = useCallback(() => {
void tagsState.setSelectedTag(view)
}, [tagsState, view])
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(view)
toggleAppPane(AppPaneId.Items)
}, [tagsState, toggleAppPane, view])
const onBlur = useCallback(() => {
tagsState.save(view, title).catch(console.error)

View File

@@ -20,6 +20,8 @@ import {
} from 'react'
import { useDrag, useDrop } from 'react-dnd'
import { DropItem, DropProps, ItemTypes } from './DragNDrop'
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
type Props = {
tag: SNTag
@@ -33,6 +35,8 @@ const PADDING_BASE_PX = 14
const PADDING_PER_LEVEL_PX = 21
export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features, tagsState, level, onContextMenu }) => {
const { toggleAppPane } = useResponsiveAppPane()
const [title, setTitle] = useState(tag.title || '')
const [subtagTitle, setSubtagTitle] = useState('')
const inputRef = useRef<HTMLInputElement>(null)
@@ -77,9 +81,10 @@ export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features,
[setShowChildren, tag, tagsState],
)
const selectCurrentTag = useCallback(() => {
void tagsState.setSelectedTag(tag)
}, [tagsState, tag])
const selectCurrentTag = useCallback(async () => {
await tagsState.setSelectedTag(tag)
toggleAppPane(AppPaneId.Items)
}, [tagsState, tag, toggleAppPane])
const onBlur = useCallback(() => {
tagsState.save(tag, title).catch(console.error)

View File

@@ -1,9 +1,11 @@
export const ElementIds = {
ContentList: 'notes-scrollable',
EditorColumn: 'editor-column',
EditorContent: 'editor-content',
FileTextPreview: 'file-text-preview',
FileTitleEditor: 'file-title-editor',
ItemsColumn: 'items-column',
NavigationColumn: 'navigation',
NoteTextEditor: 'note-text-editor',
NoteTitleEditor: 'note-title-editor',
FileTitleEditor: 'file-title-editor',
FileTextPreview: 'file-text-preview',
EditorContent: 'editor-content',
EditorColumn: 'editor-column',
ContentList: 'notes-scrollable',
}

View File

@@ -0,0 +1,3 @@
export const classNames = (...values: (string | boolean | undefined)[]): string => {
return values.map((value) => (typeof value === 'string' ? value : null)).join(' ')
}

View File

@@ -1,16 +1,50 @@
.app-column-container {
display: grid;
grid-template-columns: auto auto 2fr;
display: flex;
flex-direction: column;
@media screen and (min-width: 768px) {
display: grid;
grid-template-rows: auto;
grid-template-columns: auto auto 2fr;
}
}
.app-column-first {
width: 220px;
@media screen and (max-width: 768px) {
width: 100% !important;
}
}
.app-column-second {
width: 350px;
@media screen and (max-width: 768px) {
width: 100% !important;
}
}
.app-column {
overflow: hidden;
.content {
height: 100%;
}
@media screen and (max-width: 768px) {
&.selected {
flex-grow: 1;
.content {
height: 100%;
overflow-y: auto;
}
}
&:not(.selected) {
height: auto;
border-bottom: 1px solid var(--sn-stylekit-border-color);
}
}
}

View File

@@ -74,10 +74,13 @@ $heading-height: 75px;
font-size: calc(var(--sn-stylekit-base-font-size) - 2px);
text-transform: none;
font-weight: normal;
text-align: right;
@media screen and (min-width: 768px) {
text-align: right;
}
.desc,
.message:not(.warning):not(.danger) {
.message:not(.warning):not(.text-danger) {
opacity: 0.35;
}
}

View File

@@ -2,11 +2,14 @@
#items-column {
background-color: var(--items-column-background-color);
border-left: 1px solid var(--items-column-border-left-color);
border-right: 1px solid var(--items-column-border-right-color);
font-size: var(--sn-stylekit-font-size-h2);
user-select: none;
@media screen and (min-width: 768px) {
border-left: 1px solid var(--items-column-border-left-color);
border-right: 1px solid var(--items-column-border-right-color);
}
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
@@ -20,11 +23,6 @@
font-size: var(--sn-stylekit-font-size-h3);
}
.content {
display: flex;
flex-direction: column;
}
#items-title-bar-container {
padding: 0.8125rem;
}

View File

@@ -12,11 +12,11 @@ $content-horizontal-padding: 16px;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
display: flex;
flex-direction: column;
&,
#navigation-content {
display: flex;
flex-direction: column;
background-color: var(--navigation-column-background-color);
}