@evinvest/uikit 0.31.0 → 0.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/components/{accordion.js → atomic/accordion.js} +3 -3
- package/dist/components/atomic/accordion.js.map +1 -0
- package/dist/components/{alert-dialog.js → atomic/alert-dialog.js} +9 -9
- package/dist/components/atomic/alert-dialog.js.map +1 -0
- package/dist/components/{alert.js → atomic/alert.js} +2 -2
- package/dist/components/atomic/alert.js.map +1 -0
- package/dist/components/{animated-number-columns.js → atomic/animated-number-columns.js} +1 -1
- package/dist/components/atomic/animated-number-columns.js.map +1 -0
- package/dist/components/{avatar.js → atomic/avatar.js} +2 -2
- package/dist/components/atomic/avatar.js.map +1 -0
- package/dist/components/{badge.js → atomic/badge.js} +3 -3
- package/dist/components/atomic/badge.js.map +1 -0
- package/dist/components/{band.js → atomic/band.js} +2 -2
- package/dist/components/atomic/band.js.map +1 -0
- package/dist/components/{breadcrumb.js → atomic/breadcrumb.js} +3 -3
- package/dist/components/atomic/breadcrumb.js.map +1 -0
- package/dist/components/{button-group.js → atomic/button-group.js} +3 -3
- package/dist/components/atomic/button-group.js.map +1 -0
- package/dist/components/{button.js → atomic/button.js} +4 -4
- package/dist/components/atomic/button.js.map +1 -0
- package/dist/components/{calendar.js → atomic/calendar.js} +2 -2
- package/dist/components/atomic/calendar.js.map +1 -0
- package/dist/components/{card.js → atomic/card.js} +2 -2
- package/dist/components/atomic/card.js.map +1 -0
- package/dist/components/{checkbox.js → atomic/checkbox.js} +4 -4
- package/dist/components/atomic/checkbox.js.map +1 -0
- package/dist/components/{collapsible.js → atomic/collapsible.js} +1 -1
- package/dist/components/atomic/collapsible.js.map +1 -0
- package/dist/components/{command.js → atomic/command.js} +7 -7
- package/dist/components/atomic/command.js.map +1 -0
- package/dist/components/{container.js → atomic/container.js} +2 -2
- package/dist/components/atomic/container.js.map +1 -0
- package/dist/components/{context-menu.js → atomic/context-menu.js} +10 -10
- package/dist/components/atomic/context-menu.js.map +1 -0
- package/dist/components/{dialog.js → atomic/dialog.js} +9 -9
- package/dist/components/atomic/dialog.js.map +1 -0
- package/dist/components/{drawer.js → atomic/drawer.js} +8 -8
- package/dist/components/atomic/drawer.js.map +1 -0
- package/dist/components/{dropdown-menu.js → atomic/dropdown-menu.js} +11 -11
- package/dist/components/atomic/dropdown-menu.js.map +1 -0
- package/dist/components/{empty.js → atomic/empty.js} +2 -2
- package/dist/components/atomic/empty.js.map +1 -0
- package/dist/components/atomic/field-context.js.map +1 -0
- package/dist/components/{form-row.js → atomic/form-row.js} +2 -2
- package/dist/components/atomic/form-row.js.map +1 -0
- package/dist/components/{hover-card.js → atomic/hover-card.js} +8 -8
- package/dist/components/atomic/hover-card.js.map +1 -0
- package/dist/components/{info-tip.js → atomic/info-tip.js} +10 -10
- package/dist/components/atomic/info-tip.js.map +1 -0
- package/dist/components/{input-otp.js → atomic/input-otp.js} +3 -3
- package/dist/components/atomic/input-otp.js.map +1 -0
- package/dist/components/{input.js → atomic/input.js} +3 -3
- package/dist/components/atomic/input.js.map +1 -0
- package/dist/components/{kbd.js → atomic/kbd.js} +2 -2
- package/dist/components/atomic/kbd.js.map +1 -0
- package/dist/components/{key-value.js → atomic/key-value.js} +3 -3
- package/dist/components/atomic/key-value.js.map +1 -0
- package/dist/components/{label.js → atomic/label.js} +2 -2
- package/dist/components/atomic/label.js.map +1 -0
- package/dist/components/{menubar.js → atomic/menubar.js} +10 -10
- package/dist/components/atomic/menubar.js.map +1 -0
- package/dist/components/{native-select.js → atomic/native-select.js} +4 -4
- package/dist/components/atomic/native-select.js.map +1 -0
- package/dist/components/{navigation-menu.js → atomic/navigation-menu.js} +7 -7
- package/dist/components/atomic/navigation-menu.js.map +1 -0
- package/dist/components/{pagination.js → atomic/pagination.js} +1 -1
- package/dist/components/atomic/pagination.js.map +1 -0
- package/dist/components/{popover.js → atomic/popover.js} +10 -10
- package/dist/components/atomic/popover.js.map +1 -0
- package/dist/components/{progress.js → atomic/progress.js} +2 -2
- package/dist/components/atomic/progress.js.map +1 -0
- package/dist/components/{radio-group.js → atomic/radio-group.js} +4 -4
- package/dist/components/atomic/radio-group.js.map +1 -0
- package/dist/components/{scroll-area.js → atomic/scroll-area.js} +2 -2
- package/dist/components/atomic/scroll-area.js.map +1 -0
- package/dist/components/{section-descriptor.js → atomic/section-descriptor.js} +2 -2
- package/dist/components/atomic/section-descriptor.js.map +1 -0
- package/dist/components/{select-listbox.js → atomic/select-listbox.js} +1 -1
- package/dist/components/atomic/select-listbox.js.map +1 -0
- package/dist/components/{select.js → atomic/select.js} +11 -11
- package/dist/components/atomic/select.js.map +1 -0
- package/dist/components/{separator.js → atomic/separator.js} +2 -2
- package/dist/components/atomic/separator.js.map +1 -0
- package/dist/components/{sheet.js → atomic/sheet.js} +9 -9
- package/dist/components/atomic/sheet.js.map +1 -0
- package/dist/components/{sidebar.js → atomic/sidebar.js} +5 -5
- package/dist/components/atomic/sidebar.js.map +1 -0
- package/dist/components/{skeleton.js → atomic/skeleton.js} +2 -2
- package/dist/components/atomic/skeleton.js.map +1 -0
- package/dist/components/{slider.js → atomic/slider.js} +3 -3
- package/dist/components/atomic/slider.js.map +1 -0
- package/dist/components/{sonner.js → atomic/sonner.js} +2 -2
- package/dist/components/atomic/sonner.js.map +1 -0
- package/dist/components/{spinner.js → atomic/spinner.js} +1 -1
- package/dist/components/atomic/spinner.js.map +1 -0
- package/dist/components/{status-item.js → atomic/status-item.js} +2 -2
- package/dist/components/atomic/status-item.js.map +1 -0
- package/dist/components/{switch.js → atomic/switch.js} +4 -4
- package/dist/components/atomic/switch.js.map +1 -0
- package/dist/components/{table.js → atomic/table.js} +2 -2
- package/dist/components/atomic/table.js.map +1 -0
- package/dist/components/{tabs.js → atomic/tabs.js} +4 -4
- package/dist/components/atomic/tabs.js.map +1 -0
- package/dist/components/{textarea.js → atomic/textarea.js} +3 -3
- package/dist/components/atomic/textarea.js.map +1 -0
- package/dist/components/{toggle-group.js → atomic/toggle-group.js} +2 -2
- package/dist/components/atomic/toggle-group.js.map +1 -0
- package/dist/components/{toggle.js → atomic/toggle.js} +3 -3
- package/dist/components/atomic/toggle.js.map +1 -0
- package/dist/components/{tooltip.js → atomic/tooltip.js} +8 -8
- package/dist/components/atomic/tooltip.js.map +1 -0
- package/dist/components/{use-count-tween.js → atomic/use-count-tween.js} +1 -1
- package/dist/components/atomic/use-count-tween.js.map +1 -0
- package/dist/components/{animated-number.js → derived/animated-number.js} +5 -5
- package/dist/components/derived/animated-number.js.map +1 -0
- package/dist/components/{carousel.js → derived/carousel.js} +4 -4
- package/dist/components/derived/carousel.js.map +1 -0
- package/dist/components/{command-palette.js → derived/command-palette.js} +6 -6
- package/dist/components/derived/command-palette.js.map +1 -0
- package/dist/components/{confirm-dialog.js → derived/confirm-dialog.js} +7 -7
- package/dist/components/derived/confirm-dialog.js.map +1 -0
- package/dist/components/{date-time-picker.js → derived/date-time-picker.js} +8 -8
- package/dist/components/derived/date-time-picker.js.map +1 -0
- package/dist/components/{field.js → derived/field.js} +6 -6
- package/dist/components/derived/field.js.map +1 -0
- package/dist/components/{form.js → derived/form.js} +4 -4
- package/dist/components/derived/form.js.map +1 -0
- package/dist/components/{icon-button.js → derived/icon-button.js} +4 -4
- package/dist/components/derived/icon-button.js.map +1 -0
- package/dist/components/{input-group.js → derived/input-group.js} +5 -5
- package/dist/components/derived/input-group.js.map +1 -0
- package/dist/components/{item.js → derived/item.js} +4 -4
- package/dist/components/derived/item.js.map +1 -0
- package/dist/components/{popover-form.js → derived/popover-form.js} +5 -5
- package/dist/components/derived/popover-form.js.map +1 -0
- package/dist/components/{settings.js → derived/settings.js} +3 -3
- package/dist/components/derived/settings.js.map +1 -0
- package/dist/components/{account-list.js → shell/account-list.js} +5 -5
- package/dist/components/shell/account-list.js.map +1 -0
- package/dist/components/{account-menu-profile.js → shell/account-menu-profile.js} +6 -6
- package/dist/components/shell/account-menu-profile.js.map +1 -0
- package/dist/components/{account-menu.js → shell/account-menu.js} +4 -4
- package/dist/components/shell/account-menu.js.map +1 -0
- package/dist/components/{account-settings-dialog.js → shell/account-settings-dialog.js} +6 -6
- package/dist/components/shell/account-settings-dialog.js.map +1 -0
- package/dist/components/{app-shell.js → shell/app-shell.js} +1 -1
- package/dist/components/shell/app-shell.js.map +1 -0
- package/dist/components/{aside.js → shell/aside.js} +6 -6
- package/dist/components/shell/aside.js.map +1 -0
- package/dist/components/{auth-wall.js → shell/auth-wall.js} +1 -1
- package/dist/components/shell/auth-wall.js.map +1 -0
- package/dist/components/{bottom-tab-bar.js → shell/bottom-tab-bar.js} +1 -1
- package/dist/components/shell/bottom-tab-bar.js.map +1 -0
- package/dist/components/{chat-composer.js → shell/chat-composer.js} +7 -7
- package/dist/components/shell/chat-composer.js.map +1 -0
- package/dist/components/{chat-message.js → shell/chat-message.js} +4 -4
- package/dist/components/shell/chat-message.js.map +1 -0
- package/dist/components/{chat-panel.js → shell/chat-panel.js} +3 -3
- package/dist/components/shell/chat-panel.js.map +1 -0
- package/dist/components/shell/chat-stream-text.js.map +1 -0
- package/dist/components/{chat-typing.js → shell/chat-typing.js} +2 -2
- package/dist/components/shell/chat-typing.js.map +1 -0
- package/dist/components/{footer.js → shell/footer.js} +1 -1
- package/dist/components/shell/footer.js.map +1 -0
- package/dist/components/{header-controls.js → shell/header-controls.js} +8 -8
- package/dist/components/shell/header-controls.js.map +1 -0
- package/dist/components/{list-rows.js → shell/list-rows.js} +1 -1
- package/dist/components/shell/list-rows.js.map +1 -0
- package/dist/components/{logo.js → shell/logo.js} +1 -1
- package/dist/components/shell/logo.js.map +1 -0
- package/dist/components/{mobile-app-bar.js → shell/mobile-app-bar.js} +1 -1
- package/dist/components/shell/mobile-app-bar.js.map +1 -0
- package/dist/components/{nav-badge.js → shell/nav-badge.js} +1 -1
- package/dist/components/shell/nav-badge.js.map +1 -0
- package/dist/components/{nav-collapse.js → shell/nav-collapse.js} +2 -2
- package/dist/components/shell/nav-collapse.js.map +1 -0
- package/dist/components/shell/nav-link.js.map +1 -0
- package/dist/components/shell/nav-model.js.map +1 -0
- package/dist/components/{page-frame.js → shell/page-frame.js} +2 -2
- package/dist/components/shell/page-frame.js.map +1 -0
- package/dist/components/{resource-error.js → shell/resource-error.js} +3 -3
- package/dist/components/shell/resource-error.js.map +1 -0
- package/dist/components/{section-nav.js → shell/section-nav.js} +1 -1
- package/dist/components/shell/section-nav.js.map +1 -0
- package/dist/components/shell/settled.js.map +1 -0
- package/dist/components/shell/shell-icons.js.map +1 -0
- package/dist/components/{shell-nav.js → shell/shell-nav.js} +1 -1
- package/dist/components/shell/shell-nav.js.map +1 -0
- package/dist/components/{smart-header.js → shell/smart-header.js} +2 -2
- package/dist/components/shell/smart-header.js.map +1 -0
- package/dist/components/{status-screen.js → shell/status-screen.js} +3 -3
- package/dist/components/shell/status-screen.js.map +1 -0
- package/dist/components/{system-banner.js → shell/system-banner.js} +2 -2
- package/dist/components/shell/system-banner.js.map +1 -0
- package/dist/components/{top-bar.js → shell/top-bar.js} +2 -2
- package/dist/components/shell/top-bar.js.map +1 -0
- package/dist/components/shell/use-shell-nav.js.map +1 -0
- package/dist/components/{workspace-tab.js → shell/workspace-tab.js} +4 -4
- package/dist/components/shell/workspace-tab.js.map +1 -0
- package/dist/icons/select-chevron.js.map +1 -0
- package/dist/icons/ui-icons.js.map +1 -0
- package/dist/index.js +96 -96
- package/package.json +2 -2
- package/dist/components/accordion.js.map +0 -1
- package/dist/components/account-list.js.map +0 -1
- package/dist/components/account-menu-profile.js.map +0 -1
- package/dist/components/account-menu.js.map +0 -1
- package/dist/components/account-settings-dialog.js.map +0 -1
- package/dist/components/alert-dialog.js.map +0 -1
- package/dist/components/alert.js.map +0 -1
- package/dist/components/animated-number-columns.js.map +0 -1
- package/dist/components/animated-number.js.map +0 -1
- package/dist/components/app-shell.js.map +0 -1
- package/dist/components/aside.js.map +0 -1
- package/dist/components/auth-wall.js.map +0 -1
- package/dist/components/avatar.js.map +0 -1
- package/dist/components/badge.js.map +0 -1
- package/dist/components/band.js.map +0 -1
- package/dist/components/bottom-tab-bar.js.map +0 -1
- package/dist/components/breadcrumb.js.map +0 -1
- package/dist/components/button-group.js.map +0 -1
- package/dist/components/button.js.map +0 -1
- package/dist/components/calendar.js.map +0 -1
- package/dist/components/card.js.map +0 -1
- package/dist/components/carousel.js.map +0 -1
- package/dist/components/chat-composer.js.map +0 -1
- package/dist/components/chat-message.js.map +0 -1
- package/dist/components/chat-panel.js.map +0 -1
- package/dist/components/chat-stream-text.js.map +0 -1
- package/dist/components/chat-typing.js.map +0 -1
- package/dist/components/checkbox.js.map +0 -1
- package/dist/components/collapsible.js.map +0 -1
- package/dist/components/command-palette.js.map +0 -1
- package/dist/components/command.js.map +0 -1
- package/dist/components/confirm-dialog.js.map +0 -1
- package/dist/components/container.js.map +0 -1
- package/dist/components/context-menu.js.map +0 -1
- package/dist/components/date-time-picker.js.map +0 -1
- package/dist/components/dialog.js.map +0 -1
- package/dist/components/drawer.js.map +0 -1
- package/dist/components/dropdown-menu.js.map +0 -1
- package/dist/components/empty.js.map +0 -1
- package/dist/components/field-context.js.map +0 -1
- package/dist/components/field.js.map +0 -1
- package/dist/components/footer.js.map +0 -1
- package/dist/components/form-row.js.map +0 -1
- package/dist/components/form.js.map +0 -1
- package/dist/components/header-controls.js.map +0 -1
- package/dist/components/hover-card.js.map +0 -1
- package/dist/components/icon-button.js.map +0 -1
- package/dist/components/info-tip.js.map +0 -1
- package/dist/components/input-group.js.map +0 -1
- package/dist/components/input-otp.js.map +0 -1
- package/dist/components/input.js.map +0 -1
- package/dist/components/item.js.map +0 -1
- package/dist/components/kbd.js.map +0 -1
- package/dist/components/key-value.js.map +0 -1
- package/dist/components/label.js.map +0 -1
- package/dist/components/list-rows.js.map +0 -1
- package/dist/components/logo.js.map +0 -1
- package/dist/components/menubar.js.map +0 -1
- package/dist/components/mobile-app-bar.js.map +0 -1
- package/dist/components/native-select.js.map +0 -1
- package/dist/components/nav-badge.js.map +0 -1
- package/dist/components/nav-collapse.js.map +0 -1
- package/dist/components/nav-link.js.map +0 -1
- package/dist/components/nav-model.js.map +0 -1
- package/dist/components/navigation-menu.js.map +0 -1
- package/dist/components/page-frame.js.map +0 -1
- package/dist/components/pagination.js.map +0 -1
- package/dist/components/popover-form.js.map +0 -1
- package/dist/components/popover.js.map +0 -1
- package/dist/components/progress.js.map +0 -1
- package/dist/components/radio-group.js.map +0 -1
- package/dist/components/resource-error.js.map +0 -1
- package/dist/components/scroll-area.js.map +0 -1
- package/dist/components/section-descriptor.js.map +0 -1
- package/dist/components/section-nav.js.map +0 -1
- package/dist/components/select-chevron.js.map +0 -1
- package/dist/components/select-listbox.js.map +0 -1
- package/dist/components/select.js.map +0 -1
- package/dist/components/separator.js.map +0 -1
- package/dist/components/settings.js.map +0 -1
- package/dist/components/settled.js.map +0 -1
- package/dist/components/sheet.js.map +0 -1
- package/dist/components/shell-icons.js.map +0 -1
- package/dist/components/shell-nav.js.map +0 -1
- package/dist/components/sidebar.js.map +0 -1
- package/dist/components/skeleton.js.map +0 -1
- package/dist/components/slider.js.map +0 -1
- package/dist/components/smart-header.js.map +0 -1
- package/dist/components/sonner.js.map +0 -1
- package/dist/components/spinner.js.map +0 -1
- package/dist/components/status-item.js.map +0 -1
- package/dist/components/status-screen.js.map +0 -1
- package/dist/components/switch.js.map +0 -1
- package/dist/components/system-banner.js.map +0 -1
- package/dist/components/table.js.map +0 -1
- package/dist/components/tabs.js.map +0 -1
- package/dist/components/textarea.js.map +0 -1
- package/dist/components/toggle-group.js.map +0 -1
- package/dist/components/toggle.js.map +0 -1
- package/dist/components/tooltip.js.map +0 -1
- package/dist/components/top-bar.js.map +0 -1
- package/dist/components/ui-icons.js.map +0 -1
- package/dist/components/use-count-tween.js.map +0 -1
- package/dist/components/use-shell-nav.js.map +0 -1
- package/dist/components/workspace-tab.js.map +0 -1
- /package/dist/components/{field-context.js → atomic/field-context.js} +0 -0
- /package/dist/components/{chat-stream-text.js → shell/chat-stream-text.js} +0 -0
- /package/dist/components/{nav-link.js → shell/nav-link.js} +0 -0
- /package/dist/components/{nav-model.js → shell/nav-model.js} +0 -0
- /package/dist/components/{settled.js → shell/settled.js} +0 -0
- /package/dist/components/{shell-icons.js → shell/shell-icons.js} +0 -0
- /package/dist/components/{use-shell-nav.js → shell/use-shell-nav.js} +0 -0
- /package/dist/{components → icons}/select-chevron.js +0 -0
- /package/dist/{components → icons}/ui-icons.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/account-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { Avatar, AvatarFallback } from \"../atomic/avatar\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"../atomic/dropdown-menu\";\nimport type { NavGroup } from \"./nav-model\";\nimport { useControllableState } from \"../../primitives/use-controllable-state\";\nimport {\n ProfileCard,\n ProfileSettingRows,\n ProfileTabs,\n type AccountMenuProfile,\n type AccountMenuTab,\n} from \"./account-menu-profile\";\n\nexport type { AccountMenuProfile, AccountMenuTab };\n\nexport interface AccountMenuLabels {\n /** Accessible name of the trigger. Default `\"Account\"`. */\n trigger?: string;\n /** Default `\"Manage account\"`. */\n manage?: string;\n /** Default `\"Switch account\"`. */\n switchAccount?: string;\n /** Default `\"Sign out\"`. */\n signOut?: string;\n}\n\nexport interface AccountMenuProps {\n account: { name?: string | null | undefined; email: string };\n /** The account center, on the identity provider's origin; no item without it. */\n manageHref?: string | undefined;\n /** A full page load to the sign-in that offers the account chooser. */\n switchHref: string;\n /** The service's own entries, between the account center and switching. Draws `label`, `icon`, `external`. */\n groups?: readonly NavGroup[];\n onSignOut: () => void;\n linkComponent?: React.ElementType;\n labels?: AccountMenuLabels;\n /**\n * The wide profile form: tabs, a profile card, language and theme rows. Absent,\n * the menu is the compact one it always was.\n */\n profile?: AccountMenuProfile;\n className?: string;\n}\n\nconst initials = (account: AccountMenuProps[\"account\"]) => {\n const words = account.name?.trim().split(/\\s+/).filter(Boolean) ?? [];\n const letters = words.length > 0 ? words.slice(0, 2).map(w => Array.from(w)[0]) : [Array.from(account.email)[0]];\n return letters.join(\"\").toUpperCase();\n};\n\n/**\n * Who is signed in, top right. The order is fixed — the account, the account\n * center, the service's groups, switching, signing out — so every service's\n * menu reads the same; a service adds groups and nothing else.\n */\nexport function AccountMenu({\n account,\n manageHref,\n switchHref,\n groups = [],\n onSignOut,\n linkComponent,\n labels = {},\n profile,\n className,\n}: AccountMenuProps) {\n const Link = linkComponent ?? \"a\";\n const tabs = profile?.tabs ?? [];\n const [tab, setTab] = useControllableState<string>({\n ...(profile?.tab !== undefined ? { value: profile.tab } : {}),\n defaultValue: tabs[0]?.id ?? \"\",\n ...(profile?.onTabChange ? { onChange: profile.onTabChange } : {}),\n });\n const otherTab = tabs.length > 0 && tab !== tabs[0]?.id ? tabs.find((t) => t.id === tab) : undefined;\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"account-menu-trigger\"\n aria-label={labels.trigger ?? \"Account\"}\n className={cn(\"rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\", className)}\n >\n <Avatar>\n <AvatarFallback className=\"text-xs font-medium text-ink\">{initials(account)}</AvatarFallback>\n </Avatar>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className={profile ? \"w-75\" : \"w-64\"}>\n {tabs.length > 0 && <ProfileTabs tabs={tabs} value={tab} onChange={setTab} label={profile?.tabsLabel} />}\n {otherTab ? (\n otherTab.content\n ) : (\n <>\n {profile ? (\n <ProfileCard\n name={account.name || account.email}\n plan={profile.plan}\n initials={initials(account)}\n onOpen={profile.onOpenProfile}\n />\n ) : (\n <DropdownMenuLabel className=\"flex flex-col\">\n {account.name && <span className=\"truncate text-ink\">{account.name}</span>}\n <span className=\"truncate font-normal text-ink-soft\">{account.email}</span>\n </DropdownMenuLabel>\n )}\n {profile && <DropdownMenuSeparator />}\n {manageHref && (\n <DropdownMenuItem asChild>\n <a href={manageHref}>{labels.manage ?? \"Manage account\"}</a>\n </DropdownMenuItem>\n )}\n {groups.map(group => (\n <React.Fragment key={group.id}>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n {group.label && <DropdownMenuLabel className=\"text-xs text-ink-soft\">{group.label}</DropdownMenuLabel>}\n {group.items.map(item => {\n const Item = item.external ? \"a\" : Link;\n const Icon = item.icon;\n return (\n <DropdownMenuItem key={item.id} asChild>\n <Item href={item.href}>\n {Icon && <Icon className=\"size-4\" />}\n {item.label}\n </Item>\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuGroup>\n </React.Fragment>\n ))}\n {profile && <ProfileSettingRows profile={profile} />}\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild>\n <a href={switchHref}>{labels.switchAccount ?? \"Switch account\"}</a>\n </DropdownMenuItem>\n <DropdownMenuItem variant={profile ? \"destructive\" : \"neutral\"} onClick={onSignOut}>\n {labels.signOut ?? \"Sign out\"}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
ACCOUNT_SETTINGS_DIALOG_SIDEBAR,
|
|
14
14
|
ACCOUNT_SETTINGS_DIALOG_TABS,
|
|
15
15
|
ACCOUNT_SETTINGS_DIALOG_TITLE
|
|
16
|
-
} from "
|
|
17
|
-
import { cn } from "
|
|
18
|
-
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "
|
|
19
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from "
|
|
20
|
-
import { IconButton } from "
|
|
21
|
-
import { UiIcon } from "
|
|
16
|
+
} from "../../generated/account-settings-dialog.js";
|
|
17
|
+
import { cn } from "../../lib/cn.js";
|
|
18
|
+
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from "../atomic/dialog.js";
|
|
19
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../atomic/tabs.js";
|
|
20
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
21
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
22
22
|
function AccountSettingsDialog({
|
|
23
23
|
open,
|
|
24
24
|
defaultOpen,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/account-settings-dialog.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n ACCOUNT_SETTINGS_DIALOG_CLOSE,\n ACCOUNT_SETTINGS_DIALOG_CONTENT,\n ACCOUNT_SETTINGS_DIALOG_DESCRIPTION,\n ACCOUNT_SETTINGS_DIALOG_ENTRY,\n ACCOUNT_SETTINGS_DIALOG_FOOTER,\n ACCOUNT_SETTINGS_DIALOG_HEADING,\n ACCOUNT_SETTINGS_DIALOG_LIST,\n ACCOUNT_SETTINGS_DIALOG_PANE,\n ACCOUNT_SETTINGS_DIALOG_PANEL,\n ACCOUNT_SETTINGS_DIALOG_SIDEBAR,\n ACCOUNT_SETTINGS_DIALOG_TABS,\n ACCOUNT_SETTINGS_DIALOG_TITLE,\n} from \"../../generated/account-settings-dialog\";\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogClose, DialogContent, DialogDescription, DialogTitle } from \"../atomic/dialog\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"../atomic/tabs\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface AccountSettingsSection {\n id: string;\n label: React.ReactNode;\n icon?: React.ReactNode;\n content: React.ReactNode;\n}\n\nexport interface AccountSettingsDialogLabels {\n /** The close button. Default `\"Close\"`. */\n close?: string;\n}\n\nexport interface AccountSettingsDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** The heading of the content pane. */\n title: React.ReactNode;\n description?: React.ReactNode;\n /** One sidebar entry each; the active one's `content` fills the pane. */\n sections: readonly AccountSettingsSection[];\n value?: string;\n defaultValue?: string;\n onValueChange?: (id: string) => void;\n /** Pinned under the sidebar entries: sign out, language. */\n sidebarFooter?: React.ReactNode;\n labels?: AccountSettingsDialogLabels;\n className?: string;\n}\n\n/**\n * The account modal: a vertical tab rail on the left, the active section on\n * the right. Sections are the caller's, so Profile and Security (or any other)\n * are built from `SettingsSection` and `SettingsRow` inside `content`.\n */\nexport function AccountSettingsDialog({\n open,\n defaultOpen,\n onOpenChange,\n title,\n description,\n sections,\n value,\n defaultValue,\n onValueChange,\n sidebarFooter,\n labels = {},\n className,\n}: AccountSettingsDialogProps) {\n const first = sections[0]?.id ?? \"\";\n return (\n <Dialog\n {...(open !== undefined ? { open } : {})}\n {...(defaultOpen !== undefined ? { defaultOpen } : {})}\n {...(onOpenChange ? { onOpenChange } : {})}\n >\n <DialogContent\n showCloseButton={false}\n className={cn(ACCOUNT_SETTINGS_DIALOG_CONTENT, className)}\n >\n <Tabs\n orientation=\"vertical\"\n {...(value !== undefined ? { value } : { defaultValue: defaultValue ?? first })}\n {...(onValueChange ? { onValueChange } : {})}\n className={ACCOUNT_SETTINGS_DIALOG_TABS}\n >\n <div className={ACCOUNT_SETTINGS_DIALOG_SIDEBAR}>\n <TabsList className={ACCOUNT_SETTINGS_DIALOG_LIST}>\n {sections.map((s) => (\n <TabsTrigger key={s.id} value={s.id} className={ACCOUNT_SETTINGS_DIALOG_ENTRY}>\n {s.icon}\n {s.label}\n </TabsTrigger>\n ))}\n </TabsList>\n {sidebarFooter != null && (\n <div className={ACCOUNT_SETTINGS_DIALOG_FOOTER}>{sidebarFooter}</div>\n )}\n </div>\n <div className={ACCOUNT_SETTINGS_DIALOG_PANE}>\n <div className={ACCOUNT_SETTINGS_DIALOG_HEADING}>\n <DialogTitle className={ACCOUNT_SETTINGS_DIALOG_TITLE}>{title}</DialogTitle>\n {description != null && (\n <DialogDescription className={ACCOUNT_SETTINGS_DIALOG_DESCRIPTION}>{description}</DialogDescription>\n )}\n </div>\n {sections.map((s) => (\n <TabsContent key={s.id} value={s.id} className={ACCOUNT_SETTINGS_DIALOG_PANEL}>\n {s.content}\n </TabsContent>\n ))}\n </div>\n </Tabs>\n <DialogClose asChild>\n <IconButton aria-label={labels.close ?? \"Close\"} className={ACCOUNT_SETTINGS_DIALOG_CLOSE}>\n <UiIcon name=\"x\" />\n </IconButton>\n </DialogClose>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"AAwFY,cAEI,YAFJ;AAxFZ,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,UAAU;AACnB,SAAS,QAAQ,aAAa,eAAe,mBAAmB,mBAAmB;AACnF,SAAS,MAAM,aAAa,UAAU,mBAAmB;AACzD,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AAqChB,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AACF,GAA+B;AAC7B,QAAM,QAAQ,SAAS,CAAC,GAAG,MAAM;AACjC,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,MACrC,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACnD,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,MAExC;AAAA,QAAC;AAAA;AAAA,UACC,iBAAiB;AAAA,UACjB,WAAW,GAAG,iCAAiC,SAAS;AAAA,UAExD;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,aAAY;AAAA,gBACX,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,EAAE,cAAc,gBAAgB,MAAM;AAAA,gBAC5E,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;AAAA,gBAC1C,WAAW;AAAA,gBAEX;AAAA,uCAAC,SAAI,WAAW,iCACd;AAAA,wCAAC,YAAS,WAAW,8BAClB,mBAAS,IAAI,CAAC,MACb,qBAAC,eAAuB,OAAO,EAAE,IAAI,WAAW,+BAC7C;AAAA,wBAAE;AAAA,sBACF,EAAE;AAAA,yBAFa,EAAE,EAGpB,CACD,GACH;AAAA,oBACC,iBAAiB,QAChB,oBAAC,SAAI,WAAW,gCAAiC,yBAAc;AAAA,qBAEnE;AAAA,kBACA,qBAAC,SAAI,WAAW,8BACd;AAAA,yCAAC,SAAI,WAAW,iCACd;AAAA,0CAAC,eAAY,WAAW,+BAAgC,iBAAM;AAAA,sBAC7D,eAAe,QACd,oBAAC,qBAAkB,WAAW,qCAAsC,uBAAY;AAAA,uBAEpF;AAAA,oBACC,SAAS,IAAI,CAAC,MACb,oBAAC,eAAuB,OAAO,EAAE,IAAI,WAAW,+BAC7C,YAAE,WADa,EAAE,EAEpB,CACD;AAAA,qBACH;AAAA;AAAA;AAAA,YACF;AAAA,YACA,oBAAC,eAAY,SAAO,MAClB,8BAAC,cAAW,cAAY,OAAO,SAAS,SAAS,WAAW,+BAC1D,8BAAC,UAAO,MAAK,KAAI,GACnB,GACF;AAAA;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/app-shell.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\n\n/** Where the shell swaps the bottom tab bar for the rail. */\nexport type ShellBreakpoint = \"md\" | \"lg\";\n\nexport interface AppShellProps extends React.ComponentProps<\"div\"> {\n /** The desktop rail — a `ShellNav`. Shown from `breakpoint` up. */\n rail?: React.ReactNode;\n /** The phone's tab bar — a `BottomTabBar`. Pinned to the viewport bottom below `breakpoint`. */\n tabBar?: React.ReactNode;\n /** A `TopBar`, pinned over the column from `breakpoint` up (a phone titles its screen with `MobileAppBar`). */\n topBar?: React.ReactNode;\n /** Strips above the page (`SystemBanner`s). An empty slot takes no room. */\n banner?: React.ReactNode;\n /** Right of the content column: an `AsidePanel` and its `AsideRail`, side by side. Sticks like the rail. */\n aside?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** Props for the `<main>` the page renders into (an `id` for a skip link, say). */\n mainProps?: React.ComponentProps<\"main\">;\n}\n\n// Tailwind only sees whole class strings, so each breakpoint spells its own.\nconst AT = {\n md: {\n rail: \"hidden md:flex\",\n tabBar: \"md:hidden\",\n topBar: \"hidden md:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] md:pb-0\",\n banner: \"md:px-8\",\n },\n lg: {\n rail: \"hidden lg:flex\",\n tabBar: \"lg:hidden\",\n topBar: \"hidden lg:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] lg:pb-0\",\n banner: \"lg:px-8\",\n },\n} as const;\n\nconst present = (node: React.ReactNode) => node !== null && node !== undefined && node !== false;\n\n/**\n * The signed-in app's frame: a rail beside a content column on a wide screen, a\n * bottom tab bar under it on a phone. Layout only — the nav, the top bar, the banners\n * and the page are slots, so the shell knows nothing about routes, roles or data.\n *\n * Fills the viewport less `--ev-shell-offset` (the room a host shell's own header\n * takes); the rail sticks under that offset at full remaining height and scrolls\n * by itself when it cannot fit. The column reserves the tab bar's height plus\n * the bottom safe area, so the bar never covers content.\n */\nexport function AppShell({\n rail,\n tabBar,\n topBar,\n banner,\n aside,\n breakpoint = \"lg\",\n mainProps,\n className,\n children,\n ...props\n}: AppShellProps) {\n const at = AT[breakpoint];\n return (\n <div\n data-slot=\"app-shell\"\n data-breakpoint={breakpoint}\n className={cn(\n \"flex min-h-[calc(100dvh-var(--ev-shell-offset,0px))] bg-background\",\n className,\n )}\n {...props}\n >\n {present(rail) && (\n <div\n data-slot=\"app-shell-rail\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\",\n at.rail,\n )}\n >\n {rail}\n </div>\n )}\n <div\n data-slot=\"app-shell-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col\", present(tabBar) && at.reserve)}\n >\n {present(topBar) && (\n <div\n data-slot=\"app-shell-top-bar\"\n className={cn(\"sticky top-[var(--ev-shell-offset,0px)] z-30\", at.topBar)}\n >\n {topBar}\n </div>\n )}\n {present(banner) && (\n <div\n data-slot=\"app-shell-banner\"\n className={cn(\"flex flex-col gap-2 px-4 pt-4 empty:hidden\", at.banner)}\n >\n {banner}\n </div>\n )}\n <main {...mainProps} className={cn(\"min-w-0 flex-1\", mainProps?.className)}>\n {children}\n </main>\n </div>\n {present(aside) && (\n <div\n data-slot=\"app-shell-aside\"\n className=\"sticky top-[var(--ev-shell-offset,0px)] flex h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\"\n >\n {aside}\n </div>\n )}\n {present(tabBar) && (\n <div\n data-slot=\"app-shell-tab-bar\"\n className={cn(\"fixed inset-x-0 bottom-0 z-40\", at.tabBar)}\n >\n {tabBar}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"AA6EQ,cAUF,YAVE;AA7ER,YAAY,WAAW;AACvB,SAAS,UAAU;AAuBnB,MAAM,KAAK;AAAA,EACT,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,UAAU,CAAC,SAA0B,SAAS,QAAQ,SAAS,UAAa,SAAS;AAYpF,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,KAAK,GAAG,UAAU;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,KACX;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,GAAG;AAAA,YACL;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,gCAAgC,QAAQ,MAAM,KAAK,GAAG,OAAO;AAAA,YAE1E;AAAA,sBAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,gDAAgD,GAAG,MAAM;AAAA,kBAEtE;AAAA;AAAA,cACH;AAAA,cAED,QAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,8CAA8C,GAAG,MAAM;AAAA,kBAEpE;AAAA;AAAA,cACH;AAAA,cAEF,oBAAC,UAAM,GAAG,WAAW,WAAW,GAAG,kBAAkB,WAAW,SAAS,GACtE,UACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,QAAQ,KAAK,KACZ;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA,QAED,QAAQ,MAAM,KACb;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,iCAAiC,GAAG,MAAM;AAAA,YAEvD;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import {
|
|
6
6
|
ASIDE_BODY,
|
|
7
7
|
ASIDE_FOOTER,
|
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
ASIDE_RAIL_BASE,
|
|
15
15
|
ASIDE_RAIL_SPACER,
|
|
16
16
|
asidePanelSideClasses
|
|
17
|
-
} from "
|
|
18
|
-
import { Drawer, DrawerContent } from "
|
|
19
|
-
import { IconButton } from "
|
|
20
|
-
import { Sheet, SheetContent } from "
|
|
21
|
-
import { UiIcon } from "
|
|
17
|
+
} from "../../generated/aside.js";
|
|
18
|
+
import { Drawer, DrawerContent } from "../atomic/drawer.js";
|
|
19
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
20
|
+
import { Sheet, SheetContent } from "../atomic/sheet.js";
|
|
21
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
22
22
|
function AsideHeader({ title, onBack, onClose, actions, labels = {}, className, ...props }) {
|
|
23
23
|
return /* @__PURE__ */ jsxs(
|
|
24
24
|
"div",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/aside.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n ASIDE_BODY,\n ASIDE_FOOTER,\n ASIDE_HEADER_BACK_ICON,\n ASIDE_HEADER_BASE,\n ASIDE_HEADER_TITLE,\n ASIDE_PANEL_DOCKED,\n ASIDE_PANEL_DRAWER,\n ASIDE_PANEL_SHEET,\n ASIDE_RAIL_BASE,\n ASIDE_RAIL_SPACER,\n asidePanelSideClasses,\n type AsidePanelSide,\n} from \"../../generated/aside\";\nimport { Drawer, DrawerContent } from \"../atomic/drawer\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Sheet, SheetContent } from \"../atomic/sheet\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface AsideHeaderLabels {\n back?: string;\n close?: string;\n}\n\nexport interface AsideHeaderProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n title: React.ReactNode;\n /** Shows the back button first. */\n onBack?: () => void;\n /** Shows the close button last. */\n onClose?: () => void;\n /** `IconButton`s between the title and the close button: pin, more. */\n actions?: React.ReactNode;\n labels?: AsideHeaderLabels;\n}\n\n/** The 44 px title row of an aside. */\nexport function AsideHeader({ title, onBack, onClose, actions, labels = {}, className, ...props }: AsideHeaderProps) {\n return (\n <div\n data-slot=\"aside-header\"\n className={cn(ASIDE_HEADER_BASE, className)}\n {...props}\n >\n {onBack && (\n <IconButton aria-label={labels.back ?? \"Back\"} size=\"sm\" onClick={onBack}>\n <UiIcon name=\"chevron-right\" className={ASIDE_HEADER_BACK_ICON} />\n </IconButton>\n )}\n <h2 className={ASIDE_HEADER_TITLE}>{title}</h2>\n {actions}\n {onClose && (\n <IconButton aria-label={labels.close ?? \"Close\"} size=\"sm\" onClick={onClose}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n );\n}\n\nexport interface AsideRailItem {\n id: string;\n /** Names the button and fills its tooltip. */\n label: string;\n icon: React.ReactNode;\n}\n\nexport interface AsideRailProps extends Omit<React.ComponentProps<\"div\">, \"onChange\"> {\n items: readonly AsideRailItem[];\n /** Pinned to the bottom, e.g. settings. */\n endItems?: readonly AsideRailItem[];\n /** The open aside's id; `null` when all are closed. */\n value: string | null;\n /** Pressing the open item's button asks for `null`. */\n onValueChange: (id: string | null) => void;\n /** Accessible name of the rail. Default `\"Panels\"`. */\n \"aria-label\"?: string;\n}\n\n/** The 44 px strip of icon buttons that opens and closes the asides. */\nexport function AsideRail({\n items,\n endItems = [],\n value,\n onValueChange,\n className,\n \"aria-label\": ariaLabel = \"Panels\",\n ...props\n}: AsideRailProps) {\n const button = (item: AsideRailItem) => (\n <IconButton\n key={item.id}\n aria-label={item.label}\n tooltip={item.label}\n pressed={value === item.id}\n onClick={() => onValueChange(value === item.id ? null : item.id)}\n >\n {item.icon}\n </IconButton>\n );\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n data-slot=\"aside-rail\"\n className={cn(ASIDE_RAIL_BASE, className)}\n {...props}\n >\n {items.map(button)}\n <span className={ASIDE_RAIL_SPACER} />\n {endItems.map(button)}\n </div>\n );\n}\n\nfunction useAtLeast(query: string): boolean {\n return React.useSyncExternalStore(\n (notify) => {\n if (typeof window.matchMedia !== \"function\") return () => {};\n const mq = window.matchMedia(query);\n mq.addEventListener(\"change\", notify);\n return () => mq.removeEventListener(\"change\", notify);\n },\n () => typeof window.matchMedia !== \"function\" || window.matchMedia(query).matches,\n // Server and first paint assume a wide screen; a phone swaps to the drawer on hydrate.\n () => true,\n );\n}\n\nexport type AsidePanelMode = \"docked\" | \"overlay\";\n\nexport interface AsidePanelProps extends Omit<React.ComponentProps<\"aside\">, \"title\"> {\n /** Names the panel. */\n \"aria-label\": string;\n mode?: AsidePanelMode;\n /** Which edge of the screen it hugs. Default `right`. */\n side?: AsidePanelSide;\n open?: boolean;\n /**\n * Escape, the scrim and the drawer drag call it with `false`. A docked panel\n * is only dismissable this way where it falls back to the drawer (below `md`).\n */\n onOpenChange?: (open: boolean) => void;\n /** An `AsideHeader`. */\n header?: React.ReactNode;\n /** Sticky under the body: actions. */\n footer?: React.ReactNode;\n /**\n * Below this width the panel floats on a drawer so the content keeps its room.\n * `false` keeps it docked (or on a sheet) at every width, for a framed preview\n * or an embedded layout where the viewport says nothing about the room.\n */\n collapseBelow?: \"md\" | false;\n /** Docked 380 (260 for a left navigation), overlay 420: a width class overrides it. */\n className?: string;\n}\n\n/**\n * A side panel. `docked` sits in the layout and takes room from the content;\n * `overlay` floats over a scrim on a Sheet. Below `md` both use a bottom Drawer.\n * The edge facing the content is the only border.\n */\nexport function AsidePanel({\n mode = \"docked\",\n side = \"right\",\n open = true,\n onOpenChange,\n header,\n footer,\n collapseBelow = \"md\",\n className,\n children,\n ...props\n}: AsidePanelProps) {\n const viewportWide = useAtLeast(\"(min-width: 768px)\");\n const wide = collapseBelow === false || viewportWide;\n const body = (\n <>\n {header}\n <div data-slot=\"aside-body\" className={ASIDE_BODY}>\n {children}\n </div>\n {footer != null && (\n <div data-slot=\"aside-footer\" className={ASIDE_FOOTER}>\n {footer}\n </div>\n )}\n </>\n );\n\n if (mode === \"docked\" && wide) {\n if (!open) return null;\n return (\n <aside\n data-slot=\"aside-panel\"\n data-mode=\"docked\"\n data-side={side}\n className={cn(ASIDE_PANEL_DOCKED, asidePanelSideClasses[side], className)}\n {...props}\n >\n {body}\n </aside>\n );\n }\n\n // Below md there is no room beside the content, so a docked panel floats like an overlay.\n const label = props[\"aria-label\"];\n if (!wide) {\n return (\n <Drawer open={open} {...(onOpenChange ? { onOpenChange } : {})} direction=\"bottom\">\n <DrawerContent aria-label={label} className={ASIDE_PANEL_DRAWER}>\n {body}\n </DrawerContent>\n </Drawer>\n );\n }\n return (\n <Sheet open={open} {...(onOpenChange ? { onOpenChange } : {})}>\n <SheetContent\n side={side}\n aria-label={label}\n className={cn(ASIDE_PANEL_SHEET, className)}\n >\n {body}\n </SheetContent>\n </Sheet>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
EmptyHeader,
|
|
8
8
|
EmptyMedia,
|
|
9
9
|
EmptyTitle
|
|
10
|
-
} from "
|
|
10
|
+
} from "../atomic/empty.js";
|
|
11
11
|
function AuthWall({ title, description, children, ...props }) {
|
|
12
12
|
return /* @__PURE__ */ jsxs(Empty, { ...props, children: [
|
|
13
13
|
/* @__PURE__ */ jsxs(EmptyHeader, { children: [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/auth-wall.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from \"../atomic/empty\";\n\n/**\n * What a section shows a caller it is not for: an `Empty` with a lock, a title, why,\n * and an action slot. A guest gets the sign-in button there; a signed-in caller\n * without the permission gets no action, or the host's own (request access).\n */\nexport interface AuthWallProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n title: React.ReactNode;\n description: React.ReactNode;\n}\n\nexport function AuthWall({ title, description, children, ...props }: AuthWallProps) {\n return (\n <Empty {...props}>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <LockIcon />\n </EmptyMedia>\n <EmptyTitle>{title}</EmptyTitle>\n <EmptyDescription>{description}</EmptyDescription>\n </EmptyHeader>\n <EmptyContent>{children}</EmptyContent>\n </Empty>\n );\n}\n\n// lucide `lock`, inlined so the kit keeps its zero-icon-dep footprint.\nfunction LockIcon() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n >\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\" />\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\" />\n </svg>\n );\n}\n"],"mappings":"AAuBM,SAEI,KAFJ;AAvBN,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYA,SAAS,SAAS,EAAE,OAAO,aAAa,UAAU,GAAG,MAAM,GAAkB;AAClF,SACE,qBAAC,SAAO,GAAG,OACT;AAAA,yBAAC,eACC;AAAA,0BAAC,cAAW,SAAQ,QAClB,8BAAC,YAAS,GACZ;AAAA,MACA,oBAAC,cAAY,iBAAM;AAAA,MACnB,oBAAC,oBAAkB,uBAAY;AAAA,OACjC;AAAA,IACA,oBAAC,gBAAc,UAAS;AAAA,KAC1B;AAEJ;AAGA,SAAS,WAAW;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAa;AAAA,MACb,eAAc;AAAA,MACd,gBAAe;AAAA,MACf,eAAW;AAAA,MAEX;AAAA,4BAAC,UAAK,GAAE,KAAI,GAAE,MAAK,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI,IAAG,KAAI;AAAA,QACxD,oBAAC,UAAK,GAAE,4BAA2B;AAAA;AAAA;AAAA,EACrC;AAEJ;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import { NavBadge } from "./nav-badge.js";
|
|
6
6
|
import { labelTitle, NavLink } from "./nav-link.js";
|
|
7
7
|
import { resolveActiveNavItem } from "./nav-model.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/bottom-tab-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { NavBadge } from \"./nav-badge\";\nimport { labelTitle, NavLink, type NavLinkCallbacks } from \"./nav-link\";\nimport { resolveActiveNavItem, type NavItem } from \"./nav-model\";\nimport { usePendingNavigation } from \"./use-shell-nav\";\n\nexport interface BottomTabBarLabels {\n /** Accessible name of the bar. Default `\"Main\"`. */\n nav?: string;\n /** Spoken text of a numeric badge, given the real count. Default `\"{n} new\"`. */\n badge?: (count: number) => string;\n}\n\nexport interface BottomTabBarProps\n extends Omit<React.ComponentProps<\"nav\">, \"children\">,\n NavLinkCallbacks {\n /**\n * The tabs, in order. Five is the most a phone holds — every label gets a\n * fifth of ~390px. A tab standing in for several rail rows lists their paths\n * in `also`. `trailing` is not drawn here; `badge` sits on the icon's corner.\n */\n items: readonly NavItem[];\n pathname: string;\n linkComponent?: React.ElementType;\n labels?: BottomTabBarLabels;\n}\n\n/**\n * The mobile tab bar: equal tabs, one rule that slides between them, and a\n * corner badge. Position-agnostic — `AppShell` pins it to the bottom of the\n * viewport and hides it from its breakpoint up; the bar itself reserves the\n * bottom safe area under its `--shell-tab-bar-h`.\n *\n * The rule is a single node translated by whole tab widths (it is exactly one\n * tab wide, so the two cannot drift); on a route no tab claims it fades out\n * where it stands. That is why each tab is `min-w-0`: a long label must\n * truncate, never widen its tab off the rule's arithmetic.\n */\nexport function BottomTabBar({\n items,\n pathname,\n linkComponent,\n onItemIntent,\n onNavigate,\n labels = {},\n className,\n ...props\n}: BottomTabBarProps) {\n const [pendingId, markPending] = usePendingNavigation(pathname);\n const currentId =\n (pendingId !== null && items.some((i) => i.id === pendingId) ? pendingId : undefined) ??\n resolveActiveNavItem(items, pathname)?.id;\n const at = items.findIndex((i) => i.id === currentId);\n return (\n <nav\n data-slot=\"tab-bar\"\n aria-label={labels.nav ?? \"Main\"}\n className={cn(\n \"relative box-content flex h-(--shell-tab-bar-h) items-center border-t border-border bg-secondary px-2 pb-[env(safe-area-inset-bottom,0px)]\",\n className,\n )}\n {...props}\n >\n <span\n data-slot=\"tab-bar-marker\"\n data-state={at >= 0 ? \"active\" : \"idle\"}\n aria-hidden\n className=\"pointer-events-none absolute left-2 top-0 flex justify-center\"\n // `left-2` + `1rem` restate the bar's `px-2`: the rule is one tab wide.\n style={{\n width: `calc((100% - 1rem) / ${Math.max(items.length, 1)})`,\n translate: `${Math.max(at, 0) * 100}% 0`,\n }}\n >\n <span className=\"h-0.5 w-10 rounded-full bg-primary-ink\" />\n </span>\n {items.map((item) => {\n const Icon = item.icon;\n const active = item.id === currentId;\n return (\n <NavLink\n key={item.id}\n item={item}\n current={active}\n pending={item.id === pendingId}\n linkComponent={linkComponent}\n markPending={markPending}\n onItemIntent={onItemIntent}\n onNavigate={onNavigate}\n className={cn(\n \"flex min-w-0 flex-1 flex-col items-center justify-center gap-0.5 rounded-lg py-1 font-medium transition-colors\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-secondary\",\n active ? \"text-primary-ink\" : \"text-ink-soft hover:text-ink\",\n item.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n <span className=\"relative shrink-0\">\n {Icon ? <Icon className=\"size-5\" /> : <span className=\"block size-5\" />}\n {typeof item.badge === \"number\" ? (\n <NavBadge\n count={item.badge}\n variant=\"corner\"\n {...(labels.badge ? { label: labels.badge } : {})}\n />\n ) : (\n item.badge\n )}\n </span>\n <span className=\"w-full truncate text-center text-xs\" title={labelTitle(item.label)}>\n {item.label}\n </span>\n </NavLink>\n );\n })}\n </nav>\n );\n}\n"],"mappings":";AA6EQ,cAsBI,YAtBJ;AA3ER,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,gBAAgB;AACzB,SAAS,YAAY,eAAsC;AAC3D,SAAS,4BAA0C;AACnD,SAAS,4BAA4B;AAkC9B,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,CAAC,WAAW,WAAW,IAAI,qBAAqB,QAAQ;AAC9D,QAAM,aACH,cAAc,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS,IAAI,YAAY,WAC3E,qBAAqB,OAAO,QAAQ,GAAG;AACzC,QAAM,KAAK,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,SAAS;AACpD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAY,OAAO,OAAO;AAAA,MAC1B,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,cAAY,MAAM,IAAI,WAAW;AAAA,YACjC,eAAW;AAAA,YACX,WAAU;AAAA,YAEV,OAAO;AAAA,cACL,OAAO,wBAAwB,KAAK,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA,cACxD,WAAW,GAAG,KAAK,IAAI,IAAI,CAAC,IAAI,GAAG;AAAA,YACrC;AAAA,YAEA,8BAAC,UAAK,WAAU,0CAAyC;AAAA;AAAA,QAC3D;AAAA,QACC,MAAM,IAAI,CAAC,SAAS;AACnB,gBAAM,OAAO,KAAK;AAClB,gBAAM,SAAS,KAAK,OAAO;AAC3B,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC;AAAA,cACA,SAAS;AAAA,cACT,SAAS,KAAK,OAAO;AAAA,cACrB;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,WAAW;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA,SAAS,qBAAqB;AAAA,gBAC9B,KAAK,YAAY;AAAA,cACnB;AAAA,cAEA;AAAA,qCAAC,UAAK,WAAU,qBACb;AAAA,yBAAO,oBAAC,QAAK,WAAU,UAAS,IAAK,oBAAC,UAAK,WAAU,gBAAe;AAAA,kBACpE,OAAO,KAAK,UAAU,WACrB;AAAA,oBAAC;AAAA;AAAA,sBACC,OAAO,KAAK;AAAA,sBACZ,SAAQ;AAAA,sBACP,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;AAAA;AAAA,kBACjD,IAEA,KAAK;AAAA,mBAET;AAAA,gBACA,oBAAC,UAAK,WAAU,uCAAsC,OAAO,WAAW,KAAK,KAAK,GAC/E,eAAK,OACR;AAAA;AAAA;AAAA,YA7BK,KAAK;AAAA,UA8BZ;AAAA,QAEJ,CAAC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -11,19 +11,19 @@ import {
|
|
|
11
11
|
CHAT_COMPOSER_STREAMING,
|
|
12
12
|
CHAT_COMPOSER_TOOLBAR,
|
|
13
13
|
MODEL_CHIP_BASE
|
|
14
|
-
} from "
|
|
15
|
-
import { cn } from "
|
|
16
|
-
import { useControllableState } from "
|
|
14
|
+
} from "../../generated/chat-composer.js";
|
|
15
|
+
import { cn } from "../../lib/cn.js";
|
|
16
|
+
import { useControllableState } from "../../primitives/use-controllable-state.js";
|
|
17
17
|
import {
|
|
18
18
|
DropdownMenu,
|
|
19
19
|
DropdownMenuContent,
|
|
20
20
|
DropdownMenuRadioGroup,
|
|
21
21
|
DropdownMenuRadioItem,
|
|
22
22
|
DropdownMenuTrigger
|
|
23
|
-
} from "
|
|
24
|
-
import { IconButton } from "
|
|
25
|
-
import { Textarea } from "
|
|
26
|
-
import { UiIcon } from "
|
|
23
|
+
} from "../atomic/dropdown-menu.js";
|
|
24
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
25
|
+
import { Textarea } from "../atomic/textarea.js";
|
|
26
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
27
27
|
function ModelChip({ models, value, onValueChange, "aria-label": ariaLabel = "Model", className }) {
|
|
28
28
|
const current = models.find((m) => m.id === value);
|
|
29
29
|
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-composer.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n CHAT_COMPOSER_ATTACHMENTS,\n CHAT_COMPOSER_BASE,\n CHAT_COMPOSER_FIELD,\n CHAT_COMPOSER_HINT,\n CHAT_COMPOSER_IDLE,\n CHAT_COMPOSER_SPACER,\n CHAT_COMPOSER_STREAMING,\n CHAT_COMPOSER_TOOLBAR,\n MODEL_CHIP_BASE,\n} from \"../../generated/chat-composer\";\nimport { cn } from \"../../lib/cn\";\nimport { useControllableState } from \"../../primitives/use-controllable-state\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from \"../atomic/dropdown-menu\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Textarea } from \"../atomic/textarea\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface ModelOption {\n id: string;\n label: string;\n}\n\nexport interface ModelChipProps {\n models: readonly ModelOption[];\n value: string;\n onValueChange: (id: string) => void;\n /** Names the menu. Default `\"Model\"`. */\n \"aria-label\"?: string;\n className?: string;\n}\n\n/** The small bordered button naming the model; opens a radio menu of the options. */\nexport function ModelChip({ models, value, onValueChange, \"aria-label\": ariaLabel = \"Model\", className }: ModelChipProps) {\n const current = models.find((m) => m.id === value);\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n aria-label={`${ariaLabel}: ${current?.label ?? value}`}\n data-slot=\"model-chip\"\n className={cn(MODEL_CHIP_BASE, className)}\n >\n {current?.label ?? value}\n <UiIcon name=\"chevron-down\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuRadioGroup value={value} onValueChange={onValueChange}>\n {models.map((m) => (\n <DropdownMenuRadioItem key={m.id} value={m.id}>\n {m.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nexport interface ChatComposerLabels {\n /** Names the message field. Default `\"Message\"`. */\n field?: string;\n send?: string;\n stop?: string;\n attach?: string;\n}\n\nexport interface ChatComposerProps extends Omit<React.ComponentProps<\"div\">, \"onChange\" | \"defaultValue\"> {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n /** Enter or the send button, with the trimmed text. The field clears afterwards. */\n onSend: (text: string) => void;\n /** The assistant is answering: the button becomes Stop. Enter still queues a follow-up. */\n streaming?: boolean;\n onStop?: () => void;\n placeholder?: string;\n /** Shows the attach button. */\n onAttach?: () => void;\n /** A row of `ChatAttachment`s above the field. */\n attachments?: React.ReactNode;\n /** Model chips and other toolbar controls, after the attach button. */\n tools?: React.ReactNode;\n /** A caption in the toolbar: \"Enter to send · Shift+Enter for a new line\". */\n hint?: React.ReactNode;\n disabled?: boolean;\n labels?: ChatComposerLabels;\n}\n\nexport function ChatComposer({\n value,\n defaultValue = \"\",\n onValueChange,\n onSend,\n streaming = false,\n onStop,\n placeholder = \"Ask the assistant…\",\n onAttach,\n attachments,\n tools,\n hint,\n disabled = false,\n labels = {},\n className,\n ...props\n}: ChatComposerProps) {\n const [text, setText] = useControllableState<string>({\n ...(value !== undefined ? { value } : {}),\n defaultValue,\n ...(onValueChange ? { onChange: onValueChange } : {}),\n });\n const trimmed = text.trim();\n const root = React.useRef<HTMLDivElement>(null);\n\n const send = () => {\n if (trimmed === \"\" || disabled) return;\n onSend(trimmed);\n setText(\"\");\n };\n\n return (\n <div\n ref={root}\n data-slot=\"chat-composer\"\n data-state={streaming ? \"streaming\" : \"default\"}\n className={cn(\n CHAT_COMPOSER_BASE,\n streaming ? CHAT_COMPOSER_STREAMING : CHAT_COMPOSER_IDLE,\n className,\n )}\n {...props}\n >\n {attachments != null && <div className={CHAT_COMPOSER_ATTACHMENTS}>{attachments}</div>}\n <Textarea\n rows={1}\n value={text}\n disabled={disabled}\n placeholder={placeholder}\n aria-label={labels.field ?? \"Message\"}\n onChange={(e) => setText(e.target.value)}\n onKeyDown={(e) => {\n // Enter during IME composition confirms the candidate; it must not send. Safari reports the\n // confirming Enter after compositionend, with isComposing already false and keyCode 229.\n if (e.key === \"Enter\" && !e.shiftKey && !e.nativeEvent.isComposing && e.keyCode !== 229) {\n e.preventDefault();\n send();\n }\n }}\n className={CHAT_COMPOSER_FIELD}\n />\n <div className={CHAT_COMPOSER_TOOLBAR}>\n {onAttach && (\n <IconButton aria-label={labels.attach ?? \"Attach file\"} size=\"sm\" onClick={onAttach} disabled={disabled}>\n <UiIcon name=\"plus\" />\n </IconButton>\n )}\n {tools}\n <span className={CHAT_COMPOSER_SPACER} />\n {hint != null && <span className={CHAT_COMPOSER_HINT}>{hint}</span>}\n {streaming ? (\n <IconButton\n variant=\"outline\"\n aria-label={labels.stop ?? \"Stop\"}\n onClick={() => {\n onStop?.();\n // The Stop button turns into Send, so focus would otherwise stay on a swapped-out element.\n root.current?.querySelector(\"textarea\")?.focus();\n }}\n >\n <UiIcon name=\"stop\" />\n </IconButton>\n ) : (\n <IconButton\n variant=\"outline\"\n aria-label={labels.send ?? \"Send\"}\n onClick={send}\n disabled={disabled || trimmed === \"\"}\n >\n <UiIcon name=\"arrow-up\" />\n </IconButton>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import {
|
|
6
6
|
CHAT_ATTACHMENT_BASE,
|
|
7
7
|
CHAT_ATTACHMENT_BODY,
|
|
@@ -39,10 +39,10 @@ import {
|
|
|
39
39
|
chatBubbleVariantClasses,
|
|
40
40
|
chatStatusDotClasses,
|
|
41
41
|
chatToolMetaClasses
|
|
42
|
-
} from "
|
|
42
|
+
} from "../../generated/chat-message.js";
|
|
43
43
|
import { ChatStreamText } from "./chat-stream-text.js";
|
|
44
|
-
import { IconButton } from "
|
|
45
|
-
import { UiIcon } from "
|
|
44
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
45
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
46
46
|
function ChatBubble({ variant = "assistant", streaming = false, className, children, ...props }) {
|
|
47
47
|
const [live, setLive] = React.useState(streaming);
|
|
48
48
|
if (streaming && !live) setLive(true);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n CHAT_ATTACHMENT_BASE,\n CHAT_ATTACHMENT_BODY,\n CHAT_ATTACHMENT_ICON,\n CHAT_ATTACHMENT_META,\n CHAT_ATTACHMENT_NAME,\n CHAT_ATTACHMENT_PROGRESS,\n CHAT_ATTACHMENT_PROGRESS_FILL,\n CHAT_ATTACHMENT_ROW,\n CHAT_BUBBLE_BASE,\n CHAT_MARKER_DOT,\n CHAT_MARKER_RULE,\n CHAT_MARKER_SEPARATOR,\n CHAT_MARKER_SEPARATOR_LABEL,\n CHAT_MARKER_STATUS,\n CHAT_MARKER_TOOL,\n CHAT_MARKER_TOOL_BODY,\n CHAT_MARKER_TOOL_BORDER,\n CHAT_MARKER_TOOL_CHECK,\n CHAT_MARKER_TOOL_DETAIL,\n CHAT_MARKER_TOOL_ERROR_BORDER,\n CHAT_MARKER_TOOL_ICON,\n CHAT_MARKER_TOOL_META,\n CHAT_MARKER_TOOL_TITLE,\n CHAT_MESSAGE_AVATAR,\n CHAT_MESSAGE_AVATAR_TILE,\n CHAT_MESSAGE_BASE,\n CHAT_MESSAGE_BODY,\n CHAT_MESSAGE_BODY_ASSISTANT,\n CHAT_MESSAGE_BODY_USER,\n CHAT_MESSAGE_HEADER,\n CHAT_MESSAGE_NAME,\n CHAT_MESSAGE_TIME,\n CHAT_MESSAGE_USER,\n chatBubbleVariantClasses,\n chatStatusDotClasses,\n chatToolMetaClasses,\n} from \"../../generated/chat-message\";\nimport type { ChatBubbleVariant, ChatStatusState, ChatToolState } from \"../../generated/chat-message\";\nimport { ChatStreamText } from \"./chat-stream-text\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport type { ChatBubbleVariant };\n\nexport interface ChatBubbleProps extends React.ComponentProps<\"div\"> {\n variant?: ChatBubbleVariant;\n /**\n * Still receiving text: a soft caret pulses after it, and string children\n * fade in word by word (`ChatStreamText`). Stays set until the text is complete.\n */\n streaming?: boolean;\n}\n\n/** One block of message text; `assistant` has no box so long answers read as a page. */\nexport function ChatBubble({ variant = \"assistant\", streaming = false, className, children, ...props }: ChatBubbleProps) {\n // Once a bubble has streamed it keeps its word wrappers and its caret slot, so\n // the last words finish landing and the caret fades out instead of vanishing.\n const [live, setLive] = React.useState(streaming);\n if (streaming && !live) setLive(true);\n return (\n <div\n data-slot=\"chat-bubble\"\n data-variant={variant}\n data-streaming={streaming ? \"\" : undefined}\n data-caret={live ? \"\" : undefined}\n className={cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className)}\n {...props}\n >\n {live && typeof children === \"string\" ? <ChatStreamText text={children} streaming={streaming} /> : children}\n </div>\n );\n}\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"div\">, \"role\"> {\n role: \"assistant\" | \"user\";\n /** \"Assistant\", \"You\". */\n name?: React.ReactNode;\n /** Already formatted: \"13:04\". */\n time?: React.ReactNode;\n /** The assistant's tile; a default sparkle tile when omitted. */\n avatar?: React.ReactNode;\n /** Drops the name row and the avatar, for a narrow dock. */\n compact?: boolean;\n}\n\n/** A turn: who spoke, when, then the bubbles and markers. Fades in and rises once when it mounts. */\nexport function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }: ChatMessageProps) {\n const user = role === \"user\";\n return (\n <div\n data-slot=\"chat-message\"\n data-role={role}\n data-enter=\"rise\"\n className={cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className)}\n {...props}\n >\n {!user && !compact && (\n <div className={CHAT_MESSAGE_AVATAR}>\n {avatar ?? (\n <span className={CHAT_MESSAGE_AVATAR_TILE}>\n <UiIcon name=\"sparkles\" />\n </span>\n )}\n </div>\n )}\n <div className={cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT)}>\n {!compact && (name != null || time != null) && (\n <div className={CHAT_MESSAGE_HEADER}>\n {name != null && <span className={CHAT_MESSAGE_NAME}>{name}</span>}\n {time != null && <span className={CHAT_MESSAGE_TIME}>{time}</span>}\n </div>\n )}\n {children}\n </div>\n </div>\n );\n}\n\nexport type ChatMarkerProps = Omit<React.ComponentProps<\"div\">, \"title\"> &\n (\n | { type: \"status\"; state?: ChatStatusState }\n | {\n type: \"tool\";\n state?: ChatToolState;\n /** The tool's name. */\n title: React.ReactNode;\n detail?: React.ReactNode;\n /** The right-hand note: \"Running…\", \"0.4s\", \"Failed · 1.2s\". */\n meta?: React.ReactNode;\n }\n | { type: \"separator\" }\n );\n\n/** The quiet lines between messages: progress, a tool call, or a day break. */\nexport function ChatMarker(allProps: ChatMarkerProps) {\n if (allProps.type === \"separator\") {\n const { type: _type, className, children, ...rest } = allProps;\n return (\n <div data-slot=\"chat-marker\" data-type=\"separator\" className={cn(CHAT_MARKER_SEPARATOR, className)} {...rest}>\n <span className={CHAT_MARKER_RULE} />\n <span className={CHAT_MARKER_SEPARATOR_LABEL}>{children}</span>\n <span className={CHAT_MARKER_RULE} />\n </div>\n );\n }\n if (allProps.type === \"status\") {\n const { type: _type, state = \"done\", className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"status\"\n data-state={state}\n role={state === \"running\" ? \"status\" : undefined}\n className={cn(CHAT_MARKER_STATUS, className)}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(CHAT_MARKER_DOT, chatStatusDotClasses[state])}\n />\n {children}\n </div>\n );\n }\n const { type: _type, state = \"done\", title, detail, meta, className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"tool\"\n data-state={state}\n className={cn(\n CHAT_MARKER_TOOL,\n state === \"error\" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,\n className,\n )}\n {...rest}\n >\n <UiIcon name={state === \"error\" ? \"alert\" : \"sparkles\"} className={CHAT_MARKER_TOOL_ICON} />\n <div className={CHAT_MARKER_TOOL_BODY}>\n <span className={CHAT_MARKER_TOOL_TITLE}>{title}</span>\n {detail != null && <span className={CHAT_MARKER_TOOL_DETAIL}>{detail}</span>}\n {children}\n </div>\n {meta != null && (\n <span className={cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state])}>{meta}\n </span>\n )}\n {state === \"done\" && <UiIcon name=\"check\" className={CHAT_MARKER_TOOL_CHECK} />}\n </div>\n );\n}\n\nexport interface ChatAttachmentProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n type?: \"file\" | \"image\";\n state?: \"ready\" | \"uploading\";\n name: string;\n /** \"PDF · 1.2 MB\"; while uploading, defaults to the percentage. */\n meta?: React.ReactNode;\n /** 0 to 100, while uploading. */\n progress?: number;\n /** Shows the remove button. */\n onRemove?: () => void;\n /** Default `\"Remove {name}\"`. */\n removeLabel?: string;\n}\n\n/** A card for a file in a message or in the composer. */\nexport function ChatAttachment({\n type = \"file\",\n state = \"ready\",\n name,\n meta,\n progress = 0,\n onRemove,\n removeLabel,\n className,\n ...props\n}: ChatAttachmentProps) {\n const pct = Math.min(100, Math.max(0, Math.round(progress)));\n return (\n <div\n data-slot=\"chat-attachment\"\n data-type={type}\n data-state={state}\n className={cn(CHAT_ATTACHMENT_BASE, className)}\n {...props}\n >\n <div className={CHAT_ATTACHMENT_ROW}>\n <span className={CHAT_ATTACHMENT_ICON}>\n <UiIcon name={type === \"image\" ? \"image\" : \"file\"} />\n </span>\n <div className={CHAT_ATTACHMENT_BODY}>\n <span className={CHAT_ATTACHMENT_NAME}>{name}</span>\n <span className={CHAT_ATTACHMENT_META}>{meta ?? (state === \"uploading\" ? `Uploading… ${pct}%` : null)}</span>\n </div>\n {onRemove && (\n <IconButton aria-label={removeLabel ?? `Remove ${name}`} size=\"sm\" onClick={onRemove}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n {state === \"uploading\" && (\n <div\n role=\"progressbar\"\n aria-label={name}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct}\n className={CHAT_ATTACHMENT_PROGRESS}\n >\n <div className={CHAT_ATTACHMENT_PROGRESS_FILL} style={{ width: `${pct}%` }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_type","className","children","rest","state"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import { AsideHeader } from "./aside.js";
|
|
6
|
-
import { IconButton } from "
|
|
7
|
-
import { UiIcon } from "
|
|
6
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
7
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
8
8
|
const PIN_SLACK = 24;
|
|
9
9
|
function ChatPanel({ children, empty, composer, streaming = false, labels = {}, className, ...props }) {
|
|
10
10
|
const scroller = React.useRef(null);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-panel.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport { AsideHeader } from \"./aside\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\n// Within this many pixels of the end still counts as \"at the end\": sub-pixel\n// scroll positions and a growing last line would otherwise unpin the reader.\nconst PIN_SLACK = 24;\n\nexport interface ChatPanelLabels {\n /** The scroll-to-end button. Default `\"Jump to latest\"`. */\n jump?: string;\n /** Names the log region. Default `\"Conversation\"`. */\n log?: string;\n}\n\nexport interface ChatPanelProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n /** `ChatMessage`s and `ChatMarker`s, oldest first. */\n children?: React.ReactNode;\n /** Shown instead of the log while there is nothing to read. */\n empty?: React.ReactNode;\n /** The composer, under the log. */\n composer?: React.ReactNode;\n /**\n * The last turn is still arriving. The log is marked busy, so a screen reader\n * waits for the finished text instead of announcing every chunk.\n */\n streaming?: boolean;\n labels?: ChatPanelLabels;\n}\n\n/**\n * The conversation: a scrolling log that follows new content while the reader\n * is at the end, and lets go the moment they scroll up. The jump button brings\n * them back. The log is a polite live region, so new turns are announced\n * without taking focus.\n */\nexport function ChatPanel({ children, empty, composer, streaming = false, labels = {}, className, ...props }: ChatPanelProps) {\n const scroller = React.useRef<HTMLDivElement>(null);\n const pinned = React.useRef(true);\n const [away, setAway] = React.useState(false);\n const [scrolled, setScrolled] = React.useState(false);\n // `count` would call `false` a child, and `{list.length > 0 && …}` is common.\n const isEmpty = React.Children.toArray(children).length === 0 && empty != null;\n\n const toEnd = () => {\n const el = scroller.current;\n if (el) el.scrollTop = el.scrollHeight;\n };\n\n // Streaming grows the last node's text without adding nodes, so watch characters too.\n React.useEffect(() => {\n const el = scroller.current;\n if (!el) return;\n const follow = () => {\n if (pinned.current) toEnd();\n };\n const mo = new MutationObserver(follow);\n mo.observe(el, { childList: true, subtree: true, characterData: true });\n const ro = typeof ResizeObserver === \"function\" ? new ResizeObserver(follow) : null;\n ro?.observe(el);\n toEnd();\n return () => {\n mo.disconnect();\n ro?.disconnect();\n };\n }, [isEmpty]);\n\n return (\n <div data-slot=\"chat-panel\" className={cn(\"flex min-h-0 flex-1 flex-col\", className)} {...props}>\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n {isEmpty ? (\n <div className=\"flex flex-1 flex-col justify-center px-4\">{empty}</div>\n ) : (\n <div\n ref={scroller}\n role=\"log\"\n aria-live=\"polite\"\n aria-busy={streaming}\n aria-label={labels.log ?? \"Conversation\"}\n tabIndex={0}\n data-slot=\"chat-log\"\n data-scrolled={scrolled ? \"\" : undefined}\n className=\"flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto p-4 outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\"\n onScroll={(e) => {\n const el = e.currentTarget;\n const atEnd = el.scrollHeight - el.scrollTop - el.clientHeight <= PIN_SLACK;\n pinned.current = atEnd;\n setAway(!atEnd);\n setScrolled(el.scrollTop > 0);\n }}\n >\n {children}\n </div>\n )}\n {!isEmpty && (\n // Stays mounted so it can fade; hidden from the tree and the tab order until it matters.\n <IconButton\n variant=\"outline\"\n aria-label={labels.jump ?? \"Jump to latest\"}\n data-chat-jump={away ? \"visible\" : \"hidden\"}\n aria-hidden={!away}\n tabIndex={away ? 0 : -1}\n disabled={!away}\n className=\"absolute bottom-3 left-1/2 -translate-x-1/2\"\n onClick={() => {\n pinned.current = true;\n setAway(false);\n toEnd();\n // The button unmounts with `away`; keep focus in the conversation.\n scroller.current?.focus();\n }}\n >\n <UiIcon name=\"chevron-down\" />\n </IconButton>\n )}\n </div>\n {composer != null && <div className=\"shrink-0 px-4 pb-4\">{composer}</div>}\n </div>\n );\n}\n\nexport interface ChatDockLabels extends ChatPanelLabels {\n /** The launcher bar's name when collapsed. Default `\"Open assistant\"`. */\n open?: string;\n /** Default `\"Collapse\"`. */\n collapse?: string;\n}\n\nexport interface ChatDockProps extends Omit<React.ComponentProps<\"section\">, \"title\" | \"children\"> {\n title: React.ReactNode;\n expanded?: boolean;\n defaultExpanded?: boolean;\n onExpandedChange?: (expanded: boolean) => void;\n /** Header buttons before collapse: history. */\n actions?: React.ReactNode;\n /** The launcher bar's text when collapsed. */\n prompt?: string;\n children?: React.ReactNode;\n empty?: React.ReactNode;\n composer?: React.ReactNode;\n streaming?: boolean;\n labels?: ChatDockLabels;\n}\n\n/** The assistant docked in a column: a launcher bar when collapsed, header + conversation + composer when open. */\nexport function ChatDock({\n title,\n expanded,\n defaultExpanded = true,\n onExpandedChange,\n actions,\n prompt = \"Ask the assistant…\",\n children,\n empty,\n composer,\n streaming = false,\n labels = {},\n className,\n ...props\n}: ChatDockProps) {\n const [inner, setInner] = React.useState(defaultExpanded);\n const open = expanded ?? inner;\n const root = React.useRef<HTMLElement>(null);\n const moved = React.useRef(false);\n const set = (next: boolean) => {\n moved.current = true;\n setInner(next);\n onExpandedChange?.(next);\n };\n // The button that was pressed unmounts with its state; hand focus to the same job in the other one.\n React.useEffect(() => {\n if (!moved.current) return;\n moved.current = false;\n const target = open\n ? (root.current?.querySelector<HTMLElement>(\"textarea\") ?? root.current?.querySelector<HTMLElement>(\"[role=log]\"))\n : root.current?.querySelector<HTMLElement>(\"button\");\n target?.focus();\n }, [open]);\n if (!open) {\n return (\n <section ref={root} data-slot=\"chat-dock\" data-state=\"collapsed\" className={cn(\"bg-card\", className)} {...props}>\n <button\n type=\"button\"\n aria-label={labels.open ?? \"Open assistant\"}\n onClick={() => set(true)}\n className=\"flex h-11 w-full items-center gap-2 border-t border-border px-4 text-left text-body text-ink-soft outline-none hover:bg-hover focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring\"\n >\n <UiIcon name=\"sparkles\" />\n {prompt}\n </button>\n </section>\n );\n }\n return (\n <section ref={root} data-slot=\"chat-dock\" data-state=\"expanded\" className={cn(\"flex h-full min-h-0 flex-col bg-card\", className)} {...props}>\n <AsideHeader title={title} actions={actions} onClose={() => set(false)} labels={{ close: labels.collapse ?? \"Collapse\" }} />\n <ChatPanel empty={empty} composer={composer} streaming={streaming} labels={labels}>\n {children}\n </ChatPanel>\n </section>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-stream-text.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n// Words in one chunk start this far apart, up to STAGGER_CAP of them, so a burst\n// of text reads as a ripple instead of landing at once.\nconst STAGGER_STEP_MS = 28;\nconst STAGGER_CAP = 6;\n// Safety net for when `animationend` never fires (hidden tab, display: none).\nconst SETTLE_FALLBACK_MS = 900;\n\ninterface Unit {\n word: string;\n /** The whitespace that follows the word, kept outside the animated span. */\n gap: string;\n}\n\nfunction split(text: string): { lead: string; units: Unit[] } {\n const lead = /^\\s*/.exec(text)?.[0] ?? \"\";\n const units: Unit[] = [];\n for (const m of text.slice(lead.length).matchAll(/(\\S+)(\\s*)/g)) units.push({ word: m[1] ?? \"\", gap: m[2] ?? \"\" });\n return { lead, units };\n}\n\ninterface Progress {\n text: string;\n /** Units before this index are plain text; the rest are animating. */\n settled: number;\n /** Start delay per unit, fixed when the unit appears so re-renders never restart it. */\n delays: number[];\n}\n\nfunction delaysFor(from: number, to: number): number[] {\n return Array.from({ length: to - from }, (_, j) => Math.min(j, STAGGER_CAP) * STAGGER_STEP_MS);\n}\n\nexport interface ChatStreamTextProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** The text so far. Pass the whole growing string; only the appended tail animates. */\n text: string;\n /**\n * The text is still growing. A string that is already complete when this\n * mounts as `false` shows at once, with no entrance.\n */\n streaming?: boolean;\n}\n\n/**\n * Text that arrives word by word. New words fade in from a soft blur and settle\n * (motion.css); words already on screen are never touched again. Once a word has\n * landed it folds into one plain text node, so a long reply costs a handful of\n * elements, not thousands. The text content stays exactly `text`.\n */\nexport function ChatStreamText({ text, streaming = false, ...props }: ChatStreamTextProps) {\n const parsed = split(text);\n const [progress, setProgress] = React.useState<Progress>(() => ({\n text,\n settled: streaming ? 0 : parsed.units.length,\n delays: delaysFor(0, parsed.units.length),\n }));\n\n let cur = progress;\n if (text !== progress.text) {\n // Anything but an append (edit, regenerate) starts over rather than patching words in place.\n const appended = text.startsWith(progress.text);\n const known = appended ? split(progress.text).units.length : 0;\n cur = {\n text,\n settled: appended ? progress.settled : 0,\n delays: [...(appended ? progress.delays.slice(0, known) : []), ...delaysFor(known, parsed.units.length)],\n };\n setProgress(cur);\n }\n\n const settle = React.useCallback((upTo: number) => {\n setProgress((p) => (upTo > p.settled ? { ...p, settled: upTo } : p));\n }, []);\n\n const total = parsed.units.length;\n React.useEffect(() => {\n if (streaming) return;\n const id = setTimeout(() => settle(total), SETTLE_FALLBACK_MS);\n return () => clearTimeout(id);\n }, [streaming, total, settle]);\n\n const settledText = parsed.lead + parsed.units.slice(0, cur.settled).map((u) => u.word + u.gap).join(\"\");\n return (\n <span\n data-slot=\"chat-stream-text\"\n onAnimationEnd={(e) => {\n const i = Number((e.target as HTMLElement).dataset[\"word\"]);\n if (Number.isInteger(i)) settle(i + 1);\n }}\n {...props}\n >\n {settledText}\n {parsed.units.slice(cur.settled).map((u, k) => {\n const i = cur.settled + k;\n return (\n <React.Fragment key={i}>\n <span data-slot=\"chat-word\" data-word={i} style={{ animationDelay: `${cur.delays[i] ?? 0}ms` }}>\n {u.word}\n </span>\n {u.gap}\n </React.Fragment>\n );\n })}\n </span>\n );\n}\n"],"mappings":";AAkGU,SACE,KADF;AAhGV,YAAY,WAAW;AAIvB,MAAM,kBAAkB;AACxB,MAAM,cAAc;AAEpB,MAAM,qBAAqB;AAQ3B,SAAS,MAAM,MAA+C;AAC5D,QAAM,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK;AACvC,QAAM,QAAgB,CAAC;AACvB,aAAW,KAAK,KAAK,MAAM,KAAK,MAAM,EAAE,SAAS,aAAa,EAAG,OAAM,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,IAAI,KAAK,EAAE,CAAC,KAAK,GAAG,CAAC;AACjH,SAAO,EAAE,MAAM,MAAM;AACvB;AAUA,SAAS,UAAU,MAAc,IAAsB;AACrD,SAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,KAAK,GAAG,CAAC,GAAG,MAAM,KAAK,IAAI,GAAG,WAAW,IAAI,eAAe;AAC/F;AAkBO,SAAS,eAAe,EAAE,MAAM,YAAY,OAAO,GAAG,MAAM,GAAwB;AACzF,QAAM,SAAS,MAAM,IAAI;AACzB,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAmB,OAAO;AAAA,IAC9D;AAAA,IACA,SAAS,YAAY,IAAI,OAAO,MAAM;AAAA,IACtC,QAAQ,UAAU,GAAG,OAAO,MAAM,MAAM;AAAA,EAC1C,EAAE;AAEF,MAAI,MAAM;AACV,MAAI,SAAS,SAAS,MAAM;AAE1B,UAAM,WAAW,KAAK,WAAW,SAAS,IAAI;AAC9C,UAAM,QAAQ,WAAW,MAAM,SAAS,IAAI,EAAE,MAAM,SAAS;AAC7D,UAAM;AAAA,MACJ;AAAA,MACA,SAAS,WAAW,SAAS,UAAU;AAAA,MACvC,QAAQ,CAAC,GAAI,WAAW,SAAS,OAAO,MAAM,GAAG,KAAK,IAAI,CAAC,GAAI,GAAG,UAAU,OAAO,OAAO,MAAM,MAAM,CAAC;AAAA,IACzG;AACA,gBAAY,GAAG;AAAA,EACjB;AAEA,QAAM,SAAS,MAAM,YAAY,CAAC,SAAiB;AACjD,gBAAY,CAAC,MAAO,OAAO,EAAE,UAAU,EAAE,GAAG,GAAG,SAAS,KAAK,IAAI,CAAE;AAAA,EACrE,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ,OAAO,MAAM;AAC3B,QAAM,UAAU,MAAM;AACpB,QAAI,UAAW;AACf,UAAM,KAAK,WAAW,MAAM,OAAO,KAAK,GAAG,kBAAkB;AAC7D,WAAO,MAAM,aAAa,EAAE;AAAA,EAC9B,GAAG,CAAC,WAAW,OAAO,MAAM,CAAC;AAE7B,QAAM,cAAc,OAAO,OAAO,OAAO,MAAM,MAAM,GAAG,IAAI,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE;AACvG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAgB,CAAC,MAAM;AACrB,cAAM,IAAI,OAAQ,EAAE,OAAuB,QAAQ,MAAM,CAAC;AAC1D,YAAI,OAAO,UAAU,CAAC,EAAG,QAAO,IAAI,CAAC;AAAA,MACvC;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,OAAO,MAAM,MAAM,IAAI,OAAO,EAAE,IAAI,CAAC,GAAG,MAAM;AAC7C,gBAAM,IAAI,IAAI,UAAU;AACxB,iBACE,qBAAC,MAAM,UAAN,EACC;AAAA,gCAAC,UAAK,aAAU,aAAY,aAAW,GAAG,OAAO,EAAE,gBAAgB,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,GAC1F,YAAE,MACL;AAAA,YACC,EAAE;AAAA,eAJgB,CAKrB;AAAA,QAEJ,CAAC;AAAA;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from "
|
|
4
|
-
import { cn } from "
|
|
3
|
+
import { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from "../../generated/chat-typing.js";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
function ChatTyping({ label = "Assistant is thinking", className, ...props }) {
|
|
6
6
|
return /* @__PURE__ */ jsxs(
|
|
7
7
|
"span",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../../generated/chat-typing\";\nimport { cn } from \"../../lib/cn\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label = \"Assistant is thinking\", className, ...props }: ChatTypingProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n data-slot=\"chat-typing\"\n className={cn(CHAT_TYPING_BASE, className)}\n {...props}\n >\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-still className={CHAT_TYPING_STILL}>\n …\n </span>\n </span>\n );\n}\n"],"mappings":"AAgBI,SAOE,KAPF;AAhBJ,YAAY,WAAW;AACvB,SAAS,kBAAkB,iBAAiB,yBAAyB;AACrE,SAAS,UAAU;AAYZ,SAAS,WAAW,EAAE,QAAQ,yBAAyB,WAAW,GAAG,MAAM,GAAoB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,MACxC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,cAAU,MAAC,WAAW,mBAAmB,oBAE3D;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { Container } from "
|
|
3
|
+
import { Container } from "../atomic/container.js";
|
|
4
4
|
const HEADING = "font-mono text-xs text-ink uppercase tracking-widest mb-6";
|
|
5
5
|
function Footer({
|
|
6
6
|
lockup,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\n\n/**\n * A 12-col footer grid — brand 3 | sitemap groups 2 each | offices 3 |\n * newsletter 2 — with the legal links and copyright line. On mobile the sitemap\n * columns sit side by side. The kit brings no content of its own: every string,\n * the mark and the lock-up are the caller's, and each column renders only when\n * it has something in it. `children` render right after the `<footer>` tag —\n * the slot for app-side extras like a build-version easter egg.\n *\n * The lock-up is either composed from `mark` + `brand` + `tagline`, or handed\n * over whole as `lockup` when a brand's arrangement is not that one.\n *\n * `linkComponent` lets Next hosts pass `next/link`; everyone else gets `<a>`.\n */\nexport interface FooterLink {\n label: string;\n href: string;\n}\n\nexport interface FooterLinkGroup {\n heading: string;\n links: readonly FooterLink[];\n}\n\nexport interface FooterOffice {\n name: string;\n address: string;\n}\n\nexport interface FooterProps {\n /** The whole lock-up; replaces the one `mark` + `brand` + `tagline` compose. */\n lockup?: React.ReactNode;\n /** The wordmark beside the mark, set in the display family. */\n brand?: string;\n /** The mark beside the brand name. Sized by the caller. */\n mark?: React.ReactNode;\n tagline?: string;\n description?: React.ReactNode;\n copyright?: React.ReactNode;\n nav?: readonly FooterLinkGroup[];\n offices?: readonly FooterOffice[];\n officesHeading?: string;\n legalLinks?: readonly FooterLink[];\n /** The newsletter form; the newsletter column renders only when present. */\n newsletter?: React.ReactNode;\n newsletterHeading?: string;\n newsletterBlurb?: string;\n version?: string;\n commitHref?: string;\n linkComponent?: React.ElementType;\n children?: React.ReactNode;\n}\n\nconst HEADING = \"font-mono text-xs text-ink uppercase tracking-widest mb-6\";\n\nexport function Footer({\n lockup,\n brand,\n mark,\n tagline,\n description,\n copyright,\n nav = [],\n offices = [],\n officesHeading = \"Offices\",\n legalLinks = [],\n newsletter,\n newsletterHeading = \"Newsletter\",\n newsletterBlurb = \"\",\n version,\n commitHref,\n linkComponent,\n children,\n}: FooterProps) {\n const L = linkComponent ?? \"a\";\n const composed = brand !== undefined || mark !== undefined;\n const hasBrandColumn = lockup !== undefined || composed || description !== undefined || legalLinks.length > 0;\n\n return (\n <footer data-slot=\"footer\" className=\"bg-background border-t border-ink/10 py-16\">\n {children}\n <Container>\n <div className=\"grid grid-cols-2 gap-x-8 gap-y-8 lg:grid-cols-12 mb-12\">\n {hasBrandColumn && (\n <div className=\"col-span-2 lg:col-span-3\">\n {lockup !== undefined ? (\n <div data-slot=\"footer-lockup\" className=\"mb-6\">{lockup}</div>\n ) : (\n composed && <FooterLockup brand={brand} mark={mark} tagline={tagline} />\n )}\n {description !== undefined && (\n <p className=\"text-ink/40 text-xs font-light max-w-sm leading-relaxed mb-6\">\n {description}\n </p>\n )}\n {legalLinks.length > 0 && (\n <div className=\"flex gap-4 text-xs font-mono text-primary-ink\">\n {legalLinks.map((link, i) => (\n <React.Fragment key={link.label}>\n {i > 0 && <span className=\"text-ink/20\">|</span>}\n <L href={link.href} className=\"hover:underline\">\n {link.label}\n </L>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )}\n\n {nav.map((group) => (\n <nav key={group.heading} aria-label={`Footer ${group.heading} links`} className=\"lg:col-span-2\">\n <h4 className={HEADING}>{group.heading}</h4>\n <ul className=\"space-y-3\">\n {group.links.map((link) => (\n <li key={link.href}>\n <L\n href={link.href}\n className=\"text-xs font-light text-ink/70 hover:text-primary-ink transition-colors\"\n >\n {link.label}\n </L>\n </li>\n ))}\n </ul>\n </nav>\n ))}\n\n {offices.length > 0 && (\n <div className=\"col-span-2 lg:col-span-3\">\n <h4 className={HEADING}>{officesHeading}</h4>\n <ul className=\"space-y-4 text-xs text-ink/70 font-light leading-relaxed\">\n {offices.map((office) => (\n <li key={office.name}>\n <strong className=\"text-ink block font-mono text-[10px] uppercase tracking-wider mb-1\">\n {office.name}\n </strong>\n {office.address}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {newsletter && (\n <div className=\"col-span-2 lg:col-span-2\">\n <h4 className={HEADING}>{newsletterHeading}</h4>\n {/* What the source's <Tier tier=\"alt\"><Text> emits: alt body size\n + the info variant, then the mb-4 the caller adds. */}\n <p className=\"text-sm sm:text-xs font-light leading-relaxed text-ink/70 mb-4\">\n {newsletterBlurb}\n </p>\n {newsletter}\n </div>\n )}\n </div>\n\n {(copyright !== undefined || version !== undefined) && (\n <div className=\"border-t border-ink/10 pt-8 text-[10px] font-mono text-ink/40\">\n <p>\n {copyright}\n {version && (\n <>\n {copyright !== undefined && \" \"}\n <a href={commitHref} className=\"text-ink/30\">\n {version}\n </a>\n </>\n )}\n </p>\n </div>\n )}\n </Container>\n </footer>\n );\n}\n\nfunction FooterLockup({\n brand,\n mark,\n tagline,\n}: {\n brand: string | undefined;\n mark: React.ReactNode;\n tagline: string | undefined;\n}) {\n return (\n <div data-slot=\"footer-lockup\" className=\"flex items-center gap-3 mb-6\">\n {mark}\n {(brand !== undefined || tagline !== undefined) && (\n <div className=\"flex flex-col\">\n {brand !== undefined && (\n <span className=\"font-serif font-bold text-base tracking-wider text-ink\">{brand}</span>\n )}\n {tagline !== undefined && (\n <span className=\"text-[8px] font-mono tracking-[0.3em] text-primary-ink uppercase\">\n {tagline}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import {
|
|
6
6
|
HEADER_BALANCE_BASE,
|
|
7
7
|
HEADER_BALANCE_CHEVRON,
|
|
@@ -28,14 +28,14 @@ import {
|
|
|
28
28
|
NOTIFICATION_BELL_TRIGGER,
|
|
29
29
|
NOTIFICATION_DOT,
|
|
30
30
|
headerBalanceToneClasses
|
|
31
|
-
} from "
|
|
32
|
-
import { AnimatedNumber } from "
|
|
33
|
-
import { IconButton } from "
|
|
34
|
-
import { Kbd } from "
|
|
35
|
-
import { Popover, PopoverContent, PopoverTrigger } from "
|
|
31
|
+
} from "../../generated/header-controls.js";
|
|
32
|
+
import { AnimatedNumber } from "../derived/animated-number.js";
|
|
33
|
+
import { IconButton } from "../derived/icon-button.js";
|
|
34
|
+
import { Kbd } from "../atomic/kbd.js";
|
|
35
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
|
|
36
36
|
import { Settled } from "./settled.js";
|
|
37
|
-
import { Skeleton } from "
|
|
38
|
-
import { UiIcon } from "
|
|
37
|
+
import { Skeleton } from "../atomic/skeleton.js";
|
|
38
|
+
import { UiIcon } from "../../icons/ui-icons.js";
|
|
39
39
|
const USD = { style: "currency", currency: "USD" };
|
|
40
40
|
function HeaderBalance({
|
|
41
41
|
label = "Balance:",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/header-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n HEADER_BALANCE_BASE,\n HEADER_BALANCE_CHEVRON,\n HEADER_BALANCE_FIGURE,\n HEADER_BALANCE_INTERACTIVE,\n HEADER_BALANCE_LABEL,\n HEADER_BALANCE_SETTLED,\n HEADER_BALANCE_SKELETON,\n HEADER_SEARCH_BASE,\n HEADER_SEARCH_PLACEHOLDER,\n NOTIFICATION_BELL_CONTENT,\n NOTIFICATION_BELL_EMPTY,\n NOTIFICATION_BELL_FOOTER,\n NOTIFICATION_BELL_ITEM,\n NOTIFICATION_BELL_ITEM_BODY,\n NOTIFICATION_BELL_ITEM_DESCRIPTION,\n NOTIFICATION_BELL_ITEM_DOT,\n NOTIFICATION_BELL_ITEM_DOT_READ,\n NOTIFICATION_BELL_ITEM_DOT_UNREAD,\n NOTIFICATION_BELL_ITEM_TIME,\n NOTIFICATION_BELL_ITEM_TITLE,\n NOTIFICATION_BELL_ITEM_TITLE_UNREAD,\n NOTIFICATION_BELL_LIST,\n NOTIFICATION_BELL_TRIGGER,\n NOTIFICATION_DOT,\n headerBalanceToneClasses,\n type HeaderBalanceTone,\n} from \"../../generated/header-controls\";\nimport { AnimatedNumber } from \"../derived/animated-number\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { Settled } from \"./settled\";\nimport { Skeleton } from \"../atomic/skeleton\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface HeaderBalanceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n /** Only meaningful with `onClick`. */\n disabled?: boolean;\n label?: React.ReactNode;\n /** The figure, already formatted: \"$0.00\". Ignored when `amount` is given. */\n value?: React.ReactNode;\n /** The figure as a number: it rolls to each new amount, formatted by `format` and `locales`. */\n amount?: number;\n /** Formatting for `amount`. Default: USD currency. */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** True until the balance is known; the figure then reveals in place of the skeleton. */\n loading?: boolean;\n /** A glyph before the label. */\n icon?: React.ReactNode;\n /** `warn` colours the figure as a low-funds signal. */\n tone?: HeaderBalanceTone;\n}\n\nconst USD: Intl.NumberFormatOptions = { style: \"currency\", currency: \"USD\" };\n\n/** \"Balance: $0.00\" in the header; a button only when given `onClick`. */\nexport function HeaderBalance({\n label = \"Balance:\",\n value,\n amount,\n format = USD,\n locales,\n loading = false,\n icon,\n tone = \"default\",\n onClick,\n disabled,\n className,\n ...props\n}: HeaderBalanceProps) {\n const face = cn(HEADER_BALANCE_BASE, onClick && HEADER_BALANCE_INTERACTIVE, className);\n const body = (\n <>\n {icon}\n <span className={HEADER_BALANCE_LABEL}>{label}</span>\n <Settled\n loading={loading}\n skeleton={<Skeleton className={HEADER_BALANCE_SKELETON} />}\n className={HEADER_BALANCE_SETTLED}\n >\n <span className={cn(HEADER_BALANCE_FIGURE, headerBalanceToneClasses[tone])}>\n {amount === undefined ? value : <AnimatedNumber value={amount} format={format} locales={locales} />}\n </span>\n </Settled>\n {onClick && <UiIcon name=\"chevron-down\" className={HEADER_BALANCE_CHEVRON} />}\n </>\n );\n if (!onClick) {\n return (\n <div data-slot=\"header-balance\" className={face} {...props}>\n {body}\n </div>\n );\n }\n return (\n <button type=\"button\" data-slot=\"header-balance\" className={face} onClick={onClick} disabled={disabled} {...props}>\n {body}\n </button>\n );\n}\n\nexport interface HeaderNotification {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Already formatted: \"2m ago\". */\n time?: React.ReactNode;\n unread?: boolean;\n}\n\nexport interface NotificationBellLabels {\n /** The bell's name. Default `\"Notifications\"`. */\n trigger?: string;\n /** Spoken after the name when something is unread; gets the count. Default `\"{n} unread\"`. */\n unread?: (count: number) => string;\n /** Default `\"No notifications\"`. */\n empty?: string;\n}\n\nexport interface NotificationBellProps {\n notifications: readonly HeaderNotification[];\n onSelect?: (id: string) => void;\n labels?: NotificationBellLabels;\n /** Under the list: \"View all\". */\n footer?: React.ReactNode;\n className?: string;\n}\n\n/** The bell with an unread dot; the list opens in a popover. */\nexport function NotificationBell({ notifications, onSelect, labels = {}, footer, className }: NotificationBellProps) {\n const unread = notifications.filter((n) => n.unread).length;\n const name = labels.trigger ?? \"Notifications\";\n const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton aria-label={spoken} variant=\"outline\" className={cn(NOTIFICATION_BELL_TRIGGER, className)}>\n <UiIcon name=\"bell\" />\n {unread > 0 && (\n <span data-slot=\"notification-dot\" aria-hidden className={NOTIFICATION_DOT} />\n )}\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className={NOTIFICATION_BELL_CONTENT}>\n {notifications.length === 0 ? (\n <p className={NOTIFICATION_BELL_EMPTY}>{labels.empty ?? \"No notifications\"}</p>\n ) : (\n <ul className={NOTIFICATION_BELL_LIST}>\n {notifications.map((n) => (\n <li key={n.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(n.id)}\n className={NOTIFICATION_BELL_ITEM}\n >\n <span\n aria-hidden\n className={cn(\n NOTIFICATION_BELL_ITEM_DOT,\n n.unread ? NOTIFICATION_BELL_ITEM_DOT_UNREAD : NOTIFICATION_BELL_ITEM_DOT_READ,\n )}\n />\n <span className={NOTIFICATION_BELL_ITEM_BODY}>\n <span className={cn(NOTIFICATION_BELL_ITEM_TITLE, n.unread && NOTIFICATION_BELL_ITEM_TITLE_UNREAD)}>{n.title}</span>\n {n.description != null && <span className={NOTIFICATION_BELL_ITEM_DESCRIPTION}>{n.description}</span>}\n </span>\n {n.time != null && <span className={NOTIFICATION_BELL_ITEM_TIME}>{n.time}</span>}\n </button>\n </li>\n ))}\n </ul>\n )}\n {footer != null && <div className={NOTIFICATION_BELL_FOOTER}>{footer}</div>}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface HeaderSearchProps extends React.ComponentProps<\"button\"> {\n placeholder?: string;\n /** The shortcut hint at the end. Default: `⌘K` on Apple platforms, `Ctrl K` elsewhere. */\n shortcut?: string;\n}\n\nconst APPLE = /Mac|iPhone|iPad|iPod/;\n\n// Read after mount, so the server render and the first client render agree.\nfunction usePlatformShortcut(): string {\n const [shortcut, setShortcut] = React.useState(\"Ctrl K\");\n React.useEffect(() => {\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } };\n if (APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? \"\")) setShortcut(\"⌘K\");\n }, []);\n return shortcut;\n}\n\n/** A button dressed as the search field; it opens the palette rather than taking input. */\nexport function HeaderSearch({\n placeholder = \"Search or jump to…\",\n shortcut,\n className,\n ...props\n}: HeaderSearchProps) {\n const platformShortcut = usePlatformShortcut();\n return (\n <button\n type=\"button\"\n data-slot=\"header-search\"\n className={cn(HEADER_SEARCH_BASE, className)}\n {...props}\n >\n <UiIcon name=\"search\" />\n <span className={HEADER_SEARCH_PLACEHOLDER}>{placeholder}</span>\n <Kbd>{shortcut ?? platformShortcut}</Kbd>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
4
|
const LIST_ROWS = "flex w-full min-w-0 flex-col divide-y divide-border";
|
|
5
5
|
const listRowsVariants = {
|
|
6
6
|
plain: "",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/list-rows.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\n\n// The phone form of a table: a list of label/value rows split by hairlines.\n// A table scrolled sideways on a phone hides the trailing columns — usually the\n// date and the action, the reason the list was opened. TS-only (README\n// \"Limitations\").\n\nexport type ListRowsVariant = \"plain\" | \"card\";\n\nconst LIST_ROWS = \"flex w-full min-w-0 flex-col divide-y divide-border\";\n\n// `plain` sits in a container that already pads (a padded card, a section);\n// `card` is edge to edge in a `TableCard`, so the rows pad themselves and the\n// hairlines run the card's full width. The inset travels as a custom property,\n// as the table's cell padding does, so a row's own `px-*` still wins.\nconst listRowsVariants: Record<ListRowsVariant, string> = {\n plain: \"\",\n card: \"[--ev-list-row-x:1rem]\",\n};\n\nexport interface ListRowsProps extends React.ComponentProps<\"ul\"> {\n variant?: ListRowsVariant;\n}\n\nexport function ListRows({ variant = \"plain\", className, ...props }: ListRowsProps) {\n return (\n <ul\n data-slot=\"list-rows\"\n data-variant={variant}\n className={cn(LIST_ROWS, listRowsVariants[variant], className)}\n {...props}\n />\n );\n}\n\nconst LIST_ROW = \"flex min-w-0 items-center justify-between gap-3 px-[var(--ev-list-row-x,0px)] py-3\";\n\n// `<li value>` is an ordinal for `<ol>`; in an unordered list of rows the name\n// is far more useful for the trailing figure.\nexport interface ListRowProps extends Omit<React.ComponentProps<\"li\">, \"value\"> {\n /** The row's title. Omit it (and `value`) to lay the row out yourself. */\n label?: React.ReactNode;\n /** A caption a step down under the label. */\n description?: React.ReactNode;\n /** Trailing value, right-aligned and truncated rather than pushing the row wide. */\n value?: React.ReactNode;\n}\n\n/**\n * One row: label (and description) leading, value trailing, then `children`\n * (a badge, an action). With neither `label` nor `value` the children are the\n * whole row.\n */\nexport function ListRow({ label, description, value, className, children, ...props }: ListRowProps) {\n return (\n <li\n data-slot=\"list-row\"\n className={cn(LIST_ROW, className)}\n {...props}\n >\n {(label != null || description != null) && (\n <ListRowLabel description={description}>{label}</ListRowLabel>\n )}\n {value != null && <ListRowValue>{value}</ListRowValue>}\n {children}\n </li>\n );\n}\n\nexport interface ListRowLabelProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n description?: React.ReactNode;\n}\n\n/** The leading block on its own, for a row you compose by hand. */\nexport function ListRowLabel({ description, className, children, ...props }: ListRowLabelProps) {\n return (\n <div\n data-slot=\"list-row-label\"\n className={cn(\"flex min-w-0 flex-1 flex-col gap-0.5\", className)}\n {...props}\n >\n <span className=\"text-sm font-medium text-ink\">{children}</span>\n {description != null && (\n <span\n data-slot=\"list-row-description\"\n className=\"text-xs leading-snug text-ink-soft\"\n >\n {description}\n </span>\n )}\n </div>\n );\n}\n\n/**\n * The trailing value. It shares the label's type step, so the muted tone and\n * the lighter weight are what keep the label reading first; digits are tabular\n * so a figure in one row lines up with the next.\n */\nexport function ListRowValue({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"list-row-value\"\n className={cn(\"min-w-0 truncate text-right text-sm tabular-nums text-ink-soft\", className)}\n {...props}\n />\n );\n}\n"],"mappings":"AA2BI,cA6BA,YA7BA;AA3BJ,YAAY,WAAW;AACvB,SAAS,UAAU;AASnB,MAAM,YAAY;AAMlB,MAAM,mBAAoD;AAAA,EACxD,OAAO;AAAA,EACP,MAAM;AACR;AAMO,SAAS,SAAS,EAAE,UAAU,SAAS,WAAW,GAAG,MAAM,GAAkB;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW,GAAG,WAAW,iBAAiB,OAAO,GAAG,SAAS;AAAA,MAC5D,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,MAAM,WAAW;AAkBV,SAAS,QAAQ,EAAE,OAAO,aAAa,OAAO,WAAW,UAAU,GAAG,MAAM,GAAiB;AAClG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,UAAU,SAAS;AAAA,MAChC,GAAG;AAAA,MAEF;AAAA,kBAAS,QAAQ,eAAe,SAChC,oBAAC,gBAAa,aAA2B,iBAAM;AAAA,QAEhD,SAAS,QAAQ,oBAAC,gBAAc,iBAAM;AAAA,QACtC;AAAA;AAAA;AAAA,EACH;AAEJ;AAOO,SAAS,aAAa,EAAE,aAAa,WAAW,UAAU,GAAG,MAAM,GAAsB;AAC9F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,wCAAwC,SAAS;AAAA,MAC9D,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,WAAU,gCAAgC,UAAS;AAAA,QACxD,eAAe,QACd;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAU;AAAA,YAET;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAOO,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAiC;AAClF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,kEAAkE,SAAS;AAAA,MACxF,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/logo.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\n\n/**\n * The consumer's mark, painted as a CSS mask (`backgroundColor: currentColor` +\n * `maskImage`) so a monochrome lockup follows the surrounding text colour\n * instead of the artwork's baked-in fill. `withBackground` seats it on the brand\n * field, keeping a chip consistent with a favicon.\n *\n * The kit ships no artwork. The mark is a design token like every colour: the\n * consumer declares `--brand-mark` — a `url()` at its own asset — and\n * `--brand-aspect` in the same sheet it declares the palette in (see\n * `styles/tokens.css`). Undeclared, `maskImage` is invalid at computed-value time\n * and the span paints as a solid block: loud, and nobody else's logo.\n *\n * Decorative by design (`aria-hidden`): every real call site has the brand name\n * in adjacent text or on the wrapping link, and a mark announced twice is worse\n * than one announced once.\n */\nexport interface LogoProps {\n className?: string;\n withBackground?: boolean;\n}\n\nconst maskStyle: React.CSSProperties = {\n backgroundColor: \"currentColor\",\n maskImage: \"var(--brand-mark)\",\n WebkitMaskImage: \"var(--brand-mark)\",\n maskRepeat: \"no-repeat\",\n WebkitMaskRepeat: \"no-repeat\",\n maskPosition: \"center\",\n WebkitMaskPosition: \"center\",\n maskSize: \"contain\",\n WebkitMaskSize: \"contain\",\n // the ratio's fallback is what makes a missing mark *visible*: a masked span\n // has no intrinsic size, so `h-N w-auto` without it is zero-width\n aspectRatio: \"var(--brand-aspect, 1)\",\n};\n\nexport function Logo({ className, withBackground = false }: LogoProps) {\n const mark = (\n <span\n data-slot=\"logo\"\n aria-hidden\n style={maskStyle}\n className={cn(\"inline-block\", withBackground ? \"w-3/5 h-3/5\" : className)}\n />\n );\n\n if (!withBackground) return mark;\n\n return (\n <span\n data-slot=\"logo-background\"\n className={cn(\"inline-flex items-center justify-center bg-brand\", className)}\n >\n {mark}\n </span>\n );\n}\n"],"mappings":"AAyCI;AAzCJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAuBnB,MAAM,YAAiC;AAAA,EACrC,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,YAAY;AAAA,EACZ,kBAAkB;AAAA,EAClB,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,UAAU;AAAA,EACV,gBAAgB;AAAA;AAAA;AAAA,EAGhB,aAAa;AACf;AAEO,SAAS,KAAK,EAAE,WAAW,iBAAiB,MAAM,GAAc;AACrE,QAAM,OACJ;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,eAAW;AAAA,MACX,OAAO;AAAA,MACP,WAAW,GAAG,gBAAgB,iBAAiB,gBAAgB,SAAS;AAAA;AAAA,EAC1E;AAGF,MAAI,CAAC,eAAgB,QAAO;AAE5B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,oDAAoD,SAAS;AAAA,MAE1E;AAAA;AAAA,EACH;AAEJ;","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
5
|
import { ChevronLeftIcon } from "./shell-icons.js";
|
|
6
6
|
const BACK = "-ml-1 flex size-6 shrink-0 items-center justify-center rounded-md text-ink outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
7
7
|
const HIDE_FROM = { md: "md:hidden", lg: "lg:hidden" };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/mobile-app-bar.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport type { ShellBreakpoint } from \"./app-shell\";\nimport { ChevronLeftIcon } from \"./shell-icons\";\n\nexport interface MobileAppBarLabels {\n /** Accessible name of the back control. Default `\"Back\"`. */\n back?: string;\n}\n\nexport type MobileAppBarBack = { href: string } | { onClick: () => void };\n\nexport interface MobileAppBarProps extends Omit<React.ComponentProps<\"header\">, \"title\"> {\n title: React.ReactNode;\n /** A pushed screen: a back link (`href`) or a back button (`onClick`); centres the title. */\n back?: MobileAppBarBack;\n /** Trailing action — Save, a menu. */\n right?: React.ReactNode;\n linkComponent?: React.ElementType;\n /** Hidden from this breakpoint up, where the page heading takes over. Default `lg`; `false` never hides. */\n hideFrom?: ShellBreakpoint | false;\n labels?: MobileAppBarLabels;\n}\n\n// A bare glyph has no border of its own, so it needs a rounded box for the ring to trace.\nconst BACK =\n \"-ml-1 flex size-6 shrink-0 items-center justify-center rounded-md text-ink outline-none focus-visible:ring-2 focus-visible:ring-ring\";\n\nconst HIDE_FROM = { md: \"md:hidden\", lg: \"lg:hidden\" } as const;\n\n/**\n * The bar that titles a screen on a phone. A root screen puts the title flush\n * left with an optional action; a pushed one (`back`) centres it between the\n * back control and a matching spacer. Sticks under `--ev-shell-offset` and\n * arrives with the screen (`data-enter=\"rise\"`), one step ahead of a\n * `PageFrame`'s sections.\n */\nexport function MobileAppBar({\n title,\n back,\n right,\n linkComponent,\n hideFrom = \"lg\",\n labels = {},\n className,\n ...props\n}: MobileAppBarProps) {\n const backLabel = labels.back ?? \"Back\";\n const Link = linkComponent ?? \"a\";\n return (\n <header\n data-slot=\"mobile-app-bar\"\n data-enter=\"rise\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] z-30 flex items-center gap-2 border-b border-border bg-secondary px-4 pb-3.5 pt-4\",\n hideFrom && HIDE_FROM[hideFrom],\n className,\n )}\n {...props}\n >\n {back &&\n (\"href\" in back ? (\n <Link href={back.href} aria-label={backLabel} className={BACK}>\n <ChevronLeftIcon className=\"size-6\" />\n </Link>\n ) : (\n <button type=\"button\" onClick={back.onClick} aria-label={backLabel} className={BACK}>\n <ChevronLeftIcon className=\"size-6\" />\n </button>\n ))}\n <h1\n className={cn(\n \"min-w-0 flex-1 truncate font-semibold text-ink\",\n back ? \"text-center text-base\" : \"text-lg\",\n )}\n >\n {title}\n </h1>\n {back && right === undefined ? <span className=\"size-6 shrink-0\" aria-hidden /> : right}\n </header>\n );\n}\n"],"mappings":";AAoDI,SAaQ,KAbR;AAlDJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAEnB,SAAS,uBAAuB;AAsBhC,MAAM,OACJ;AAEF,MAAM,YAAY,EAAE,IAAI,aAAa,IAAI,YAAY;AAS9C,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,SAAS,CAAC;AAAA,EACV;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,YAAY,OAAO,QAAQ;AACjC,QAAM,OAAO,iBAAiB;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,cAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA,YAAY,UAAU,QAAQ;AAAA,QAC9B;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,iBACE,UAAU,OACT,oBAAC,QAAK,MAAM,KAAK,MAAM,cAAY,WAAW,WAAW,MACvD,8BAAC,mBAAgB,WAAU,UAAS,GACtC,IAEA,oBAAC,YAAO,MAAK,UAAS,SAAS,KAAK,SAAS,cAAY,WAAW,WAAW,MAC7E,8BAAC,mBAAgB,WAAU,UAAS,GACtC;AAAA,QAEJ;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,OAAO,0BAA0B;AAAA,YACnC;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,QAAQ,UAAU,SAAY,oBAAC,UAAK,WAAU,mBAAkB,eAAW,MAAC,IAAK;AAAA;AAAA;AAAA,EACpF;AAEJ;","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
4
|
const PILL = "rounded-full px-2 py-0.5 text-xs font-semibold tabular-nums";
|
|
5
5
|
const CORNER = "absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-none tabular-nums text-on-primary";
|
|
6
6
|
const defaultLabel = (n) => `${n} new`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/shell/nav-badge.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\n\nexport type NavBadgeVariant = \"pill\" | \"corner\";\n\nexport interface NavBadgeProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n count: number;\n /** Above this the badge shows `{max}+`. */\n max?: number;\n /**\n * The spoken text, given the REAL count — the one figure the cap throws away.\n * Default: `\"{n} new\"`.\n */\n label?: (count: number) => string;\n /**\n * `pill` sits in a row after the label (the rail); `corner` is the solid dot on\n * an icon's top-right corner (the tab bar) — its parent must be `relative`.\n */\n variant?: NavBadgeVariant;\n /** A pill on the marked row: inverted so it reads on the marker's fill. */\n active?: boolean;\n}\n\nconst PILL = \"rounded-full px-2 py-0.5 text-xs font-semibold tabular-nums\";\nconst CORNER =\n \"absolute -right-2.5 -top-1.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-xs font-semibold leading-none tabular-nums text-on-primary\";\n\nconst defaultLabel = (n: number) => `${n} new`;\n\n/**\n * A count on a nav row or tab, capped so a neglected inbox cannot widen the\n * rail. Draws nothing for `0` or less.\n *\n * The visible figure is `aria-hidden` and the spoken one rides in an `sr-only`\n * span rather than an `aria-label`: a label on a plain span is not part of the\n * name the browser computes for the link around it, while text is — so the row\n * reads \"Notifications, 150 new\".\n */\nexport function NavBadge({\n count,\n max = 99,\n label = defaultLabel,\n variant = \"pill\",\n active = false,\n className,\n ...props\n}: NavBadgeProps) {\n if (!(count > 0)) return null;\n return (\n <span\n data-slot=\"nav-badge\"\n data-variant={variant}\n className={cn(\n variant === \"corner\"\n ? CORNER\n : cn(PILL, active ? \"bg-background text-ink\" : \"bg-primary-ink/15 text-primary-ink\"),\n className,\n )}\n {...props}\n >\n <span aria-hidden>{count > max ? `${max}+` : count}</span>\n <span className=\"sr-only\">{label(count)}</span>\n </span>\n );\n}\n\nexport type NavDotTone = \"primary\" | \"positive\" | \"warn\" | \"error\";\n\nconst DOT_TONE: Record<NavDotTone, string> = {\n primary: \"bg-primary-ink\",\n positive: \"bg-positive\",\n warn: \"bg-accent-warn\",\n error: \"bg-accent-error\",\n};\n\nexport interface NavDotProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n tone?: NavDotTone;\n /** Spoken text; without one the dot is decoration and hidden from AT. */\n label?: string;\n /** Pin to an icon's top-right corner (parent `relative`), as the tab bar does. */\n corner?: boolean;\n}\n\n/** A presence dot — \"something here\", no figure. */\nexport function NavDot({ tone = \"primary\", label, corner = false, className, ...props }: NavDotProps) {\n return (\n <span\n data-slot=\"nav-dot\"\n aria-hidden={label === undefined ? true : undefined}\n className={cn(\n \"inline-block size-2 shrink-0 rounded-full\",\n DOT_TONE[tone],\n corner && \"absolute -right-1 -top-0.5\",\n className,\n )}\n {...props}\n >\n {label !== undefined && <span className=\"sr-only\">{label}</span>}\n </span>\n );\n}\n"],"mappings":"AAiDI,SAWE,KAXF;AAjDJ,YAAY,WAAW;AACvB,SAAS,UAAU;AAsBnB,MAAM,OAAO;AACb,MAAM,SACJ;AAEF,MAAM,eAAe,CAAC,MAAc,GAAG,CAAC;AAWjC,SAAS,SAAS;AAAA,EACvB;AAAA,EACA,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,MAAI,EAAE,QAAQ,GAAI,QAAO;AACzB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,WAAW;AAAA,QACT,YAAY,WACR,SACA,GAAG,MAAM,SAAS,2BAA2B,oCAAoC;AAAA,QACrF;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAE,kBAAQ,MAAM,GAAG,GAAG,MAAM,OAAM;AAAA,QACnD,oBAAC,UAAK,WAAU,WAAW,gBAAM,KAAK,GAAE;AAAA;AAAA;AAAA,EAC1C;AAEJ;AAIA,MAAM,WAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,UAAU;AAAA,EACV,MAAM;AAAA,EACN,OAAO;AACT;AAWO,SAAS,OAAO,EAAE,OAAO,WAAW,OAAO,SAAS,OAAO,WAAW,GAAG,MAAM,GAAgB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,eAAa,UAAU,SAAY,OAAO;AAAA,MAC1C,WAAW;AAAA,QACT;AAAA,QACA,SAAS,IAAI;AAAA,QACb,UAAU;AAAA,QACV;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH,oBAAU,UAAa,oBAAC,UAAK,WAAU,WAAW,iBAAM;AAAA;AAAA,EAC3D;AAEJ;","names":[]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cn } from "
|
|
5
|
-
import { useControllableState } from "
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { useControllableState } from "../../primitives/use-controllable-state.js";
|
|
6
6
|
function useNavGroupOpen(group, containsCurrent) {
|
|
7
7
|
const [open, setOpen] = useControllableState({
|
|
8
8
|
value: group.open,
|