From acd8a24043bfd39bb71a493989469539f72bff04 Mon Sep 17 00:00:00 2001
From: Aaron Leopold <36278431+aaronleopold@users.noreply.github.com>
Date: Thu, 26 Dec 2024 11:05:00 -0700
Subject: [PATCH] :lipstick: Improve small topbar nav issues (#540)
* wip: give topbar love, add layout docs
initial effort towards #488
* small misc improvements
---
docs/components/InlineIconButton.tsx | 24 +++++
docs/pages/guides/configuration/_meta.ts | 1 +
docs/pages/guides/configuration/layout.mdx | 101 ++++++++++++++++++
.../src/components/filters/FilterHeader.tsx | 10 +-
.../components/library/LastVisitedLibrary.tsx | 4 +-
.../components/navigation/topbar/TopBar.tsx | 12 ++-
.../navigation/topbar/TopBarNavLink.tsx | 1 +
.../navigation/topbar/sections/UserMenu.tsx | 9 --
.../book-club/BookClubNavigationItem.tsx | 15 ++-
.../library/LibraryNavigationItem.tsx | 25 +++--
.../src/components/navigation/types.ts | 1 +
.../tabs/settings/BookClubSettingsSideBar.tsx | 2 +-
.../src/scenes/library/LibraryLayout.tsx | 13 ++-
.../src/scenes/library/LibraryNavigation.tsx | 5 +-
.../tabs/series/LibrarySeriesScene.tsx | 20 +---
.../tabs/settings/LibrarySettingsRouter.tsx | 11 +-
.../scenes/library/tabs/settings/context.ts | 5 +
.../settings/options/ScannerBehaviorScene.tsx | 19 ++++
.../src/scenes/settings/SettingsLayout.tsx | 2 +-
.../src/scenes/settings/SettingsSideBar.tsx | 2 +-
.../settings/SmartListSettingsSideBar.tsx | 2 +-
packages/client/src/queries/filesystem.ts | 2 +-
packages/client/src/queries/library.ts | 1 +
23 files changed, 233 insertions(+), 54 deletions(-)
create mode 100644 docs/components/InlineIconButton.tsx
create mode 100644 docs/pages/guides/configuration/layout.mdx
diff --git a/docs/components/InlineIconButton.tsx b/docs/components/InlineIconButton.tsx
new file mode 100644
index 000000000..0b771e42c
--- /dev/null
+++ b/docs/components/InlineIconButton.tsx
@@ -0,0 +1,24 @@
+import clsx from 'clsx'
+import React from 'react'
+
+interface InlineIconButtonProps {
+ children: React.ReactNode
+ text?: string
+ size?: number
+}
+
+export default function InlineIconButton(props: InlineIconButtonProps) {
+ const size = props.size ? props.size : 16
+
+ return (
+
+ {React.cloneElement(props.children as React.ReactElement, { size })}
+ {props.text && {props.text}}
+
+ )
+}
diff --git a/docs/pages/guides/configuration/_meta.ts b/docs/pages/guides/configuration/_meta.ts
index b9b8780e8..976c31aef 100644
--- a/docs/pages/guides/configuration/_meta.ts
+++ b/docs/pages/guides/configuration/_meta.ts
@@ -3,4 +3,5 @@ import { Meta } from 'nextra'
export default {
'server-options': 'Server',
theming: 'Theming',
+ layout: 'Layout',
} satisfies Meta
diff --git a/docs/pages/guides/configuration/layout.mdx b/docs/pages/guides/configuration/layout.mdx
new file mode 100644
index 000000000..dc0e32c90
--- /dev/null
+++ b/docs/pages/guides/configuration/layout.mdx
@@ -0,0 +1,101 @@
+import { Callout, Steps } from 'nextra/components'
+import { Brush, Lock, LockOpen, Eye, EyeOff, Bolt } from 'lucide-react'
+import InlineIconButton from '../../../components/InlineIconButton'
+
+# Layout
+
+Stump has a flexible layout system that allows you to customize the layout of the app to your liking. There are more options planned, but for now the two main options are navigation placement
+
+## Navigation Placement
+
+The navigation has two main placement options:
+
+1. Sidebar
+2. Topbar
+
+They cannot be used together, and while they offer the same navigtion options there are some differences in functionality
+
+## Sidebar
+
+The sidebar is the default navigation placement. It is a vertical navigation, left-aligned and fixed to the viewport height. Since the sidebar is fixed and always visible, there is more wiggle room for squeezing in additional functionality and sub-menus. Therefore, when using the sidebar you have access to shortcuts that otherwise aren't found in the topbar. For example, the library submenu allows for:
+
+- Scanning a library
+- Accessing the file explorer
+- Directly accessing the settings page for a library
+- Quick deletion of a library (with a confirmation dialog)
+
+Each first-class item (e.g., library, smart list, etc.) has a submenu of its own, with options like outlined above.
+
+### Secondary Sidebars
+
+Throughout the app, you will find secondary sidebars that are used to display nested navigation options when needed. Some of these are collapsible, and/or can be toggled on / off. These are generally used for navigation that is specific to the current context, e.g., the library settings is organized into many pages which otherwise wouldn't fit in the main sidebar.
+
+#### Enable / Disable
+
+You can enable or disable the settings-specific secondary sidebars by toggling the switch in the appearance settings labeled `Settings sidebar`. This setting will apply to all settings pages with a secondary sidebar.
+
+#### Collapse Primary Sidebar
+
+You can set the primary sidebar to be collapsed when a secondary sidebar is present. This setting is found in the appearance settings labeled `Replace primary sidebar`.
+
+## Topbar
+
+The topbar is a horizontal navigation, top-aligned and fixed to the viewport width. It is more compact than the sidebar, and is better suited for users who prefer a more minimalistic interface. The topbar has a more limited set of options, but still allows for the same core navigation-specific functionality as the sidebar.
+
+### Last Visited Library
+
+The topbar has a special feature that allows you to quickly access the last library you visited. This is a convenience feature with no real equivalent in the sidebar, and might be useful for users who have many libraries but frequently visit the same one.
+
+
+ Stump tracks the timestamp for each library you visit, but only exposes the most recent one in the
+ UI. If you feel like this feature could be improved to utilize a history of visited libraries,
+ please let us know!
+
+
+### Adjusted Width
+
+The topbar has a special feature that allows you to adjust the width of the margins around the main content area. This allows you to better optimize a layout that suits your needs, and is especially useful for users who prefer a more spacious layout on larger monitors. This option is unlocked when the topbar is enabled, and can be found in the appearance settings labeled `Adjusted width`
+
+## Arrangement
+
+The arrangement of the navigation items is the same between the sidebar and topbar, and you are able to customize the order of the items to your liking. You can also hide / show items as you see fit.
+
+### Customization
+
+To customize the layout of the app:
+
+
+
+#### Navigate to appearance settings
+
+You can access the settings page by clicking on the cog icon in the sidebar or topbar, depending on your layout choice, then clicking on the tab
+
+
+ If you have disabled the secondary, settings sidebar, you will either need to access the select
+ box towards the top labeled `Section` and select `Appearance` or use the topbar settings menu to
+ navigate to the appearance settings
+
+
+#### Scroll down to the arrangement section
+
+Scroll down until you see a section titled `Navigation arrangement`. Here you can drag and drop the items to rearrange them, and toggle the visibility of items by clicking on the eye icon
+
+#### Customize to your liking
+
+To make any changes, you need to click the icon to unlock the arrangement. Once you are done, click the icon again to lock the arrangement. Changes are saved automatically as you make them, so you don't need to worry about losing your changes if you forget to lock the arrangement. The following is a list of icons and their meanings:
+
+| Icon | Description |
+| ------------------------------------------------ | -------------------------------------- |
+| | Arrangement is locked, click to unlock |
+| | Arrangement is unlocked, click to lock |
+| | Item is visible, click to hide |
+| | Item is hidden, click to show |
+| | Show / hide additional options |
+
+
+
+## Future Plans
+
+In the future, I would love to have the time to implement more options for the layout system. Some ideas include:
+
+- Spacing options for the entire app (e.g., compact vs spacious)
diff --git a/packages/browser/src/components/filters/FilterHeader.tsx b/packages/browser/src/components/filters/FilterHeader.tsx
index 65e0c6f40..874c80138 100644
--- a/packages/browser/src/components/filters/FilterHeader.tsx
+++ b/packages/browser/src/components/filters/FilterHeader.tsx
@@ -1,6 +1,8 @@
import { cn, useSticky } from '@stump/components'
import { useMediaMatch } from 'rooks'
+import { usePreferences } from '@/hooks/usePreferences'
+
import { useFilterContext } from './context'
import Search from './Search'
@@ -43,7 +45,13 @@ export default function FilterHeader({
navOffset,
}: Props) {
const isMobile = useMediaMatch('(max-width: 768px)')
- const { ref, isSticky } = useSticky({ extraOffset: isMobile ? 56 : 0 })
+ const {
+ preferences: { primary_navigation_mode },
+ } = usePreferences()
+ const { ref, isSticky } = useSticky({
+ extraOffset: isMobile || primary_navigation_mode === 'TOPBAR' ? 56 : 0,
+ })
+
const { filters, setFilter, removeFilter } = useFilterContext()
return (
diff --git a/packages/browser/src/components/library/LastVisitedLibrary.tsx b/packages/browser/src/components/library/LastVisitedLibrary.tsx
index cc855c9af..d46f6f19a 100644
--- a/packages/browser/src/components/library/LastVisitedLibrary.tsx
+++ b/packages/browser/src/components/library/LastVisitedLibrary.tsx
@@ -11,7 +11,9 @@ type Props = {
export default function LastVisitedLibrary({ container }: Props) {
const { sdk } = useSDK()
- const { data: library } = useQuery([sdk.library.keys.getLastVisited], sdk.library.getLastVisited)
+ const { data: library } = useQuery([sdk.library.keys.getLastVisited], () =>
+ sdk.library.getLastVisited(),
+ )
if (!library) {
return null
diff --git a/packages/browser/src/components/navigation/topbar/TopBar.tsx b/packages/browser/src/components/navigation/topbar/TopBar.tsx
index b59d87dc5..cfa044373 100644
--- a/packages/browser/src/components/navigation/topbar/TopBar.tsx
+++ b/packages/browser/src/components/navigation/topbar/TopBar.tsx
@@ -5,6 +5,7 @@ import { NavigationItem } from '@stump/sdk'
import { Book, Home } from 'lucide-react'
import { useCallback, useMemo } from 'react'
import { useLocation } from 'react-router'
+import { useDimensionsRef } from 'rooks'
import { match } from 'ts-pattern'
import { useAppContext } from '@/context'
@@ -19,6 +20,7 @@ import TopBarNavLink from './TopBarNavLink'
export default function TopNavigation() {
const location = useLocation()
+ const [ref, size] = useDimensionsRef()
const { t } = useLocaleContext()
const { checkPermission } = useAppContext()
const {
@@ -76,6 +78,7 @@ export default function TopNavigation() {
key="libraries-topbar-navlink"
showCreate={ctx.show_create_action}
showLinkToAll={ctx.show_link_to_all}
+ width={size?.width}
/>
))
// .with('SmartLists', () => )
@@ -84,12 +87,13 @@ export default function TopNavigation() {
key="book-clubs-topbar-navlink"
showCreate={ctx.show_create_action}
showLinkToAll={ctx.show_link_to_all}
+ width={size?.width}
/>
))
.otherwise(() => null),
)
.filter(Boolean),
- [arrangement, checkSectionPermission, location, t],
+ [arrangement, checkSectionPermission, location, t, size?.width],
)
return (
@@ -101,10 +105,12 @@ export default function TopNavigation() {
}}
>
- {sections}
+
+ {sections}
+
-
+
diff --git a/packages/browser/src/components/navigation/topbar/TopBarNavLink.tsx b/packages/browser/src/components/navigation/topbar/TopBarNavLink.tsx
index c83f0e49d..4ac9eaf03 100644
--- a/packages/browser/src/components/navigation/topbar/TopBarNavLink.tsx
+++ b/packages/browser/src/components/navigation/topbar/TopBarNavLink.tsx
@@ -8,6 +8,7 @@ type Props = {
isActive?: boolean
className?: string
}
+
export default function TopBarNavLink({
to,
isActive,
diff --git a/packages/browser/src/components/navigation/topbar/sections/UserMenu.tsx b/packages/browser/src/components/navigation/topbar/sections/UserMenu.tsx
index d87924389..2417d2d95 100644
--- a/packages/browser/src/components/navigation/topbar/sections/UserMenu.tsx
+++ b/packages/browser/src/components/navigation/topbar/sections/UserMenu.tsx
@@ -63,15 +63,6 @@ export default function UserMenu() {
Notifications
-
-
- Preferences
-
-
Logout
diff --git a/packages/browser/src/components/navigation/topbar/sections/book-club/BookClubNavigationItem.tsx b/packages/browser/src/components/navigation/topbar/sections/book-club/BookClubNavigationItem.tsx
index a741a0ac0..5865490d8 100644
--- a/packages/browser/src/components/navigation/topbar/sections/book-club/BookClubNavigationItem.tsx
+++ b/packages/browser/src/components/navigation/topbar/sections/book-club/BookClubNavigationItem.tsx
@@ -1,4 +1,4 @@
-import { NavigationMenu } from '@stump/components'
+import { cn, NavigationMenu } from '@stump/components'
import { EntityOptionProps } from '@/components/navigation/types'
@@ -7,8 +7,8 @@ const IS_DEVELOPMENT = import.meta.env.DEV
// TODO: implement me
type Props = EntityOptionProps
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-export default function BookClubNavigationItem(_: Props) {
+
+export default function BookClubNavigationItem({ width }: Props) {
if (!IS_DEVELOPMENT) {
return null
}
@@ -19,7 +19,14 @@ export default function BookClubNavigationItem(_: Props) {
Book clubs
-