@braedonsaunders/appkit-ui 0.2.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/LICENSE +661 -0
- package/README.md +11 -0
- package/account-menu.d.ts +49 -0
- package/account-menu.d.ts.map +1 -0
- package/account-menu.js +66 -0
- package/account-menu.js.map +1 -0
- package/alert.d.ts +13 -0
- package/alert.d.ts.map +1 -0
- package/alert.js +22 -0
- package/alert.js.map +1 -0
- package/animated-number.d.ts +13 -0
- package/animated-number.d.ts.map +1 -0
- package/animated-number.js +53 -0
- package/animated-number.js.map +1 -0
- package/api-reference.d.ts +44 -0
- package/api-reference.d.ts.map +1 -0
- package/api-reference.js +82 -0
- package/api-reference.js.map +1 -0
- package/app-shell.d.ts +39 -0
- package/app-shell.d.ts.map +1 -0
- package/app-shell.js +21 -0
- package/app-shell.js.map +1 -0
- package/app-sidebar.d.ts +22 -0
- package/app-sidebar.d.ts.map +1 -0
- package/app-sidebar.js +30 -0
- package/app-sidebar.js.map +1 -0
- package/avatar.d.ts +9 -0
- package/avatar.d.ts.map +1 -0
- package/avatar.js +18 -0
- package/avatar.js.map +1 -0
- package/badge.d.ts +9 -0
- package/badge.d.ts.map +1 -0
- package/badge.js +23 -0
- package/badge.js.map +1 -0
- package/button.d.ts +24 -0
- package/button.d.ts.map +1 -0
- package/button.js +50 -0
- package/button.js.map +1 -0
- package/card.d.ts +21 -0
- package/card.d.ts.map +1 -0
- package/card.js +36 -0
- package/card.js.map +1 -0
- package/checkbox.d.ts +4 -0
- package/checkbox.d.ts.map +1 -0
- package/checkbox.js +6 -0
- package/checkbox.js.map +1 -0
- package/confirm-dialog.d.ts +23 -0
- package/confirm-dialog.d.ts.map +1 -0
- package/confirm-dialog.js +67 -0
- package/confirm-dialog.js.map +1 -0
- package/context-menu.d.ts +47 -0
- package/context-menu.d.ts.map +1 -0
- package/context-menu.js +90 -0
- package/context-menu.js.map +1 -0
- package/dialog.d.ts +25 -0
- package/dialog.d.ts.map +1 -0
- package/dialog.js +89 -0
- package/dialog.js.map +1 -0
- package/document-title.d.ts +11 -0
- package/document-title.d.ts.map +1 -0
- package/document-title.js +17 -0
- package/document-title.js.map +1 -0
- package/drawer.d.ts +63 -0
- package/drawer.d.ts.map +1 -0
- package/drawer.js +188 -0
- package/drawer.js.map +1 -0
- package/empty-state.d.ts +10 -0
- package/empty-state.d.ts.map +1 -0
- package/empty-state.js +7 -0
- package/empty-state.js.map +1 -0
- package/file-uploader.d.ts +67 -0
- package/file-uploader.d.ts.map +1 -0
- package/file-uploader.js +156 -0
- package/file-uploader.js.map +1 -0
- package/filter-chips.d.ts +47 -0
- package/filter-chips.d.ts.map +1 -0
- package/filter-chips.js +62 -0
- package/filter-chips.js.map +1 -0
- package/global-search.d.ts +41 -0
- package/global-search.d.ts.map +1 -0
- package/global-search.js +116 -0
- package/global-search.js.map +1 -0
- package/index.d.ts +70 -0
- package/index.d.ts.map +1 -0
- package/index.js +70 -0
- package/index.js.map +1 -0
- package/input.d.ts +9 -0
- package/input.d.ts.map +1 -0
- package/input.js +11 -0
- package/input.js.map +1 -0
- package/label.d.ts +3 -0
- package/label.d.ts.map +1 -0
- package/label.js +6 -0
- package/label.js.map +1 -0
- package/line-grid.d.ts +48 -0
- package/line-grid.d.ts.map +1 -0
- package/line-grid.js +155 -0
- package/line-grid.js.map +1 -0
- package/link-context.d.ts +44 -0
- package/link-context.d.ts.map +1 -0
- package/link-context.js +32 -0
- package/link-context.js.map +1 -0
- package/list-nav.d.ts +28 -0
- package/list-nav.d.ts.map +1 -0
- package/list-nav.js +41 -0
- package/list-nav.js.map +1 -0
- package/list-pagination.d.ts +27 -0
- package/list-pagination.d.ts.map +1 -0
- package/list-pagination.js +43 -0
- package/list-pagination.js.map +1 -0
- package/list-params.d.ts +41 -0
- package/list-params.d.ts.map +1 -0
- package/list-params.js +84 -0
- package/list-params.js.map +1 -0
- package/list-search-input-state.d.ts +13 -0
- package/list-search-input-state.d.ts.map +1 -0
- package/list-search-input-state.js +27 -0
- package/list-search-input-state.js.map +1 -0
- package/list-search-input.d.ts +16 -0
- package/list-search-input.d.ts.map +1 -0
- package/list-search-input.js +51 -0
- package/list-search-input.js.map +1 -0
- package/mobile-tab-bar.d.ts +14 -0
- package/mobile-tab-bar.d.ts.map +1 -0
- package/mobile-tab-bar.js +25 -0
- package/mobile-tab-bar.js.map +1 -0
- package/multi-select.d.ts +32 -0
- package/multi-select.d.ts.map +1 -0
- package/multi-select.js +52 -0
- package/multi-select.js.map +1 -0
- package/nav-overflow.d.ts +12 -0
- package/nav-overflow.d.ts.map +1 -0
- package/nav-overflow.js +30 -0
- package/nav-overflow.js.map +1 -0
- package/navigation-config-editor.d.ts +26 -0
- package/navigation-config-editor.d.ts.map +1 -0
- package/navigation-config-editor.js +57 -0
- package/navigation-config-editor.js.map +1 -0
- package/navigation-config.d.ts +48 -0
- package/navigation-config.d.ts.map +1 -0
- package/navigation-config.js +75 -0
- package/navigation-config.js.map +1 -0
- package/navigation-mode.d.ts +16 -0
- package/navigation-mode.d.ts.map +1 -0
- package/navigation-mode.js +30 -0
- package/navigation-mode.js.map +1 -0
- package/notifications-bell.d.ts +22 -0
- package/notifications-bell.d.ts.map +1 -0
- package/notifications-bell.js +30 -0
- package/notifications-bell.js.map +1 -0
- package/org-chart.d.ts +85 -0
- package/org-chart.d.ts.map +1 -0
- package/org-chart.js +193 -0
- package/org-chart.js.map +1 -0
- package/package.json +61 -0
- package/page-header.d.ts +32 -0
- package/page-header.d.ts.map +1 -0
- package/page-header.js +14 -0
- package/page-header.js.map +1 -0
- package/page-layout.d.ts +29 -0
- package/page-layout.d.ts.map +1 -0
- package/page-layout.js +23 -0
- package/page-layout.js.map +1 -0
- package/page-transition.d.ts +13 -0
- package/page-transition.d.ts.map +1 -0
- package/page-transition.js +14 -0
- package/page-transition.js.map +1 -0
- package/paged-table.d.ts +54 -0
- package/paged-table.d.ts.map +1 -0
- package/paged-table.js +74 -0
- package/paged-table.js.map +1 -0
- package/popover.d.ts +16 -0
- package/popover.d.ts.map +1 -0
- package/popover.js +99 -0
- package/popover.js.map +1 -0
- package/progress.d.ts +7 -0
- package/progress.d.ts.map +1 -0
- package/progress.js +8 -0
- package/progress.js.map +1 -0
- package/prompt-dialog.d.ts +21 -0
- package/prompt-dialog.d.ts.map +1 -0
- package/prompt-dialog.js +109 -0
- package/prompt-dialog.js.map +1 -0
- package/record-list.d.ts +76 -0
- package/record-list.d.ts.map +1 -0
- package/record-list.js +51 -0
- package/record-list.js.map +1 -0
- package/search-select.d.ts +42 -0
- package/search-select.d.ts.map +1 -0
- package/search-select.js +194 -0
- package/search-select.js.map +1 -0
- package/select-options.d.ts +16 -0
- package/select-options.d.ts.map +1 -0
- package/select-options.js +119 -0
- package/select-options.js.map +1 -0
- package/select.d.ts +24 -0
- package/select.d.ts.map +1 -0
- package/select.js +65 -0
- package/select.js.map +1 -0
- package/separator.d.ts +6 -0
- package/separator.d.ts.map +1 -0
- package/separator.js +7 -0
- package/separator.js.map +1 -0
- package/settings-layout.d.ts +79 -0
- package/settings-layout.d.ts.map +1 -0
- package/settings-layout.js +74 -0
- package/settings-layout.js.map +1 -0
- package/sidebar-nav.d.ts +67 -0
- package/sidebar-nav.d.ts.map +1 -0
- package/sidebar-nav.js +291 -0
- package/sidebar-nav.js.map +1 -0
- package/signature-pad.d.ts +16 -0
- package/signature-pad.d.ts.map +1 -0
- package/signature-pad.js +355 -0
- package/signature-pad.js.map +1 -0
- package/skeleton.d.ts +9 -0
- package/skeleton.d.ts.map +1 -0
- package/skeleton.js +12 -0
- package/skeleton.js.map +1 -0
- package/slider.d.ts +13 -0
- package/slider.d.ts.map +1 -0
- package/slider.js +15 -0
- package/slider.js.map +1 -0
- package/sortable-th.d.ts +28 -0
- package/sortable-th.d.ts.map +1 -0
- package/sortable-th.js +35 -0
- package/sortable-th.js.map +1 -0
- package/sparkline.d.ts +16 -0
- package/sparkline.d.ts.map +1 -0
- package/sparkline.js +46 -0
- package/sparkline.js.map +1 -0
- package/spinner.d.ts +11 -0
- package/spinner.d.ts.map +1 -0
- package/spinner.js +11 -0
- package/spinner.js.map +1 -0
- package/styles.css +166 -0
- package/subtab-nav.d.ts +17 -0
- package/subtab-nav.d.ts.map +1 -0
- package/subtab-nav.js +11 -0
- package/subtab-nav.js.map +1 -0
- package/switch.d.ts +9 -0
- package/switch.d.ts.map +1 -0
- package/switch.js +11 -0
- package/switch.js.map +1 -0
- package/tab-content.d.ts +24 -0
- package/tab-content.d.ts.map +1 -0
- package/tab-content.js +26 -0
- package/tab-content.js.map +1 -0
- package/table.d.ts +28 -0
- package/table.d.ts.map +1 -0
- package/table.js +21 -0
- package/table.js.map +1 -0
- package/tabs.d.ts +18 -0
- package/tabs.d.ts.map +1 -0
- package/tabs.js +18 -0
- package/tabs.js.map +1 -0
- package/text-context.d.ts +9 -0
- package/text-context.d.ts.map +1 -0
- package/text-context.js +12 -0
- package/text-context.js.map +1 -0
- package/textarea.d.ts +4 -0
- package/textarea.d.ts.map +1 -0
- package/textarea.js +6 -0
- package/textarea.js.map +1 -0
- package/theme-script.d.ts +3 -0
- package/theme-script.d.ts.map +1 -0
- package/theme-script.js +6 -0
- package/theme-script.js.map +1 -0
- package/theme.d.ts +28 -0
- package/theme.d.ts.map +1 -0
- package/theme.js +92 -0
- package/theme.js.map +1 -0
- package/toast.d.ts +52 -0
- package/toast.d.ts.map +1 -0
- package/toast.js +178 -0
- package/toast.js.map +1 -0
- package/tooltip.d.ts +12 -0
- package/tooltip.d.ts.map +1 -0
- package/tooltip.js +14 -0
- package/tooltip.js.map +1 -0
- package/top-nav.d.ts +18 -0
- package/top-nav.d.ts.map +1 -0
- package/top-nav.js +160 -0
- package/top-nav.js.map +1 -0
- package/upload-limits.d.ts +4 -0
- package/upload-limits.d.ts.map +1 -0
- package/upload-limits.js +21 -0
- package/upload-limits.js.map +1 -0
- package/use-element-size.d.ts +7 -0
- package/use-element-size.d.ts.map +1 -0
- package/use-element-size.js +21 -0
- package/use-element-size.js.map +1 -0
- package/utils.d.ts +4 -0
- package/utils.d.ts.map +1 -0
- package/utils.js +7 -0
- package/utils.js.map +1 -0
- package/workspace-launcher.d.ts +50 -0
- package/workspace-launcher.d.ts.map +1 -0
- package/workspace-launcher.js +127 -0
- package/workspace-launcher.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-search-input.js","sourceRoot":"","sources":["../src/list-search-input.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Search, X } from 'lucide-react'\nimport { Input } from './input'\nimport { useListNav } from './list-nav'\nimport {\n applySearchInputEdit,\n createSearchInputEditState,\n reconcileSearchInputUrl,\n} from './list-search-input-state'\n\n/**\n * URL-driven search box: debounced 250ms, writes `?q=` (configurable) and\n * resets the page param. Keeps focus by replacing (not pushing) through the\n * wired ListNav router. Localized copy is supplied through props.\n */\nexport function SearchInput({\n placeholder = 'Search…',\n searchLabel = 'Search',\n clearLabel = 'Clear search',\n paramKey = 'q',\n pageParamKey = 'page',\n className,\n}: {\n placeholder?: string\n searchLabel?: string\n clearLabel?: string\n paramKey?: string\n /** Pagination param to reset when the search changes (sub-tables use prefixed params). */\n pageParamKey?: string\n className?: string\n}) {\n const nav = useListNav()\n const urlValue = React.useMemo(\n () => new URLSearchParams(nav?.search ?? '').get(paramKey) ?? '',\n [nav?.search, paramKey],\n )\n const [edit, setEdit] = React.useState(() => createSearchInputEditState(urlValue))\n const [navigationPending, startTransition] = React.useTransition()\n const value = edit.value\n\n React.useEffect(() => {\n setEdit((current) => reconcileSearchInputUrl(current, urlValue, navigationPending))\n }, [navigationPending, urlValue])\n\n React.useEffect(() => {\n if (!nav) return\n const handle = setTimeout(() => {\n // No-op when the input already matches the URL (mount, external URL\n // change) — navigating anyway would strip the page param and reset\n // deep-linked pagination back to page 1.\n const current = new URLSearchParams(nav.search).get(paramKey) ?? ''\n if (value === current) return\n const next = new URLSearchParams(nav.search)\n if (value) next.set(paramKey, value)\n else next.delete(paramKey)\n next.delete(pageParamKey)\n const qs = next.toString()\n // Transition keeps the current page (and this input's focus) mounted\n // while the next server render streams in.\n startTransition(() => {\n nav.replace(qs ? `${nav.pathname}?${qs}` : nav.pathname)\n })\n }, 250)\n return () => clearTimeout(handle)\n }, [nav, pageParamKey, paramKey, value])\n\n return (\n <div className={className ?? 'relative w-full sm:w-72'}>\n <Search className=\"pointer-events-none absolute left-2.5 top-2 text-fg-subtle\" size={16} />\n <Input\n type=\"search\"\n aria-label={searchLabel}\n placeholder={placeholder}\n value={value}\n onChange={(e) =>\n setEdit(applySearchInputEdit(e.target.value, urlValue, navigationPending))\n }\n // Hide the browser's native search clear (×) — we render our own.\n className=\"h-8 pl-9 pr-9 [&::-webkit-search-cancel-button]:hidden\"\n />\n {value ? (\n <button\n type=\"button\"\n aria-label={clearLabel}\n onClick={() => setEdit(applySearchInputEdit('', urlValue, navigationPending))}\n className=\"absolute right-2.5 top-2 text-fg-subtle hover:text-fg-muted\"\n >\n <X size={16} />\n </button>\n ) : null}\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type SidebarNavGroup } from './sidebar-nav.js';
|
|
3
|
+
import { type LinkRender } from './link-context.js';
|
|
4
|
+
/** Native-style mobile bottom bar driven by the shared navigation registry. */
|
|
5
|
+
export declare function MobileTabBar({ groups, pathname, onOpenMenu, linkRender, tabCount, menuLabel, ariaLabel, }: {
|
|
6
|
+
groups: SidebarNavGroup[];
|
|
7
|
+
pathname: string;
|
|
8
|
+
onOpenMenu: () => void;
|
|
9
|
+
linkRender?: LinkRender;
|
|
10
|
+
tabCount?: number;
|
|
11
|
+
menuLabel?: string;
|
|
12
|
+
ariaLabel?: string;
|
|
13
|
+
}): React.JSX.Element | null;
|
|
14
|
+
//# sourceMappingURL=mobile-tab-bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-tab-bar.d.ts","sourceRoot":"","sources":["../src/mobile-tab-bar.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AASnE,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,EACR,UAAU,EACV,UAA8B,EAC9B,QAAY,EACZ,SAAkB,EAClB,SAAgC,GACjC,EAAE;IACD,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,4BAmCA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Menu } from 'lucide-react';
|
|
5
|
+
import { findActiveNavHref, NavIcon, selectMobileTabs, } from './sidebar-nav.js';
|
|
6
|
+
import { defaultLinkRender } from './link-context.js';
|
|
7
|
+
import { cn } from './utils.js';
|
|
8
|
+
const tabClass = (active) => cn('flex min-w-0 flex-1 flex-col items-center gap-1 px-1 pt-2 pb-1.5 text-[10px] font-medium transition-colors', active ? 'text-primary' : 'text-fg-muted hover:text-fg');
|
|
9
|
+
/** Native-style mobile bottom bar driven by the shared navigation registry. */
|
|
10
|
+
export function MobileTabBar({ groups, pathname, onOpenMenu, linkRender = defaultLinkRender, tabCount = 4, menuLabel = 'Menu', ariaLabel = 'Primary navigation', }) {
|
|
11
|
+
const tabs = selectMobileTabs(groups, tabCount);
|
|
12
|
+
const activeHref = findActiveNavHref(pathname, [{ label: '', items: tabs }]);
|
|
13
|
+
if (tabs.length === 0)
|
|
14
|
+
return null;
|
|
15
|
+
return (_jsxs("nav", { "aria-label": ariaLabel, className: "flex shrink-0 border-t border-border bg-surface pb-[env(safe-area-inset-bottom)] lg:hidden", children: [tabs.map((tab) => {
|
|
16
|
+
const active = activeHref === tab.href;
|
|
17
|
+
return (_jsx(React.Fragment, { children: linkRender({
|
|
18
|
+
href: tab.href,
|
|
19
|
+
ariaCurrent: active ? 'page' : undefined,
|
|
20
|
+
className: tabClass(active),
|
|
21
|
+
children: (_jsxs(_Fragment, { children: [_jsx(NavIcon, { iconKey: tab.iconKey, icon: tab.icon, size: 20 }), _jsx("span", { className: "w-full truncate text-center", children: tab.label })] })),
|
|
22
|
+
}) }, tab.href));
|
|
23
|
+
}), _jsxs("button", { type: "button", onClick: onOpenMenu, className: tabClass(false), children: [_jsx(Menu, { size: 20 }), _jsx("span", { className: "w-full truncate text-center", children: menuLabel })] })] }));
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=mobile-tab-bar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-tab-bar.js","sourceRoot":"","sources":["../src/mobile-tab-bar.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EACL,iBAAiB,EACjB,OAAO,EACP,gBAAgB,GAEjB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,iBAAiB,EAAmB,MAAM,gBAAgB,CAAA;AACnE,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAA;AAE5B,MAAM,QAAQ,GAAG,CAAC,MAAe,EAAE,EAAE,CACnC,EAAE,CACA,4GAA4G,EAC5G,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,6BAA6B,CACxD,CAAA;AAEH,+EAA+E;AAC/E,MAAM,UAAU,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,EACR,UAAU,EACV,UAAU,GAAG,iBAAiB,EAC9B,QAAQ,GAAG,CAAC,EACZ,SAAS,GAAG,MAAM,EAClB,SAAS,GAAG,oBAAoB,GASjC;IACC,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IAC/C,MAAM,UAAU,GAAG,iBAAiB,CAAC,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAE5E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IAElC,OAAO,CACL,6BACc,SAAS,EACrB,SAAS,EAAC,4FAA4F,aAErG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBAChB,MAAM,MAAM,GAAG,UAAU,KAAK,GAAG,CAAC,IAAI,CAAA;gBACtC,OAAO,CACL,KAAC,KAAK,CAAC,QAAQ,cACZ,UAAU,CAAC;wBACV,IAAI,EAAE,GAAG,CAAC,IAAI;wBACd,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;wBACxC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC;wBAC3B,QAAQ,EAAE,CACR,8BACE,KAAC,OAAO,IAAC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,GAAI,EAC3D,eAAM,SAAS,EAAC,6BAA6B,YAAE,GAAG,CAAC,KAAK,GAAQ,IAC/D,CACJ;qBACF,CAAC,IAXiB,GAAG,CAAC,IAAI,CAYZ,CAClB,CAAA;YACH,CAAC,CAAC,EACF,kBAAQ,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,aACnE,KAAC,IAAI,IAAC,IAAI,EAAE,EAAE,GAAI,EAClB,eAAM,SAAS,EAAC,6BAA6B,YAAE,SAAS,GAAQ,IACzD,IACL,CACP,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Menu } from 'lucide-react'\nimport {\n findActiveNavHref,\n NavIcon,\n selectMobileTabs,\n type SidebarNavGroup,\n} from './sidebar-nav'\nimport { defaultLinkRender, type LinkRender } from './link-context'\nimport { cn } from './utils'\n\nconst tabClass = (active: boolean) =>\n cn(\n 'flex min-w-0 flex-1 flex-col items-center gap-1 px-1 pt-2 pb-1.5 text-[10px] font-medium transition-colors',\n active ? 'text-primary' : 'text-fg-muted hover:text-fg',\n )\n\n/** Native-style mobile bottom bar driven by the shared navigation registry. */\nexport function MobileTabBar({\n groups,\n pathname,\n onOpenMenu,\n linkRender = defaultLinkRender,\n tabCount = 4,\n menuLabel = 'Menu',\n ariaLabel = 'Primary navigation',\n}: {\n groups: SidebarNavGroup[]\n pathname: string\n onOpenMenu: () => void\n linkRender?: LinkRender\n tabCount?: number\n menuLabel?: string\n ariaLabel?: string\n}) {\n const tabs = selectMobileTabs(groups, tabCount)\n const activeHref = findActiveNavHref(pathname, [{ label: '', items: tabs }])\n\n if (tabs.length === 0) return null\n\n return (\n <nav\n aria-label={ariaLabel}\n className=\"flex shrink-0 border-t border-border bg-surface pb-[env(safe-area-inset-bottom)] lg:hidden\"\n >\n {tabs.map((tab) => {\n const active = activeHref === tab.href\n return (\n <React.Fragment key={tab.href}>\n {linkRender({\n href: tab.href,\n ariaCurrent: active ? 'page' : undefined,\n className: tabClass(active),\n children: (\n <>\n <NavIcon iconKey={tab.iconKey} icon={tab.icon} size={20} />\n <span className=\"w-full truncate text-center\">{tab.label}</span>\n </>\n ),\n })}\n </React.Fragment>\n )\n })}\n <button type=\"button\" onClick={onOpenMenu} className={tabClass(false)}>\n <Menu size={20} />\n <span className=\"w-full truncate text-center\">{menuLabel}</span>\n </button>\n </nav>\n )\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type MultiSelectOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
group?: string;
|
|
8
|
+
};
|
|
9
|
+
export type MultiSelectProps = {
|
|
10
|
+
value: string[];
|
|
11
|
+
onChange: (value: string[]) => void;
|
|
12
|
+
options: MultiSelectOption[];
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
searchPlaceholder?: string;
|
|
15
|
+
noMatchesLabel?: string;
|
|
16
|
+
clearLabel?: string;
|
|
17
|
+
selectAllLabel?: string;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
clearable?: boolean;
|
|
20
|
+
maxVisibleValues?: number;
|
|
21
|
+
id?: string;
|
|
22
|
+
ariaLabel?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
triggerClassName?: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Searchable multi-value picker for permissions, categories, tags, and other
|
|
28
|
+
* bounded option sets. The selected value order follows the option registry so
|
|
29
|
+
* apps get stable rendering even when values are supplied from persisted data.
|
|
30
|
+
*/
|
|
31
|
+
export declare function MultiSelect({ value, onChange, options, placeholder, searchPlaceholder, noMatchesLabel, clearLabel, selectAllLabel, disabled, clearable, maxVisibleValues, id, ariaLabel, className, triggerClassName, }: MultiSelectProps): React.JSX.Element;
|
|
32
|
+
//# sourceMappingURL=multi-select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-select.d.ts","sourceRoot":"","sources":["../src/multi-select.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACnC,OAAO,EAAE,iBAAiB,EAAE,CAAA;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAuB,EACvB,iBAA6B,EAC7B,cAA6B,EAC7B,UAAoB,EACpB,cAA6B,EAC7B,QAAgB,EAChB,SAAgB,EAChB,gBAAoB,EACpB,EAAE,EACF,SAAS,EACT,SAAS,EACT,gBAAgB,GACjB,EAAE,gBAAgB,qBA+JlB"}
|
package/multi-select.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Check, ChevronDown, Search, X } from 'lucide-react';
|
|
5
|
+
import { Badge } from './badge.js';
|
|
6
|
+
import { Button } from './button.js';
|
|
7
|
+
import { Input } from './input.js';
|
|
8
|
+
import { Popover } from './popover.js';
|
|
9
|
+
import { cn } from './utils.js';
|
|
10
|
+
/**
|
|
11
|
+
* Searchable multi-value picker for permissions, categories, tags, and other
|
|
12
|
+
* bounded option sets. The selected value order follows the option registry so
|
|
13
|
+
* apps get stable rendering even when values are supplied from persisted data.
|
|
14
|
+
*/
|
|
15
|
+
export function MultiSelect({ value, onChange, options, placeholder = 'Select…', searchPlaceholder = 'Search…', noMatchesLabel = 'No matches', clearLabel = 'Clear', selectAllLabel = 'Select all', disabled = false, clearable = true, maxVisibleValues = 3, id, ariaLabel, className, triggerClassName, }) {
|
|
16
|
+
const [open, setOpen] = React.useState(false);
|
|
17
|
+
const [search, setSearch] = React.useState('');
|
|
18
|
+
const selected = React.useMemo(() => new Set(value), [value]);
|
|
19
|
+
const selectedOptions = React.useMemo(() => options.filter((option) => selected.has(option.value)), [options, selected]);
|
|
20
|
+
const enabledValues = React.useMemo(() => options.filter((option) => !option.disabled).map((option) => option.value), [options]);
|
|
21
|
+
const filtered = React.useMemo(() => {
|
|
22
|
+
const query = search.trim().toLowerCase();
|
|
23
|
+
if (!query)
|
|
24
|
+
return options;
|
|
25
|
+
return options.filter((option) => [option.label, option.description, option.group]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.some((field) => field.toLowerCase().includes(query)));
|
|
28
|
+
}, [options, search]);
|
|
29
|
+
function toggle(option) {
|
|
30
|
+
if (option.disabled)
|
|
31
|
+
return;
|
|
32
|
+
if (selected.has(option.value)) {
|
|
33
|
+
onChange(value.filter((candidate) => candidate !== option.value));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
onChange([...value, option.value]);
|
|
37
|
+
}
|
|
38
|
+
const visibleValues = selectedOptions.slice(0, Math.max(0, maxVisibleValues));
|
|
39
|
+
const overflowCount = Math.max(0, selectedOptions.length - visibleValues.length);
|
|
40
|
+
const allEnabledSelected = enabledValues.length > 0 && enabledValues.every((candidate) => selected.has(candidate));
|
|
41
|
+
return (_jsx("div", { className: cn('w-full', className), children: _jsxs(Popover, { open: open, onOpenChange: (next) => {
|
|
42
|
+
setOpen(next);
|
|
43
|
+
if (!next)
|
|
44
|
+
setSearch('');
|
|
45
|
+
}, align: "start", className: "w-[min(24rem,calc(100vw-2rem))] overflow-hidden p-0", trigger: _jsxs("button", { id: id, type: "button", "aria-label": ariaLabel, "aria-expanded": open, disabled: disabled, onClick: () => setOpen((current) => !current), className: cn('flex min-h-9 w-full items-center gap-1.5 rounded-md border border-border bg-surface px-3 py-1.5 text-left text-sm text-fg outline-none transition-colors', 'hover:border-border-strong focus-visible:ring-2 focus-visible:ring-ring/50', 'disabled:pointer-events-none disabled:opacity-50', triggerClassName), children: [_jsx("span", { className: "flex min-w-0 flex-1 flex-wrap gap-1", children: selectedOptions.length === 0 ? (_jsx("span", { className: "truncate text-fg-subtle", children: placeholder })) : (_jsxs(_Fragment, { children: [visibleValues.map((option) => (_jsx(Badge, { variant: "secondary", className: "max-w-full truncate", children: option.label }, option.value))), overflowCount > 0 ? _jsxs(Badge, { variant: "outline", children: ["+", overflowCount] }) : null] })) }), _jsx(ChevronDown, { className: "size-4 shrink-0 text-fg-subtle", "aria-hidden": true })] }), children: [_jsxs("div", { className: "border-b border-border p-2", children: [_jsxs("div", { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-fg-subtle" }), _jsx(Input, { value: search, onChange: (event) => setSearch(event.target.value), placeholder: searchPlaceholder, className: "h-8 pl-8 text-xs", autoFocus: true })] }), _jsxs("div", { className: "mt-2 flex items-center justify-between gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onChange(allEnabledSelected ? [] : enabledValues), disabled: enabledValues.length === 0, children: allEnabledSelected ? clearLabel : selectAllLabel }), clearable && value.length > 0 ? (_jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onChange([]), children: [_jsx(X, { className: "size-3.5" }), clearLabel] })) : null] })] }), _jsx("div", { className: "max-h-72 overflow-y-auto p-1", children: filtered.length === 0 ? (_jsx("div", { className: "px-3 py-8 text-center text-sm text-fg-subtle", children: noMatchesLabel })) : (filtered.map((option) => {
|
|
46
|
+
const checked = selected.has(option.value);
|
|
47
|
+
return (_jsxs("button", { type: "button", role: "option", "aria-selected": checked, disabled: option.disabled, onClick: () => toggle(option), className: cn('flex w-full items-start gap-2.5 rounded-md px-2.5 py-2 text-left text-sm transition-colors', 'hover:bg-surface-hover disabled:pointer-events-none disabled:opacity-40', checked && 'bg-primary-subtle'), children: [_jsx("span", { className: cn('mt-0.5 grid size-4 shrink-0 place-items-center rounded border', checked
|
|
48
|
+
? 'border-primary bg-primary text-primary-fg'
|
|
49
|
+
: 'border-border-strong bg-surface'), children: checked ? _jsx(Check, { className: "size-3", strokeWidth: 3, "aria-hidden": true }) : null }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium text-fg", children: option.label }), option.description ? (_jsx("span", { className: "mt-0.5 block truncate text-xs text-fg-muted", children: option.description })) : null] }), option.group ? (_jsx("span", { className: "shrink-0 text-[10px] font-medium tracking-wide text-fg-subtle uppercase", children: option.group })) : null] }, option.value));
|
|
50
|
+
})) })] }) }));
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=multi-select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multi-select.js","sourceRoot":"","sources":["../src/multi-select.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { Check, ChevronDown, Search, X } from 'lucide-react'\nimport { Badge } from './badge'\nimport { Button } from './button'\nimport { Input } from './input'\nimport { Popover } from './popover'\nimport { cn } from './utils'\n\nexport type MultiSelectOption = {\n value: string\n label: string\n description?: string\n disabled?: boolean\n group?: string\n}\n\nexport type MultiSelectProps = {\n value: string[]\n onChange: (value: string[]) => void\n options: MultiSelectOption[]\n placeholder?: string\n searchPlaceholder?: string\n noMatchesLabel?: string\n clearLabel?: string\n selectAllLabel?: string\n disabled?: boolean\n clearable?: boolean\n maxVisibleValues?: number\n id?: string\n ariaLabel?: string\n className?: string\n triggerClassName?: string\n}\n\n/**\n * Searchable multi-value picker for permissions, categories, tags, and other\n * bounded option sets. The selected value order follows the option registry so\n * apps get stable rendering even when values are supplied from persisted data.\n */\nexport function MultiSelect({\n value,\n onChange,\n options,\n placeholder = 'Select…',\n searchPlaceholder = 'Search…',\n noMatchesLabel = 'No matches',\n clearLabel = 'Clear',\n selectAllLabel = 'Select all',\n disabled = false,\n clearable = true,\n maxVisibleValues = 3,\n id,\n ariaLabel,\n className,\n triggerClassName,\n}: MultiSelectProps) {\n const [open, setOpen] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const selected = React.useMemo(() => new Set(value), [value])\n const selectedOptions = React.useMemo(\n () => options.filter((option) => selected.has(option.value)),\n [options, selected],\n )\n const enabledValues = React.useMemo(\n () => options.filter((option) => !option.disabled).map((option) => option.value),\n [options],\n )\n const filtered = React.useMemo(() => {\n const query = search.trim().toLowerCase()\n if (!query) return options\n return options.filter((option) =>\n [option.label, option.description, option.group]\n .filter(Boolean)\n .some((field) => field!.toLowerCase().includes(query)),\n )\n }, [options, search])\n\n function toggle(option: MultiSelectOption) {\n if (option.disabled) return\n if (selected.has(option.value)) {\n onChange(value.filter((candidate) => candidate !== option.value))\n return\n }\n onChange([...value, option.value])\n }\n\n const visibleValues = selectedOptions.slice(0, Math.max(0, maxVisibleValues))\n const overflowCount = Math.max(0, selectedOptions.length - visibleValues.length)\n const allEnabledSelected =\n enabledValues.length > 0 && enabledValues.every((candidate) => selected.has(candidate))\n\n return (\n <div className={cn('w-full', className)}>\n <Popover\n open={open}\n onOpenChange={(next) => {\n setOpen(next)\n if (!next) setSearch('')\n }}\n align=\"start\"\n className=\"w-[min(24rem,calc(100vw-2rem))] overflow-hidden p-0\"\n trigger={\n <button\n id={id}\n type=\"button\"\n aria-label={ariaLabel}\n aria-expanded={open}\n disabled={disabled}\n onClick={() => setOpen((current) => !current)}\n className={cn(\n 'flex min-h-9 w-full items-center gap-1.5 rounded-md border border-border bg-surface px-3 py-1.5 text-left text-sm text-fg outline-none transition-colors',\n 'hover:border-border-strong focus-visible:ring-2 focus-visible:ring-ring/50',\n 'disabled:pointer-events-none disabled:opacity-50',\n triggerClassName,\n )}\n >\n <span className=\"flex min-w-0 flex-1 flex-wrap gap-1\">\n {selectedOptions.length === 0 ? (\n <span className=\"truncate text-fg-subtle\">{placeholder}</span>\n ) : (\n <>\n {visibleValues.map((option) => (\n <Badge key={option.value} variant=\"secondary\" className=\"max-w-full truncate\">\n {option.label}\n </Badge>\n ))}\n {overflowCount > 0 ? <Badge variant=\"outline\">+{overflowCount}</Badge> : null}\n </>\n )}\n </span>\n <ChevronDown className=\"size-4 shrink-0 text-fg-subtle\" aria-hidden />\n </button>\n }\n >\n <div className=\"border-b border-border p-2\">\n <div className=\"relative\">\n <Search className=\"pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-fg-subtle\" />\n <Input\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={searchPlaceholder}\n className=\"h-8 pl-8 text-xs\"\n autoFocus\n />\n </div>\n <div className=\"mt-2 flex items-center justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onChange(allEnabledSelected ? [] : enabledValues)}\n disabled={enabledValues.length === 0}\n >\n {allEnabledSelected ? clearLabel : selectAllLabel}\n </Button>\n {clearable && value.length > 0 ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => onChange([])}>\n <X className=\"size-3.5\" />\n {clearLabel}\n </Button>\n ) : null}\n </div>\n </div>\n <div className=\"max-h-72 overflow-y-auto p-1\">\n {filtered.length === 0 ? (\n <div className=\"px-3 py-8 text-center text-sm text-fg-subtle\">{noMatchesLabel}</div>\n ) : (\n filtered.map((option) => {\n const checked = selected.has(option.value)\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"option\"\n aria-selected={checked}\n disabled={option.disabled}\n onClick={() => toggle(option)}\n className={cn(\n 'flex w-full items-start gap-2.5 rounded-md px-2.5 py-2 text-left text-sm transition-colors',\n 'hover:bg-surface-hover disabled:pointer-events-none disabled:opacity-40',\n checked && 'bg-primary-subtle',\n )}\n >\n <span\n className={cn(\n 'mt-0.5 grid size-4 shrink-0 place-items-center rounded border',\n checked\n ? 'border-primary bg-primary text-primary-fg'\n : 'border-border-strong bg-surface',\n )}\n >\n {checked ? <Check className=\"size-3\" strokeWidth={3} aria-hidden /> : null}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate font-medium text-fg\">{option.label}</span>\n {option.description ? (\n <span className=\"mt-0.5 block truncate text-xs text-fg-muted\">\n {option.description}\n </span>\n ) : null}\n </span>\n {option.group ? (\n <span className=\"shrink-0 text-[10px] font-medium tracking-wide text-fg-subtle uppercase\">\n {option.group}\n </span>\n ) : null}\n </button>\n )\n })\n )}\n </div>\n </Popover>\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the number of leading top-navigation groups that fit before the
|
|
3
|
+
* overflow menu. All measurements are CSS pixels from the rendered controls.
|
|
4
|
+
* Shared by the production top-navigation implementation.
|
|
5
|
+
*/
|
|
6
|
+
export declare function visibleTopNavGroupCount({ availableWidth, groupWidths, moreWidth, gap, }: {
|
|
7
|
+
availableWidth: number;
|
|
8
|
+
groupWidths: number[];
|
|
9
|
+
moreWidth: number;
|
|
10
|
+
gap: number;
|
|
11
|
+
}): number;
|
|
12
|
+
//# sourceMappingURL=nav-overflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-overflow.d.ts","sourceRoot":"","sources":["../src/nav-overflow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,cAAc,EACd,WAAW,EACX,SAAS,EACT,GAAG,GACJ,EAAE;IACD,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;CACZ,GAAG,MAAM,CAsBT"}
|
package/nav-overflow.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return the number of leading top-navigation groups that fit before the
|
|
3
|
+
* overflow menu. All measurements are CSS pixels from the rendered controls.
|
|
4
|
+
* Shared by the production top-navigation implementation.
|
|
5
|
+
*/
|
|
6
|
+
export function visibleTopNavGroupCount({ availableWidth, groupWidths, moreWidth, gap, }) {
|
|
7
|
+
const available = finiteNonNegative(availableWidth);
|
|
8
|
+
const widths = groupWidths.map(finiteNonNegative);
|
|
9
|
+
const itemGap = finiteNonNegative(gap);
|
|
10
|
+
if (widths.length === 0)
|
|
11
|
+
return 0;
|
|
12
|
+
const allGroupsWidth = widths.reduce((sum, width) => sum + width, 0) + itemGap * (widths.length - 1);
|
|
13
|
+
if (allGroupsWidth <= available)
|
|
14
|
+
return widths.length;
|
|
15
|
+
const availableForGroups = Math.max(0, available - finiteNonNegative(moreWidth) - itemGap);
|
|
16
|
+
let used = 0;
|
|
17
|
+
let visible = 0;
|
|
18
|
+
for (const width of widths) {
|
|
19
|
+
const next = used + (visible > 0 ? itemGap : 0) + width;
|
|
20
|
+
if (next > availableForGroups)
|
|
21
|
+
break;
|
|
22
|
+
used = next;
|
|
23
|
+
visible += 1;
|
|
24
|
+
}
|
|
25
|
+
return visible;
|
|
26
|
+
}
|
|
27
|
+
function finiteNonNegative(value) {
|
|
28
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=nav-overflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nav-overflow.js","sourceRoot":"","sources":["../src/nav-overflow.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,EACtC,cAAc,EACd,WAAW,EACX,SAAS,EACT,GAAG,GAMJ;IACC,MAAM,SAAS,GAAG,iBAAiB,CAAC,cAAc,CAAC,CAAA;IACnD,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAA;IAEtC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAEjC,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,OAAO,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IACpG,IAAI,cAAc,IAAI,SAAS;QAAE,OAAO,MAAM,CAAC,MAAM,CAAA;IAErD,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,CAAA;IAC1F,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,CAAA;QACvD,IAAI,IAAI,GAAG,kBAAkB;YAAE,MAAK;QACpC,IAAI,GAAG,IAAI,CAAA;QACX,OAAO,IAAI,CAAC,CAAA;IACd,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AACxD,CAAC","sourcesContent":["/**\n * Return the number of leading top-navigation groups that fit before the\n * overflow menu. All measurements are CSS pixels from the rendered controls.\n * Shared by the production top-navigation implementation.\n */\nexport function visibleTopNavGroupCount({\n availableWidth,\n groupWidths,\n moreWidth,\n gap,\n}: {\n availableWidth: number\n groupWidths: number[]\n moreWidth: number\n gap: number\n}): number {\n const available = finiteNonNegative(availableWidth)\n const widths = groupWidths.map(finiteNonNegative)\n const itemGap = finiteNonNegative(gap)\n\n if (widths.length === 0) return 0\n\n const allGroupsWidth = widths.reduce((sum, width) => sum + width, 0) + itemGap * (widths.length - 1)\n if (allGroupsWidth <= available) return widths.length\n\n const availableForGroups = Math.max(0, available - finiteNonNegative(moreWidth) - itemGap)\n let used = 0\n let visible = 0\n\n for (const width of widths) {\n const next = used + (visible > 0 ? itemGap : 0) + width\n if (next > availableForGroups) break\n used = next\n visible += 1\n }\n\n return visible\n}\n\nfunction finiteNonNegative(value: number): number {\n return Number.isFinite(value) ? Math.max(0, value) : 0\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type NavigationRegistryItem, type TenantNavigationConfig } from './navigation-config.js';
|
|
3
|
+
export type NavigationConfigEditorLabels = {
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
visible?: string;
|
|
7
|
+
hidden?: string;
|
|
8
|
+
required?: string;
|
|
9
|
+
moveUp?: (label: string) => string;
|
|
10
|
+
moveDown?: (label: string) => string;
|
|
11
|
+
drag?: (label: string) => string;
|
|
12
|
+
};
|
|
13
|
+
export type NavigationConfigEditorProps = {
|
|
14
|
+
registry: readonly NavigationRegistryItem[];
|
|
15
|
+
value?: TenantNavigationConfig | null;
|
|
16
|
+
onChange: (config: TenantNavigationConfig) => void;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
labels?: NavigationConfigEditorLabels;
|
|
19
|
+
className?: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Controlled tenant navigation editor extracted from the production sibling.
|
|
23
|
+
* Persistence and authorization stay application-owned.
|
|
24
|
+
*/
|
|
25
|
+
export declare function NavigationConfigEditor({ registry, value, onChange, disabled, labels, className, }: NavigationConfigEditorProps): React.JSX.Element;
|
|
26
|
+
//# sourceMappingURL=navigation-config-editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-config-editor.d.ts","sourceRoot":"","sources":["../src/navigation-config-editor.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAO9B,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,EAC5B,MAAM,qBAAqB,CAAA;AAE5B,MAAM,MAAM,4BAA4B,GAAG;IACzC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAClC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IACpC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,SAAS,sBAAsB,EAAE,CAAA;IAC3C,KAAK,CAAC,EAAE,sBAAsB,GAAG,IAAI,CAAA;IACrC,QAAQ,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAA;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,MAAM,CAAC,EAAE,4BAA4B,CAAA;IACrC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAaD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,QAAgB,EAChB,MAAM,EACN,SAAS,GACV,EAAE,2BAA2B,qBAyE7B"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ChevronDown, ChevronUp, GripVertical, LockKeyhole } from 'lucide-react';
|
|
5
|
+
import { Reorder, useDragControls } from 'framer-motion';
|
|
6
|
+
import { Button } from './button.js';
|
|
7
|
+
import { SettingsSection } from './settings-layout.js';
|
|
8
|
+
import { NavIcon } from './sidebar-nav.js';
|
|
9
|
+
import { Switch } from './switch.js';
|
|
10
|
+
import { reconcileNavigationConfig, stampKnownNavigationItems, } from './navigation-config.js';
|
|
11
|
+
const DEFAULT_LABELS = {
|
|
12
|
+
title: 'Main navigation',
|
|
13
|
+
description: 'Choose which destinations appear and drag them into the order your team uses.',
|
|
14
|
+
visible: 'Visible',
|
|
15
|
+
hidden: 'Hidden',
|
|
16
|
+
required: 'Required',
|
|
17
|
+
moveUp: (label) => `Move ${label} up`,
|
|
18
|
+
moveDown: (label) => `Move ${label} down`,
|
|
19
|
+
drag: (label) => `Drag to reorder ${label}`,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Controlled tenant navigation editor extracted from the production sibling.
|
|
23
|
+
* Persistence and authorization stay application-owned.
|
|
24
|
+
*/
|
|
25
|
+
export function NavigationConfigEditor({ registry, value, onChange, disabled = false, labels, className, }) {
|
|
26
|
+
const text = { ...DEFAULT_LABELS, ...labels };
|
|
27
|
+
const config = React.useMemo(() => reconcileNavigationConfig(value, registry), [registry, value]);
|
|
28
|
+
const registryByKey = React.useMemo(() => new Map(registry.map((item) => [item.key, item])), [registry]);
|
|
29
|
+
const emit = React.useCallback((items) => {
|
|
30
|
+
onChange(stampKnownNavigationItems({ version: 1, items }, registry));
|
|
31
|
+
}, [onChange, registry]);
|
|
32
|
+
const move = React.useCallback((index, direction) => {
|
|
33
|
+
const target = index + direction;
|
|
34
|
+
if (target < 0 || target >= config.items.length)
|
|
35
|
+
return;
|
|
36
|
+
const items = [...config.items];
|
|
37
|
+
const [item] = items.splice(index, 1);
|
|
38
|
+
if (!item)
|
|
39
|
+
return;
|
|
40
|
+
items.splice(target, 0, item);
|
|
41
|
+
emit(items);
|
|
42
|
+
}, [config.items, emit]);
|
|
43
|
+
return (_jsx(SettingsSection, { title: text.title, description: text.description, className: className, children: _jsx(Reorder.Group, { axis: "y", values: config.items, onReorder: emit, as: "div", className: "divide-y divide-border", children: config.items.map((item, index) => {
|
|
44
|
+
const registryItem = registryByKey.get(item.key);
|
|
45
|
+
if (!registryItem)
|
|
46
|
+
return null;
|
|
47
|
+
return (_jsx(NavigationConfigRow, { item: item, registryItem: registryItem, first: index === 0, last: index === config.items.length - 1, disabled: disabled, labels: text, onMoveUp: () => move(index, -1), onMoveDown: () => move(index, 1), onVisibilityChange: (visible) => emit(config.items.map((candidate) => candidate.key === item.key
|
|
48
|
+
? { key: candidate.key, ...(visible ? {} : { hidden: true }) }
|
|
49
|
+
: candidate)) }, item.key));
|
|
50
|
+
}) }) }));
|
|
51
|
+
}
|
|
52
|
+
function NavigationConfigRow({ item, registryItem, first, last, disabled, labels, onMoveUp, onMoveDown, onVisibilityChange, }) {
|
|
53
|
+
const dragControls = useDragControls();
|
|
54
|
+
const visible = registryItem.required || !item.hidden;
|
|
55
|
+
return (_jsxs(Reorder.Item, { value: item, dragListener: false, dragControls: dragControls, as: "div", className: "flex items-center gap-3 bg-surface px-4 py-3", children: [_jsx("button", { type: "button", disabled: disabled, "aria-label": labels.drag(registryItem.label), onPointerDown: (event) => dragControls.start(event), className: "cursor-grab touch-none rounded-md p-1 text-fg-subtle transition-colors hover:bg-surface-hover hover:text-fg disabled:cursor-not-allowed disabled:opacity-40 active:cursor-grabbing", children: _jsx(GripVertical, { className: "size-4", "aria-hidden": true }) }), _jsx("span", { className: "grid size-8 shrink-0 place-items-center rounded-lg bg-bg-subtle text-fg-muted", children: _jsx(NavIcon, { iconKey: registryItem.iconKey }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium text-fg", children: registryItem.label }), registryItem.description ? (_jsx("div", { className: "truncate text-xs text-fg-muted", children: registryItem.description })) : null] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", disabled: disabled || first, "aria-label": labels.moveUp(registryItem.label), onClick: onMoveUp, children: _jsx(ChevronUp, { className: "size-4", "aria-hidden": true }) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", disabled: disabled || last, "aria-label": labels.moveDown(registryItem.label), onClick: onMoveDown, children: _jsx(ChevronDown, { className: "size-4", "aria-hidden": true }) })] }), registryItem.required ? (_jsxs("span", { className: "flex min-w-20 items-center justify-end gap-1 text-xs font-medium text-fg-muted", title: labels.required, children: [_jsx(LockKeyhole, { className: "size-3.5", "aria-hidden": true }), labels.required] })) : (_jsxs("label", { className: "flex min-w-20 items-center justify-end gap-2 text-xs font-medium text-fg-muted", children: [_jsx("span", { children: visible ? labels.visible : labels.hidden }), _jsx(Switch, { checked: visible, disabled: disabled, "aria-label": `${registryItem.label}: ${visible ? labels.visible : labels.hidden}`, onChange: (event) => onVisibilityChange(event.target.checked) })] }))] }));
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=navigation-config-editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-config-editor.js","sourceRoot":"","sources":["../src/navigation-config-editor.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport { ChevronDown, ChevronUp, GripVertical, LockKeyhole } from 'lucide-react'\nimport { Reorder, useDragControls } from 'framer-motion'\nimport { Button } from './button'\nimport { SettingsSection } from './settings-layout'\nimport { NavIcon } from './sidebar-nav'\nimport { Switch } from './switch'\nimport {\n reconcileNavigationConfig,\n stampKnownNavigationItems,\n type NavigationItemConfig,\n type NavigationRegistryItem,\n type TenantNavigationConfig,\n} from './navigation-config'\n\nexport type NavigationConfigEditorLabels = {\n title?: string\n description?: string\n visible?: string\n hidden?: string\n required?: string\n moveUp?: (label: string) => string\n moveDown?: (label: string) => string\n drag?: (label: string) => string\n}\n\nexport type NavigationConfigEditorProps = {\n registry: readonly NavigationRegistryItem[]\n value?: TenantNavigationConfig | null\n onChange: (config: TenantNavigationConfig) => void\n disabled?: boolean\n labels?: NavigationConfigEditorLabels\n className?: string\n}\n\nconst DEFAULT_LABELS: Required<NavigationConfigEditorLabels> = {\n title: 'Main navigation',\n description: 'Choose which destinations appear and drag them into the order your team uses.',\n visible: 'Visible',\n hidden: 'Hidden',\n required: 'Required',\n moveUp: (label) => `Move ${label} up`,\n moveDown: (label) => `Move ${label} down`,\n drag: (label) => `Drag to reorder ${label}`,\n}\n\n/**\n * Controlled tenant navigation editor extracted from the production sibling.\n * Persistence and authorization stay application-owned.\n */\nexport function NavigationConfigEditor({\n registry,\n value,\n onChange,\n disabled = false,\n labels,\n className,\n}: NavigationConfigEditorProps) {\n const text = { ...DEFAULT_LABELS, ...labels }\n const config = React.useMemo(\n () => reconcileNavigationConfig(value, registry),\n [registry, value],\n )\n const registryByKey = React.useMemo(\n () => new Map(registry.map((item) => [item.key, item])),\n [registry],\n )\n\n const emit = React.useCallback(\n (items: NavigationItemConfig[]) => {\n onChange(stampKnownNavigationItems({ version: 1, items }, registry))\n },\n [onChange, registry],\n )\n\n const move = React.useCallback(\n (index: number, direction: -1 | 1) => {\n const target = index + direction\n if (target < 0 || target >= config.items.length) return\n const items = [...config.items]\n const [item] = items.splice(index, 1)\n if (!item) return\n items.splice(target, 0, item)\n emit(items)\n },\n [config.items, emit],\n )\n\n return (\n <SettingsSection\n title={text.title}\n description={text.description}\n className={className}\n >\n <Reorder.Group\n axis=\"y\"\n values={config.items}\n onReorder={emit}\n as=\"div\"\n className=\"divide-y divide-border\"\n >\n {config.items.map((item, index) => {\n const registryItem = registryByKey.get(item.key)\n if (!registryItem) return null\n return (\n <NavigationConfigRow\n key={item.key}\n item={item}\n registryItem={registryItem}\n first={index === 0}\n last={index === config.items.length - 1}\n disabled={disabled}\n labels={text}\n onMoveUp={() => move(index, -1)}\n onMoveDown={() => move(index, 1)}\n onVisibilityChange={(visible) =>\n emit(\n config.items.map((candidate) =>\n candidate.key === item.key\n ? { key: candidate.key, ...(visible ? {} : { hidden: true }) }\n : candidate,\n ),\n )\n }\n />\n )\n })}\n </Reorder.Group>\n </SettingsSection>\n )\n}\n\nfunction NavigationConfigRow({\n item,\n registryItem,\n first,\n last,\n disabled,\n labels,\n onMoveUp,\n onMoveDown,\n onVisibilityChange,\n}: {\n item: NavigationItemConfig\n registryItem: NavigationRegistryItem\n first: boolean\n last: boolean\n disabled: boolean\n labels: Required<NavigationConfigEditorLabels>\n onMoveUp: () => void\n onMoveDown: () => void\n onVisibilityChange: (visible: boolean) => void\n}) {\n const dragControls = useDragControls()\n const visible = registryItem.required || !item.hidden\n\n return (\n <Reorder.Item\n value={item}\n dragListener={false}\n dragControls={dragControls}\n as=\"div\"\n className=\"flex items-center gap-3 bg-surface px-4 py-3\"\n >\n <button\n type=\"button\"\n disabled={disabled}\n aria-label={labels.drag(registryItem.label)}\n onPointerDown={(event) => dragControls.start(event)}\n className=\"cursor-grab touch-none rounded-md p-1 text-fg-subtle transition-colors hover:bg-surface-hover hover:text-fg disabled:cursor-not-allowed disabled:opacity-40 active:cursor-grabbing\"\n >\n <GripVertical className=\"size-4\" aria-hidden />\n </button>\n\n <span className=\"grid size-8 shrink-0 place-items-center rounded-lg bg-bg-subtle text-fg-muted\">\n <NavIcon iconKey={registryItem.iconKey} />\n </span>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm font-medium text-fg\">{registryItem.label}</div>\n {registryItem.description ? (\n <div className=\"truncate text-xs text-fg-muted\">{registryItem.description}</div>\n ) : null}\n </div>\n\n <div className=\"flex shrink-0 items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n disabled={disabled || first}\n aria-label={labels.moveUp(registryItem.label)}\n onClick={onMoveUp}\n >\n <ChevronUp className=\"size-4\" aria-hidden />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n disabled={disabled || last}\n aria-label={labels.moveDown(registryItem.label)}\n onClick={onMoveDown}\n >\n <ChevronDown className=\"size-4\" aria-hidden />\n </Button>\n </div>\n\n {registryItem.required ? (\n <span\n className=\"flex min-w-20 items-center justify-end gap-1 text-xs font-medium text-fg-muted\"\n title={labels.required}\n >\n <LockKeyhole className=\"size-3.5\" aria-hidden />\n {labels.required}\n </span>\n ) : (\n <label className=\"flex min-w-20 items-center justify-end gap-2 text-xs font-medium text-fg-muted\">\n <span>{visible ? labels.visible : labels.hidden}</span>\n <Switch\n checked={visible}\n disabled={disabled}\n aria-label={`${registryItem.label}: ${visible ? labels.visible : labels.hidden}`}\n onChange={(event) => onVisibilityChange(event.target.checked)}\n />\n </label>\n )}\n </Reorder.Item>\n )\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-neutral tenant navigation configuration.
|
|
3
|
+
*
|
|
4
|
+
* Applications own their route registry and persistence. AppKit owns the
|
|
5
|
+
* stable-key layout contract, reconciliation when new modules ship, and the
|
|
6
|
+
* visibility/order projection consumed by shells and editors.
|
|
7
|
+
*/
|
|
8
|
+
export type NavigationRegistryItem = {
|
|
9
|
+
/** Stable across releases and persisted by tenants. */
|
|
10
|
+
key: string;
|
|
11
|
+
label: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
iconKey?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Required destinations remain visible. Use this for the product home and
|
|
16
|
+
* the screen that lets an administrator restore navigation.
|
|
17
|
+
*/
|
|
18
|
+
required?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type NavigationItemConfig = {
|
|
21
|
+
key: string;
|
|
22
|
+
hidden?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type TenantNavigationConfig = {
|
|
25
|
+
version: 1;
|
|
26
|
+
items: NavigationItemConfig[];
|
|
27
|
+
/**
|
|
28
|
+
* Registry keys available when the config was saved. A key missing from this
|
|
29
|
+
* stamp is newly shipped and is appended automatically. A known key omitted
|
|
30
|
+
* from `items` remains omitted.
|
|
31
|
+
*/
|
|
32
|
+
knownItemKeys?: string[];
|
|
33
|
+
};
|
|
34
|
+
export type ResolvedNavigationItem<T extends NavigationRegistryItem = NavigationRegistryItem> = T & {
|
|
35
|
+
hidden: boolean;
|
|
36
|
+
};
|
|
37
|
+
export declare function buildDefaultNavigationConfig(registry: readonly NavigationRegistryItem[]): TenantNavigationConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Reconcile saved tenant state with the current application registry.
|
|
40
|
+
*
|
|
41
|
+
* This preserves tenant order, drops stale/duplicate entries, restores
|
|
42
|
+
* required destinations, and appends modules shipped after the last save.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reconcileNavigationConfig(config: TenantNavigationConfig | null | undefined, registry: readonly NavigationRegistryItem[]): TenantNavigationConfig;
|
|
45
|
+
export declare function stampKnownNavigationItems(config: TenantNavigationConfig, registry: readonly NavigationRegistryItem[]): TenantNavigationConfig;
|
|
46
|
+
export declare function resolveNavigationItems<T extends NavigationRegistryItem>(registry: readonly T[], config?: TenantNavigationConfig | null): ResolvedNavigationItem<T>[];
|
|
47
|
+
export declare function isTenantNavigationConfig(value: unknown): value is TenantNavigationConfig;
|
|
48
|
+
//# sourceMappingURL=navigation-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-config.d.ts","sourceRoot":"","sources":["../src/navigation-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,uDAAuD;IACvD,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,CAAC,CAAA;IACV,KAAK,EAAE,oBAAoB,EAAE,CAAA;IAC7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,sBAAsB,CAAC,CAAC,SAAS,sBAAsB,GAAG,sBAAsB,IAC1F,CAAC,GAAG;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,CAAA;AAEzB,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAC1C,sBAAsB,CAKxB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EACjD,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAC1C,sBAAsB,CA8BxB;AAED,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,SAAS,sBAAsB,EAAE,GAC1C,sBAAsB,CAKxB;AAED,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,sBAAsB,EACrE,QAAQ,EAAE,SAAS,CAAC,EAAE,EACtB,MAAM,CAAC,EAAE,sBAAsB,GAAG,IAAI,GACrC,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAO7B;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,sBAAsB,CAkBxF"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export function buildDefaultNavigationConfig(registry) {
|
|
2
|
+
return {
|
|
3
|
+
version: 1,
|
|
4
|
+
items: registry.map((item) => ({ key: item.key })),
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reconcile saved tenant state with the current application registry.
|
|
9
|
+
*
|
|
10
|
+
* This preserves tenant order, drops stale/duplicate entries, restores
|
|
11
|
+
* required destinations, and appends modules shipped after the last save.
|
|
12
|
+
*/
|
|
13
|
+
export function reconcileNavigationConfig(config, registry) {
|
|
14
|
+
if (!config)
|
|
15
|
+
return buildDefaultNavigationConfig(registry);
|
|
16
|
+
const registryByKey = new Map(registry.map((item) => [item.key, item]));
|
|
17
|
+
const known = new Set(config.knownItemKeys ?? []);
|
|
18
|
+
const seen = new Set();
|
|
19
|
+
const items = [];
|
|
20
|
+
for (const item of config.items) {
|
|
21
|
+
const registryItem = registryByKey.get(item.key);
|
|
22
|
+
if (!registryItem || seen.has(item.key))
|
|
23
|
+
continue;
|
|
24
|
+
seen.add(item.key);
|
|
25
|
+
items.push({
|
|
26
|
+
key: item.key,
|
|
27
|
+
...(item.hidden && !registryItem.required ? { hidden: true } : {}),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
for (const registryItem of registry) {
|
|
31
|
+
if (seen.has(registryItem.key))
|
|
32
|
+
continue;
|
|
33
|
+
if (!registryItem.required && known.has(registryItem.key))
|
|
34
|
+
continue;
|
|
35
|
+
seen.add(registryItem.key);
|
|
36
|
+
items.push({ key: registryItem.key });
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
version: 1,
|
|
40
|
+
items,
|
|
41
|
+
...(config.knownItemKeys ? { knownItemKeys: [...config.knownItemKeys] } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function stampKnownNavigationItems(config, registry) {
|
|
45
|
+
return {
|
|
46
|
+
...reconcileNavigationConfig(config, registry),
|
|
47
|
+
knownItemKeys: registry.map((item) => item.key),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function resolveNavigationItems(registry, config) {
|
|
51
|
+
const registryByKey = new Map(registry.map((item) => [item.key, item]));
|
|
52
|
+
return reconcileNavigationConfig(config, registry).items.flatMap((saved) => {
|
|
53
|
+
const item = registryByKey.get(saved.key);
|
|
54
|
+
if (!item)
|
|
55
|
+
return [];
|
|
56
|
+
return [{ ...item, hidden: Boolean(saved.hidden && !item.required) }];
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function isTenantNavigationConfig(value) {
|
|
60
|
+
if (!value || typeof value !== 'object')
|
|
61
|
+
return false;
|
|
62
|
+
const config = value;
|
|
63
|
+
if (config.version !== 1 || !Array.isArray(config.items))
|
|
64
|
+
return false;
|
|
65
|
+
if (config.knownItemKeys !== undefined &&
|
|
66
|
+
(!Array.isArray(config.knownItemKeys) ||
|
|
67
|
+
config.knownItemKeys.some((key) => typeof key !== 'string'))) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return config.items.every((item) => Boolean(item) &&
|
|
71
|
+
typeof item === 'object' &&
|
|
72
|
+
typeof item.key === 'string' &&
|
|
73
|
+
(item.hidden === undefined || typeof item.hidden === 'boolean'));
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=navigation-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-config.js","sourceRoot":"","sources":["../src/navigation-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Framework-neutral tenant navigation configuration.\n *\n * Applications own their route registry and persistence. AppKit owns the\n * stable-key layout contract, reconciliation when new modules ship, and the\n * visibility/order projection consumed by shells and editors.\n */\nexport type NavigationRegistryItem = {\n /** Stable across releases and persisted by tenants. */\n key: string\n label: string\n description?: string\n iconKey?: string\n /**\n * Required destinations remain visible. Use this for the product home and\n * the screen that lets an administrator restore navigation.\n */\n required?: boolean\n}\n\nexport type NavigationItemConfig = {\n key: string\n hidden?: boolean\n}\n\nexport type TenantNavigationConfig = {\n version: 1\n items: NavigationItemConfig[]\n /**\n * Registry keys available when the config was saved. A key missing from this\n * stamp is newly shipped and is appended automatically. A known key omitted\n * from `items` remains omitted.\n */\n knownItemKeys?: string[]\n}\n\nexport type ResolvedNavigationItem<T extends NavigationRegistryItem = NavigationRegistryItem> =\n T & { hidden: boolean }\n\nexport function buildDefaultNavigationConfig(\n registry: readonly NavigationRegistryItem[],\n): TenantNavigationConfig {\n return {\n version: 1,\n items: registry.map((item) => ({ key: item.key })),\n }\n}\n\n/**\n * Reconcile saved tenant state with the current application registry.\n *\n * This preserves tenant order, drops stale/duplicate entries, restores\n * required destinations, and appends modules shipped after the last save.\n */\nexport function reconcileNavigationConfig(\n config: TenantNavigationConfig | null | undefined,\n registry: readonly NavigationRegistryItem[],\n): TenantNavigationConfig {\n if (!config) return buildDefaultNavigationConfig(registry)\n\n const registryByKey = new Map(registry.map((item) => [item.key, item]))\n const known = new Set(config.knownItemKeys ?? [])\n const seen = new Set<string>()\n const items: NavigationItemConfig[] = []\n\n for (const item of config.items) {\n const registryItem = registryByKey.get(item.key)\n if (!registryItem || seen.has(item.key)) continue\n seen.add(item.key)\n items.push({\n key: item.key,\n ...(item.hidden && !registryItem.required ? { hidden: true } : {}),\n })\n }\n\n for (const registryItem of registry) {\n if (seen.has(registryItem.key)) continue\n if (!registryItem.required && known.has(registryItem.key)) continue\n seen.add(registryItem.key)\n items.push({ key: registryItem.key })\n }\n\n return {\n version: 1,\n items,\n ...(config.knownItemKeys ? { knownItemKeys: [...config.knownItemKeys] } : {}),\n }\n}\n\nexport function stampKnownNavigationItems(\n config: TenantNavigationConfig,\n registry: readonly NavigationRegistryItem[],\n): TenantNavigationConfig {\n return {\n ...reconcileNavigationConfig(config, registry),\n knownItemKeys: registry.map((item) => item.key),\n }\n}\n\nexport function resolveNavigationItems<T extends NavigationRegistryItem>(\n registry: readonly T[],\n config?: TenantNavigationConfig | null,\n): ResolvedNavigationItem<T>[] {\n const registryByKey = new Map(registry.map((item) => [item.key, item]))\n return reconcileNavigationConfig(config, registry).items.flatMap((saved) => {\n const item = registryByKey.get(saved.key)\n if (!item) return []\n return [{ ...item, hidden: Boolean(saved.hidden && !item.required) }]\n })\n}\n\nexport function isTenantNavigationConfig(value: unknown): value is TenantNavigationConfig {\n if (!value || typeof value !== 'object') return false\n const config = value as Partial<TenantNavigationConfig>\n if (config.version !== 1 || !Array.isArray(config.items)) return false\n if (\n config.knownItemKeys !== undefined &&\n (!Array.isArray(config.knownItemKeys) ||\n config.knownItemKeys.some((key) => typeof key !== 'string'))\n ) {\n return false\n }\n return config.items.every(\n (item) =>\n Boolean(item) &&\n typeof item === 'object' &&\n typeof item.key === 'string' &&\n (item.hidden === undefined || typeof item.hidden === 'boolean'),\n )\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { AppShellNavigationMode } from './app-shell.js';
|
|
3
|
+
type NavigationModeContextValue = {
|
|
4
|
+
mode: AppShellNavigationMode;
|
|
5
|
+
setMode: (mode: AppShellNavigationMode) => void;
|
|
6
|
+
mounted: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function NavigationModeProvider({ children, defaultMode, cookieName, onChange, }: {
|
|
9
|
+
children: React.ReactNode;
|
|
10
|
+
defaultMode?: AppShellNavigationMode;
|
|
11
|
+
cookieName?: string;
|
|
12
|
+
onChange?: (mode: AppShellNavigationMode) => void | Promise<void>;
|
|
13
|
+
}): React.JSX.Element;
|
|
14
|
+
export declare function useNavigationMode(): NavigationModeContextValue;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=navigation-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-mode.d.ts","sourceRoot":"","sources":["../src/navigation-mode.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAEzD,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,sBAAsB,CAAA;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,sBAAsB,KAAK,IAAI,CAAA;IAC/C,OAAO,EAAE,OAAO,CAAA;CACjB,CAAA;AAID,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,WAAsB,EACtB,UAAqC,EACrC,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClE,qBA2BA;AAED,wBAAgB,iBAAiB,IAAI,0BAA0B,CAI9D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
const NavigationModeContext = React.createContext(null);
|
|
5
|
+
export function NavigationModeProvider({ children, defaultMode = 'topbar', cookieName = 'appkit-navigation-mode', onChange, }) {
|
|
6
|
+
const [mode, setModeState] = React.useState(defaultMode);
|
|
7
|
+
const [mounted, setMounted] = React.useState(false);
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const stored = document.cookie
|
|
10
|
+
.split('; ')
|
|
11
|
+
.find((entry) => entry.startsWith(`${encodeURIComponent(cookieName)}=`))
|
|
12
|
+
?.slice(encodeURIComponent(cookieName).length + 1);
|
|
13
|
+
if (stored === 'topbar' || stored === 'sidebar')
|
|
14
|
+
setModeState(stored);
|
|
15
|
+
setMounted(true);
|
|
16
|
+
}, [cookieName]);
|
|
17
|
+
const setMode = React.useCallback((next) => {
|
|
18
|
+
setModeState(next);
|
|
19
|
+
document.cookie = `${encodeURIComponent(cookieName)}=${next};path=/;max-age=31536000;samesite=lax`;
|
|
20
|
+
void onChange?.(next);
|
|
21
|
+
}, [cookieName, onChange]);
|
|
22
|
+
return (_jsx(NavigationModeContext.Provider, { value: { mode, setMode, mounted }, children: children }));
|
|
23
|
+
}
|
|
24
|
+
export function useNavigationMode() {
|
|
25
|
+
const context = React.useContext(NavigationModeContext);
|
|
26
|
+
if (!context)
|
|
27
|
+
throw new Error('useNavigationMode must be used within NavigationModeProvider');
|
|
28
|
+
return context;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=navigation-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-mode.js","sourceRoot":"","sources":["../src/navigation-mode.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAS9B,MAAM,qBAAqB,GAAG,KAAK,CAAC,aAAa,CAAoC,IAAI,CAAC,CAAA;AAE1F,MAAM,UAAU,sBAAsB,CAAC,EACrC,QAAQ,EACR,WAAW,GAAG,QAAQ,EACtB,UAAU,GAAG,wBAAwB,EACrC,QAAQ,GAMT;IACC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAyB,WAAW,CAAC,CAAA;IAChF,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEnD,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM;aAC3B,KAAK,CAAC,IAAI,CAAC;aACX,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,kBAAkB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YACxE,EAAE,KAAK,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACpD,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,SAAS;YAAE,YAAY,CAAC,MAAM,CAAC,CAAA;QACrE,UAAU,CAAC,IAAI,CAAC,CAAA;IAClB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEhB,MAAM,OAAO,GAAG,KAAK,CAAC,WAAW,CAC/B,CAAC,IAA4B,EAAE,EAAE;QAC/B,YAAY,CAAC,IAAI,CAAC,CAAA;QAClB,QAAQ,CAAC,MAAM,GAAG,GAAG,kBAAkB,CAAC,UAAU,CAAC,IAAI,IAAI,uCAAuC,CAAA;QAClG,KAAK,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC,EACD,CAAC,UAAU,EAAE,QAAQ,CAAC,CACvB,CAAA;IAED,OAAO,CACL,KAAC,qBAAqB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,YAC9D,QAAQ,GACsB,CAClC,CAAA;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAA;IACvD,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;IAC7F,OAAO,OAAO,CAAA;AAChB,CAAC","sourcesContent":["'use client'\n\nimport * as React from 'react'\nimport type { AppShellNavigationMode } from './app-shell'\n\ntype NavigationModeContextValue = {\n mode: AppShellNavigationMode\n setMode: (mode: AppShellNavigationMode) => void\n mounted: boolean\n}\n\nconst NavigationModeContext = React.createContext<NavigationModeContextValue | null>(null)\n\nexport function NavigationModeProvider({\n children,\n defaultMode = 'topbar',\n cookieName = 'appkit-navigation-mode',\n onChange,\n}: {\n children: React.ReactNode\n defaultMode?: AppShellNavigationMode\n cookieName?: string\n onChange?: (mode: AppShellNavigationMode) => void | Promise<void>\n}) {\n const [mode, setModeState] = React.useState<AppShellNavigationMode>(defaultMode)\n const [mounted, setMounted] = React.useState(false)\n\n React.useEffect(() => {\n const stored = document.cookie\n .split('; ')\n .find((entry) => entry.startsWith(`${encodeURIComponent(cookieName)}=`))\n ?.slice(encodeURIComponent(cookieName).length + 1)\n if (stored === 'topbar' || stored === 'sidebar') setModeState(stored)\n setMounted(true)\n }, [cookieName])\n\n const setMode = React.useCallback(\n (next: AppShellNavigationMode) => {\n setModeState(next)\n document.cookie = `${encodeURIComponent(cookieName)}=${next};path=/;max-age=31536000;samesite=lax`\n void onChange?.(next)\n },\n [cookieName, onChange],\n )\n\n return (\n <NavigationModeContext.Provider value={{ mode, setMode, mounted }}>\n {children}\n </NavigationModeContext.Provider>\n )\n}\n\nexport function useNavigationMode(): NavigationModeContextValue {\n const context = React.useContext(NavigationModeContext)\n if (!context) throw new Error('useNavigationMode must be used within NavigationModeProvider')\n return context\n}\n"]}
|