diff --git a/src/app/(app)/epub/[id]/page.tsx b/src/app/(app)/epub/[id]/page.tsx index e55761d..b2df05d 100644 --- a/src/app/(app)/epub/[id]/page.tsx +++ b/src/app/(app)/epub/[id]/page.tsx @@ -176,7 +176,7 @@ export default function EPUBPage() { href="/app" className={buttonClass({ variant: 'secondary', size: 'md', className: 'gap-2' })} > - + Back to Documents diff --git a/src/app/(app)/pdf/[id]/page.tsx b/src/app/(app)/pdf/[id]/page.tsx index 85d711e..bd9f17b 100644 --- a/src/app/(app)/pdf/[id]/page.tsx +++ b/src/app/(app)/pdf/[id]/page.tsx @@ -258,7 +258,7 @@ export default function PDFViewerPage() { onClick={handleBackToDocuments} className={buttonClass({ variant: 'secondary', size: 'md', className: 'gap-2' })} > - + Back to Documents @@ -346,7 +346,7 @@ export default function PDFViewerPage() { diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index d2d9077..e203595 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -110,7 +110,7 @@ export function ColorPicker({ value, field, label, onChange }: ColorPickerProps) size="xs" aria-label="Open system color picker" > - +
- - - Connect + + } + label="Connect" + /> {enableUserSignups && ( - - - Create account + + } + label="Create account" + /> )}
@@ -72,20 +75,24 @@ export function UserMenu({ if (variant === 'sidebar') { return ( - + {session.user.email || 'Account'} + ); } diff --git a/src/components/doclist/DocumentList.tsx b/src/components/doclist/DocumentList.tsx index 9c420af..3b96fb1 100644 --- a/src/components/doclist/DocumentList.tsx +++ b/src/components/doclist/DocumentList.tsx @@ -182,6 +182,16 @@ function SidebarUploadLoader({ ); } +function DocumentListStateLoader() { + return ( +
+ ); +} + function DocumentListInner({ brand, appActions }: DocumentListInnerProps) { const cachedState = cachedDocumentListState; const [sortBy, setSortBy] = useState(cachedState?.sortBy ?? DEFAULT_STATE.sortBy); @@ -670,7 +680,11 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) { {isLoading ? (
- + {isInitialized ? ( + + ) : ( + + )}
) : allDocuments.length === 0 ? (
diff --git a/src/components/doclist/window/FinderSidebar.tsx b/src/components/doclist/window/FinderSidebar.tsx index a6383bd..b534af0 100644 --- a/src/components/doclist/window/FinderSidebar.tsx +++ b/src/components/doclist/window/FinderSidebar.tsx @@ -79,20 +79,20 @@ function FolderRow({ countClassName="group-hover/folder:-translate-x-6 group-focus-within/folder:-translate-x-6" isDropTarget={isDropTarget} /> - +
); } diff --git a/src/components/doclist/window/FinderToolbar.tsx b/src/components/doclist/window/FinderToolbar.tsx index 219d5b6..811090a 100644 --- a/src/components/doclist/window/FinderToolbar.tsx +++ b/src/components/doclist/window/FinderToolbar.tsx @@ -10,7 +10,7 @@ import { HamburgerIcon, } from './finderIcons'; import { ChevronUpDownIcon } from '@/components/icons/Icons'; -import { SearchField, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSegment, listboxOptionClass, listboxOptionsClass, toolbarButtonStyles } from '@/components/ui'; +import { SearchField, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSegment, listboxCompactOptionClass, listboxCompactOptionsClass, toolbarButtonStyles } from '@/components/ui'; import type { ReactNode } from 'react'; interface FinderToolbarProps { @@ -146,17 +146,17 @@ export function FinderToolbar({ {currentSort.label} - + {SORT_OPTIONS.map((opt) => ( listboxOptionClass(active || selected)} + className={({ active, selected }) => listboxCompactOptionClass(active, selected)} > {opt.label} diff --git a/src/components/reader/SegmentsSidebar.tsx b/src/components/reader/SegmentsSidebar.tsx index aeacbdf..961c6dd 100644 --- a/src/components/reader/SegmentsSidebar.tsx +++ b/src/components/reader/SegmentsSidebar.tsx @@ -8,7 +8,7 @@ import toast from 'react-hot-toast'; import { useTTS } from '@/contexts/TTSContext'; import { useConfig } from '@/contexts/ConfigContext'; import { RefreshIcon, InfoIcon } from '@/components/icons/Icons'; -import { buttonClass } from '@/components/ui'; +import { Button, IconButton, popoverPanelClass } from '@/components/ui'; import { ReaderSidebarShell } from '@/components/reader/ReaderSidebarShell'; import { compareSegmentLocators, locatorGroupKey, locatorIdentityKey } from '@/lib/shared/tts-locator'; import { buildSegmentKey, buildSegmentKeyPrefix } from '@/lib/shared/tts-segment-plan'; @@ -632,33 +632,27 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }: const headerActions = ( <> - - + ); @@ -893,9 +887,10 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) { return ( @@ -910,7 +905,7 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) { >
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 03742f3..1fca5df 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -13,6 +13,9 @@ export const listboxPanelClass = export const listboxOptionsClass = cn(listboxPanelClass, 'w-[var(--button-width)] [--anchor-gap:0.25rem]'); +export const listboxCompactOptionsClass = + 'z-50 min-w-[8rem] rounded-md bg-surface p-1 shadow-elev-2 ring-1 ring-line focus:outline-none [--anchor-gap:0.25rem]'; + export const listboxOptionClass = (active: boolean, selected = false, inset: 'check' | 'none' = 'check') => cn( 'relative cursor-pointer select-none rounded-sm py-1.5 text-sm', @@ -20,6 +23,16 @@ export const listboxOptionClass = (active: boolean, selected = false, inset: 'ch selected ? 'bg-accent text-background font-medium' : active ? 'bg-accent-wash text-foreground' : 'text-foreground', ); +export const listboxCompactOptionClass = (active: boolean, selected = false) => + cn( + 'relative cursor-pointer select-none rounded-sm px-2 py-1 text-xs', + active + ? 'bg-accent-wash text-accent' + : selected + ? 'bg-surface-sunken text-accent font-medium' + : 'text-foreground', + ); + export function Select({ value, onChange, diff --git a/src/components/ui/sidebar-nav.tsx b/src/components/ui/sidebar-nav.tsx index 41b83ed..8d1fa98 100644 --- a/src/components/ui/sidebar-nav.tsx +++ b/src/components/ui/sidebar-nav.tsx @@ -1,4 +1,4 @@ -import type { ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react'; +import type { AnchorHTMLAttributes, ButtonHTMLAttributes, HTMLAttributes, ReactNode } from 'react'; import { cn } from './cn'; import { focusRing, motionColors } from './tokens'; @@ -50,6 +50,82 @@ export function SidebarNavGroup({ ); } +export function sidebarNavItemClass({ + active = false, + compact = false, + isDropTarget = false, + className, +}: { + active?: boolean; + compact?: boolean; + isDropTarget?: boolean; + className?: string; +} = {}) { + return cn( + 'group w-full min-w-0 border text-left font-medium', + 'inline-flex items-center transition duration-base ease-standard', + focusRing, + motionColors, + compact ? 'gap-1.5 rounded-md px-2 py-1 text-xs' : 'gap-2 rounded-md px-2.5 py-1.5 text-sm', + active + ? 'border-accent-line bg-surface-sunken text-accent' + : 'border-transparent bg-transparent text-foreground hover:border-accent-line hover:bg-accent-wash hover:text-accent', + isDropTarget ? 'ring-1 ring-accent-line' : '', + className, + ); +} + +export function sidebarNavIconClass({ + active = false, + compact = false, +}: { + active?: boolean; + compact?: boolean; +} = {}) { + return cn( + 'shrink-0 inline-flex items-center justify-center transition-colors duration-base', + compact ? 'h-4 w-4' : 'h-5 w-5', + active ? 'text-accent' : 'text-soft group-hover:text-accent', + ); +} + +function SidebarNavItemContent({ + active = false, + icon, + label, + count, + countClassName, + trailing, + compact = false, + children, +}: { + active?: boolean; + icon?: ReactNode; + label?: ReactNode; + count?: number; + countClassName?: string; + trailing?: ReactNode; + compact?: boolean; + children?: ReactNode; +}) { + return ( + <> + {icon ? ( + + {icon} + + ) : null} + {label ?? children ? {label ?? children} : null} + {typeof count === 'number' && count > 0 ? ( + + {count} + + ) : null} + {trailing} + + ); +} + export function SidebarNavItem({ active = false, icon, @@ -76,38 +152,62 @@ export function SidebarNavItem({ return ( ); } + +export function SidebarNavLink({ + active = false, + icon, + label, + count, + countClassName, + trailing, + isDropTarget = false, + className, + compact = false, + children, + ...props +}: AnchorHTMLAttributes & { + active?: boolean; + icon?: ReactNode; + label?: ReactNode; + count?: number; + countClassName?: string; + trailing?: ReactNode; + isDropTarget?: boolean; + compact?: boolean; +}) { + return ( + + + {children} + + + ); +}