feat: initial implementation of responsive app panes (#1198)
This commit is contained in:
@@ -25,6 +25,7 @@ import PermissionsModalWrapper from '@/Components/PermissionsModal/PermissionsMo
|
|||||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||||
import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper'
|
import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper'
|
||||||
import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvider'
|
import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvider'
|
||||||
|
import ResponsivePaneProvider from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -182,19 +183,21 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
featuresController={viewControllerManager.featuresController}
|
featuresController={viewControllerManager.featuresController}
|
||||||
filesController={viewControllerManager.filesController}
|
filesController={viewControllerManager.filesController}
|
||||||
>
|
>
|
||||||
<Navigation application={application} />
|
<ResponsivePaneProvider>
|
||||||
<ContentListView
|
<Navigation application={application} />
|
||||||
application={application}
|
<ContentListView
|
||||||
accountMenuController={viewControllerManager.accountMenuController}
|
application={application}
|
||||||
filesController={viewControllerManager.filesController}
|
accountMenuController={viewControllerManager.accountMenuController}
|
||||||
itemListController={viewControllerManager.itemListController}
|
filesController={viewControllerManager.filesController}
|
||||||
navigationController={viewControllerManager.navigationController}
|
itemListController={viewControllerManager.itemListController}
|
||||||
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
navigationController={viewControllerManager.navigationController}
|
||||||
noteTagsController={viewControllerManager.noteTagsController}
|
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
||||||
notesController={viewControllerManager.notesController}
|
noteTagsController={viewControllerManager.noteTagsController}
|
||||||
selectionController={viewControllerManager.selectionController}
|
notesController={viewControllerManager.notesController}
|
||||||
/>
|
selectionController={viewControllerManager.selectionController}
|
||||||
<NoteGroupView application={application} />
|
/>
|
||||||
|
<NoteGroupView application={application} />
|
||||||
|
</ResponsivePaneProvider>
|
||||||
</FileDragNDropProvider>
|
</FileDragNDropProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { NotesController } from '@/Controllers/NotesController'
|
|||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import ContentListHeader from './Header/ContentListHeader'
|
import ContentListHeader from './Header/ContentListHeader'
|
||||||
|
import ResponsivePaneContent from '../ResponsivePane/ResponsivePaneContent'
|
||||||
|
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
accountMenuController: AccountMenuController
|
accountMenuController: AccountMenuController
|
||||||
@@ -168,11 +170,11 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="items-column"
|
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'}
|
aria-label={'Notes & Files'}
|
||||||
ref={itemsViewPanelRef}
|
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" className="section-title-bar border-b border-solid border-border">
|
||||||
<div id="items-title-bar-container">
|
<div id="items-title-bar-container">
|
||||||
<ContentListHeader
|
<ContentListHeader
|
||||||
@@ -204,7 +206,7 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
selectionController={selectionController}
|
selectionController={selectionController}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</ResponsivePaneContent>
|
||||||
{itemsViewPanelRef.current && (
|
{itemsViewPanelRef.current && (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
collapsable={true}
|
collapsable={true}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import ListItemFlagIcons from './ListItemFlagIcons'
|
|||||||
import ListItemTags from './ListItemTags'
|
import ListItemTags from './ListItemTags'
|
||||||
import ListItemMetadata from './ListItemMetadata'
|
import ListItemMetadata from './ListItemMetadata'
|
||||||
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
||||||
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
|
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||||
|
|
||||||
const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||||
application,
|
application,
|
||||||
@@ -20,6 +22,8 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
|||||||
sortBy,
|
sortBy,
|
||||||
tags,
|
tags,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { toggleAppPane } = useResponsiveAppPane()
|
||||||
|
|
||||||
const openFileContextMenu = useCallback(
|
const openFileContextMenu = useCallback(
|
||||||
(posX: number, posY: number) => {
|
(posX: number, posY: number) => {
|
||||||
filesController.setFileContextMenuLocation({
|
filesController.setFileContextMenuLocation({
|
||||||
@@ -41,9 +45,12 @@ const FileListItem: FunctionComponent<DisplayableListItemProps> = ({
|
|||||||
[selectionController, item.uuid, openFileContextMenu],
|
[selectionController, item.uuid, openFileContextMenu],
|
||||||
)
|
)
|
||||||
|
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(async () => {
|
||||||
void selectionController.selectItem(item.uuid, true)
|
const { didSelect } = await selectionController.selectItem(item.uuid, true)
|
||||||
}, [item.uuid, selectionController])
|
if (didSelect) {
|
||||||
|
toggleAppPane(AppPaneId.Editor)
|
||||||
|
}
|
||||||
|
}, [item.uuid, selectionController, toggleAppPane])
|
||||||
|
|
||||||
const IconComponent = () =>
|
const IconComponent = () =>
|
||||||
getFileIconComponent(
|
getFileIconComponent(
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
||||||
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
|
import { sanitizeHtmlString, SNNote } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent, useCallback } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import ListItemConflictIndicator from './ListItemConflictIndicator'
|
import ListItemConflictIndicator from './ListItemConflictIndicator'
|
||||||
import ListItemFlagIcons from './ListItemFlagIcons'
|
import ListItemFlagIcons from './ListItemFlagIcons'
|
||||||
import ListItemTags from './ListItemTags'
|
import ListItemTags from './ListItemTags'
|
||||||
import ListItemMetadata from './ListItemMetadata'
|
import ListItemMetadata from './ListItemMetadata'
|
||||||
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
import { DisplayableListItemProps } from './Types/DisplayableListItemProps'
|
||||||
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
|
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||||
|
|
||||||
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
||||||
application,
|
application,
|
||||||
@@ -22,6 +24,8 @@ const NoteListItem: FunctionComponent<DisplayableListItemProps> = ({
|
|||||||
sortBy,
|
sortBy,
|
||||||
tags,
|
tags,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { toggleAppPane } = useResponsiveAppPane()
|
||||||
|
|
||||||
const editorForNote = application.componentManager.editorForNote(item as SNNote)
|
const editorForNote = application.componentManager.editorForNote(item as SNNote)
|
||||||
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
|
const editorName = editorForNote?.name ?? PLAIN_EDITOR_NAME
|
||||||
const [icon, tint] = application.iconsController.getIconAndTintForNoteType(editorForNote?.package_info.note_type)
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`content-list-item flex w-full cursor-pointer items-stretch text-text ${
|
className={`content-list-item flex w-full cursor-pointer items-stretch text-text ${
|
||||||
selected && 'selected border-l-2 border-solid border-info'
|
selected && 'selected border-l-2 border-solid border-info'
|
||||||
}`}
|
}`}
|
||||||
id={item.uuid}
|
id={item.uuid}
|
||||||
onClick={() => {
|
onClick={onClick}
|
||||||
void selectionController.selectItem(item.uuid, true)
|
|
||||||
}}
|
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
void openContextMenu(event.clientX, event.clientY)
|
void openContextMenu(event.clientX, event.clientY)
|
||||||
|
|||||||
@@ -4,9 +4,11 @@ import { WebApplication } from '@/Application/Application'
|
|||||||
import { PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
|
import { PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
|
||||||
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
||||||
import { observer } from 'mobx-react-lite'
|
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 PanelResizer, { PanelSide, ResizeFinishCallback, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
|
||||||
import SearchBar from '@/Components/SearchBar/SearchBar'
|
import SearchBar from '@/Components/SearchBar/SearchBar'
|
||||||
|
import ResponsivePaneContent from '@/Components/ResponsivePane/ResponsivePaneContent'
|
||||||
|
import { AppPaneId } from '@/Components/ResponsivePane/AppPaneMetadata'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -14,7 +16,7 @@ type Props = {
|
|||||||
|
|
||||||
const Navigation: FunctionComponent<Props> = ({ application }) => {
|
const Navigation: FunctionComponent<Props> = ({ application }) => {
|
||||||
const viewControllerManager = useMemo(() => application.getViewControllerManager(), [application])
|
const viewControllerManager = useMemo(() => application.getViewControllerManager(), [application])
|
||||||
const [ref, setRef] = useState<HTMLDivElement | null>()
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
const [panelWidth, setPanelWidth] = useState<number>(0)
|
const [panelWidth, setPanelWidth] = useState<number>(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -44,13 +46,8 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
|
|||||||
}, [viewControllerManager])
|
}, [viewControllerManager])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div id="navigation" className="sn-component section app-column app-column-first" ref={ref}>
|
||||||
id="navigation"
|
<ResponsivePaneContent paneId={AppPaneId.Navigation} contentElementId="navigation-content">
|
||||||
className="sn-component section app-column app-column-first"
|
|
||||||
data-aria-label="Navigation"
|
|
||||||
ref={setRef}
|
|
||||||
>
|
|
||||||
<div id="navigation-content" className="content">
|
|
||||||
<SearchBar
|
<SearchBar
|
||||||
itemListController={viewControllerManager.itemListController}
|
itemListController={viewControllerManager.itemListController}
|
||||||
searchOptionsController={viewControllerManager.searchOptionsController}
|
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||||
@@ -66,12 +63,12 @@ const Navigation: FunctionComponent<Props> = ({ application }) => {
|
|||||||
<SmartViewsSection viewControllerManager={viewControllerManager} />
|
<SmartViewsSection viewControllerManager={viewControllerManager} />
|
||||||
<TagsSection viewControllerManager={viewControllerManager} />
|
<TagsSection viewControllerManager={viewControllerManager} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ResponsivePaneContent>
|
||||||
{ref && (
|
{ref.current && (
|
||||||
<PanelResizer
|
<PanelResizer
|
||||||
collapsable={true}
|
collapsable={true}
|
||||||
defaultWidth={150}
|
defaultWidth={150}
|
||||||
panel={ref}
|
panel={ref.current}
|
||||||
hoverable={true}
|
hoverable={true}
|
||||||
side={PanelSide.Right}
|
side={PanelSide.Right}
|
||||||
type={PanelResizeType.WidthOnly}
|
type={PanelResizeType.WidthOnly}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import MultipleSelectedFiles from '../MultipleSelectedFiles/MultipleSelectedFile
|
|||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import FileView from '@/Components/FileView/FileView'
|
import FileView from '@/Components/FileView/FileView'
|
||||||
import { FileDnDContext } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
|
import { FileDnDContext } from '@/Components/FileDragNDropProvider/FileDragNDropProvider'
|
||||||
|
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||||
|
import ResponsivePaneContent from '../ResponsivePane/ResponsivePaneContent'
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
showMultipleSelectedNotes: boolean
|
showMultipleSelectedNotes: boolean
|
||||||
@@ -88,49 +90,48 @@ class NoteGroupView extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={ElementIds.EditorColumn} className="app-column app-column-third h-full">
|
<div id={ElementIds.EditorColumn} className="app-column app-column-third h-full">
|
||||||
{this.state.showMultipleSelectedNotes && (
|
<ResponsivePaneContent paneId={AppPaneId.Editor}>
|
||||||
<MultipleSelectedNotes
|
{this.state.showMultipleSelectedNotes && (
|
||||||
application={this.application}
|
<MultipleSelectedNotes
|
||||||
filesController={this.viewControllerManager.filesController}
|
application={this.application}
|
||||||
selectionController={this.viewControllerManager.selectionController}
|
filesController={this.viewControllerManager.filesController}
|
||||||
featuresController={this.viewControllerManager.featuresController}
|
selectionController={this.viewControllerManager.selectionController}
|
||||||
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
|
featuresController={this.viewControllerManager.featuresController}
|
||||||
navigationController={this.viewControllerManager.navigationController}
|
filePreviewModalController={this.viewControllerManager.filePreviewModalController}
|
||||||
notesController={this.viewControllerManager.notesController}
|
navigationController={this.viewControllerManager.navigationController}
|
||||||
noteTagsController={this.viewControllerManager.noteTagsController}
|
notesController={this.viewControllerManager.notesController}
|
||||||
historyModalController={this.viewControllerManager.historyModalController}
|
noteTagsController={this.viewControllerManager.noteTagsController}
|
||||||
/>
|
historyModalController={this.viewControllerManager.historyModalController}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
{this.state.showMultipleSelectedFiles && (
|
{this.state.showMultipleSelectedFiles && (
|
||||||
<MultipleSelectedFiles
|
<MultipleSelectedFiles
|
||||||
filesController={this.viewControllerManager.filesController}
|
filesController={this.viewControllerManager.filesController}
|
||||||
selectionController={this.viewControllerManager.selectionController}
|
selectionController={this.viewControllerManager.selectionController}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{this.viewControllerManager.navigationController.isInFilesView && fileDragNDropContext?.isDraggingFiles && (
|
||||||
{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">
|
||||||
<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
|
||||||
Drop your files to upload them
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && (
|
||||||
|
<>
|
||||||
{shouldNotShowMultipleSelectedItems && this.state.controllers.length > 0 && (
|
{this.state.controllers.map((controller) => {
|
||||||
<>
|
return controller instanceof NoteViewController ? (
|
||||||
{this.state.controllers.map((controller) => {
|
<NoteView key={controller.item.uuid} application={this.application} controller={controller} />
|
||||||
return controller instanceof NoteViewController ? (
|
) : (
|
||||||
<NoteView key={controller.item.uuid} application={this.application} controller={controller} />
|
<FileView
|
||||||
) : (
|
key={controller.item.uuid}
|
||||||
<FileView
|
application={this.application}
|
||||||
key={controller.item.uuid}
|
viewControllerManager={this.viewControllerManager}
|
||||||
application={this.application}
|
file={controller.item}
|
||||||
viewControllerManager={this.viewControllerManager}
|
/>
|
||||||
file={controller.item}
|
)
|
||||||
/>
|
})}
|
||||||
)
|
</>
|
||||||
})}
|
)}
|
||||||
</>
|
</ResponsivePaneContent>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const NoteTagsContainer = ({ viewControllerManager }: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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={{
|
style={{
|
||||||
maxWidth: tagsContainerMaxWidth,
|
maxWidth: tagsContainerMaxWidth,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -908,11 +908,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{this.note && (
|
{this.note && (
|
||||||
<div
|
<div id="editor-title-bar" className="content-title-bar section-title-bar z-editor-title-bar w-full">
|
||||||
id="editor-title-bar"
|
<div className="mb-2 flex flex-wrap items-center justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-0">
|
||||||
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 className={(this.state.noteLocked ? 'locked' : '') + ' flex-grow'}>
|
<div className={(this.state.noteLocked ? 'locked' : '') + ' flex-grow'}>
|
||||||
<div className="title overflow-auto">
|
<div className="title overflow-auto">
|
||||||
<input
|
<input
|
||||||
@@ -930,22 +927,26 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">
|
<div className="flex flex-col flex-wrap items-start gap-3 md:flex-row md:flex-nowrap md:items-center">
|
||||||
<div id="save-status-container">
|
{this.state.noteStatus?.message?.length && (
|
||||||
<div id="save-status">
|
<div id="save-status-container">
|
||||||
<div
|
<div id="save-status">
|
||||||
className={
|
<div
|
||||||
(this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') +
|
className={
|
||||||
(this.state.saveError ? 'font-bold text-danger ' : '') +
|
(this.state.syncTakingTooLong ? 'font-bold text-warning ' : '') +
|
||||||
'message text-xs'
|
(this.state.saveError ? 'font-bold text-danger ' : '') +
|
||||||
}
|
'message text-xs'
|
||||||
>
|
}
|
||||||
{this.state.noteStatus?.message}
|
>
|
||||||
|
{this.state.noteStatus?.message}
|
||||||
|
</div>
|
||||||
|
{this.state.noteStatus?.desc && (
|
||||||
|
<div className="desc text-xs">{this.state.noteStatus.desc}</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{this.state.noteStatus?.desc && <div className="desc text-xs">{this.state.noteStatus.desc}</div>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
<div className="mr-3">
|
<div className="flex items-center gap-3">
|
||||||
<AttachedFilesButton
|
<AttachedFilesButton
|
||||||
application={this.application}
|
application={this.application}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||||
@@ -956,28 +957,24 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
|||||||
notesController={this.viewControllerManager.notesController}
|
notesController={this.viewControllerManager.notesController}
|
||||||
selectionController={this.viewControllerManager.selectionController}
|
selectionController={this.viewControllerManager.selectionController}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="mr-3">
|
|
||||||
<ChangeEditorButton
|
<ChangeEditorButton
|
||||||
application={this.application}
|
application={this.application}
|
||||||
viewControllerManager={this.viewControllerManager}
|
viewControllerManager={this.viewControllerManager}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="mr-3">
|
|
||||||
<PinNoteButton
|
<PinNoteButton
|
||||||
notesController={this.viewControllerManager.notesController}
|
notesController={this.viewControllerManager.notesController}
|
||||||
onClickPreprocessing={this.ensureNoteIsInsertedBeforeUIAction}
|
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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<NoteTagsContainer viewControllerManager={this.viewControllerManager} />
|
<NoteTagsContainer viewControllerManager={this.viewControllerManager} />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Component, createRef, MouseEventHandler } from 'react'
|
import { Component, createRef, MouseEventHandler } from 'react'
|
||||||
import { debounce } from '@/Utils'
|
import { debounce } from '@/Utils'
|
||||||
import styled from 'styled-components'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
|
|
||||||
export type ResizeFinishCallback = (
|
export type ResizeFinishCallback = (
|
||||||
lastWidth: number,
|
lastWidth: number,
|
||||||
@@ -39,50 +39,6 @@ type State = {
|
|||||||
pressed: boolean
|
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> {
|
class PanelResizer extends Component<Props, State> {
|
||||||
private overlay?: HTMLDivElement
|
private overlay?: HTMLDivElement
|
||||||
private resizerElementRef = createRef<HTMLDivElement>()
|
private resizerElementRef = createRef<HTMLDivElement>()
|
||||||
@@ -351,13 +307,14 @@ class PanelResizer extends Component<Props, State> {
|
|||||||
|
|
||||||
override render() {
|
override render() {
|
||||||
return (
|
return (
|
||||||
<StyledPanelResizer
|
<div
|
||||||
hoverable={this.props.hoverable}
|
className={classNames(
|
||||||
alwaysVisible={this.props.alwaysVisible}
|
'absolute right-0 top-0 z-panel-resizer',
|
||||||
pressed={this.state.pressed}
|
'hidden h-full w-[4px] cursor-col-resize border-y-0 bg-[color:var(--panel-resizer-background-color)] md:block',
|
||||||
collapsed={this.state.collapsed}
|
this.props.alwaysVisible || this.state.collapsed || this.state.pressed ? ' opacity-100' : 'opacity-0',
|
||||||
className={`panel-resizer ${this.props.side}`}
|
this.props.hoverable && 'hover:opacity-100',
|
||||||
onMouseDown={this.onMouseDown}
|
this.props.side === PanelSide.Left && 'left-0 right-auto',
|
||||||
|
)}
|
||||||
ref={this.resizerElementRef}
|
ref={this.resizerElementRef}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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',
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||||
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
view: SmartView
|
view: SmartView
|
||||||
@@ -44,6 +46,8 @@ const smartViewIconType = (view: SmartView, isSelected: boolean): IconType => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
|
const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
|
||||||
|
const { toggleAppPane } = useResponsiveAppPane()
|
||||||
|
|
||||||
const [title, setTitle] = useState(view.title || '')
|
const [title, setTitle] = useState(view.title || '')
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
@@ -55,9 +59,10 @@ const SmartViewsListItem: FunctionComponent<Props> = ({ view, tagsState }) => {
|
|||||||
setTitle(view.title || '')
|
setTitle(view.title || '')
|
||||||
}, [setTitle, view])
|
}, [setTitle, view])
|
||||||
|
|
||||||
const selectCurrentTag = useCallback(() => {
|
const selectCurrentTag = useCallback(async () => {
|
||||||
void tagsState.setSelectedTag(view)
|
await tagsState.setSelectedTag(view)
|
||||||
}, [tagsState, view])
|
toggleAppPane(AppPaneId.Items)
|
||||||
|
}, [tagsState, toggleAppPane, view])
|
||||||
|
|
||||||
const onBlur = useCallback(() => {
|
const onBlur = useCallback(() => {
|
||||||
tagsState.save(view, title).catch(console.error)
|
tagsState.save(view, title).catch(console.error)
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { useDrag, useDrop } from 'react-dnd'
|
import { useDrag, useDrop } from 'react-dnd'
|
||||||
import { DropItem, DropProps, ItemTypes } from './DragNDrop'
|
import { DropItem, DropProps, ItemTypes } from './DragNDrop'
|
||||||
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
|
import { AppPaneId } from '../ResponsivePane/AppPaneMetadata'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
tag: SNTag
|
tag: SNTag
|
||||||
@@ -33,6 +35,8 @@ const PADDING_BASE_PX = 14
|
|||||||
const PADDING_PER_LEVEL_PX = 21
|
const PADDING_PER_LEVEL_PX = 21
|
||||||
|
|
||||||
export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features, tagsState, level, onContextMenu }) => {
|
export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features, tagsState, level, onContextMenu }) => {
|
||||||
|
const { toggleAppPane } = useResponsiveAppPane()
|
||||||
|
|
||||||
const [title, setTitle] = useState(tag.title || '')
|
const [title, setTitle] = useState(tag.title || '')
|
||||||
const [subtagTitle, setSubtagTitle] = useState('')
|
const [subtagTitle, setSubtagTitle] = useState('')
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
@@ -77,9 +81,10 @@ export const TagsListItem: FunctionComponent<Props> = observer(({ tag, features,
|
|||||||
[setShowChildren, tag, tagsState],
|
[setShowChildren, tag, tagsState],
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectCurrentTag = useCallback(() => {
|
const selectCurrentTag = useCallback(async () => {
|
||||||
void tagsState.setSelectedTag(tag)
|
await tagsState.setSelectedTag(tag)
|
||||||
}, [tagsState, tag])
|
toggleAppPane(AppPaneId.Items)
|
||||||
|
}, [tagsState, tag, toggleAppPane])
|
||||||
|
|
||||||
const onBlur = useCallback(() => {
|
const onBlur = useCallback(() => {
|
||||||
tagsState.save(tag, title).catch(console.error)
|
tagsState.save(tag, title).catch(console.error)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
export const ElementIds = {
|
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',
|
NoteTextEditor: 'note-text-editor',
|
||||||
NoteTitleEditor: 'note-title-editor',
|
NoteTitleEditor: 'note-title-editor',
|
||||||
FileTitleEditor: 'file-title-editor',
|
|
||||||
FileTextPreview: 'file-text-preview',
|
|
||||||
EditorContent: 'editor-content',
|
|
||||||
EditorColumn: 'editor-column',
|
|
||||||
ContentList: 'notes-scrollable',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const classNames = (...values: (string | boolean | undefined)[]): string => {
|
||||||
|
return values.map((value) => (typeof value === 'string' ? value : null)).join(' ')
|
||||||
|
}
|
||||||
@@ -1,16 +1,50 @@
|
|||||||
.app-column-container {
|
.app-column-container {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: auto auto 2fr;
|
flex-direction: column;
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: auto auto 2fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-column-first {
|
.app-column-first {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-column-second {
|
.app-column-second {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-column {
|
.app-column {
|
||||||
overflow: hidden;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,10 +74,13 @@ $heading-height: 75px;
|
|||||||
font-size: calc(var(--sn-stylekit-base-font-size) - 2px);
|
font-size: calc(var(--sn-stylekit-base-font-size) - 2px);
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-align: right;
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
.desc,
|
.desc,
|
||||||
.message:not(.warning):not(.danger) {
|
.message:not(.warning):not(.text-danger) {
|
||||||
opacity: 0.35;
|
opacity: 0.35;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
#items-column {
|
#items-column {
|
||||||
background-color: var(--items-column-background-color);
|
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);
|
font-size: var(--sn-stylekit-font-size-h2);
|
||||||
user-select: none;
|
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;
|
-moz-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
@@ -20,11 +23,6 @@
|
|||||||
font-size: var(--sn-stylekit-font-size-h3);
|
font-size: var(--sn-stylekit-font-size-h3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#items-title-bar-container {
|
#items-title-bar-container {
|
||||||
padding: 0.8125rem;
|
padding: 0.8125rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ $content-horizontal-padding: 16px;
|
|||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-khtml-user-select: none;
|
-khtml-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
#navigation-content {
|
#navigation-content {
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: var(--navigation-column-background-color);
|
background-color: var(--navigation-column-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user