@alfakit/react 0.1.12 → 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/CHANGELOG.md +12 -0
- package/dist/components/cms/blocks.d.ts.map +1 -1
- package/dist/components/cms/blocks.js +2 -2
- package/dist/components/cms/blocks.js.map +1 -1
- package/dist/components/cms/head.d.ts +6 -4
- package/dist/components/cms/head.d.ts.map +1 -1
- package/dist/components/cms/head.js +10 -4
- package/dist/components/cms/head.js.map +1 -1
- package/dist/components/cms/page.d.ts.map +1 -1
- package/dist/components/cms/page.js +16 -3
- package/dist/components/cms/page.js.map +1 -1
- package/dist/components/fields/file-upload.js +4 -4
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/rich-editor.js +4 -2
- package/dist/components/fields/rich-editor.js.map +1 -1
- package/dist/components/fields/slider.js +1 -1
- package/dist/components/fields/slider.js.map +1 -1
- package/dist/components/helpdesk/chat-widget.js +1 -1
- package/dist/components/layout/section.js +1 -1
- package/dist/components/layout/tabs.js +1 -1
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +27 -5
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/global-search.js +2 -2
- package/dist/components/panel/global-search.js.map +1 -1
- package/dist/components/panel/shortcut-keys.js +1 -1
- package/dist/components/panel/shortcut-keys.js.map +1 -1
- package/dist/components/table/list-tabs.js +1 -1
- package/dist/components/table/list-tabs.js.map +1 -1
- package/dist/components/table/table-views.js +1 -1
- package/dist/components/table/table-views.js.map +1 -1
- package/dist/components/versions/record-versions.js +1 -1
- package/dist/context.d.ts +8 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +27 -2
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +6 -3
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/index.js +1 -1
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +13 -3
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/lib/discovery.d.ts +4 -0
- package/dist/lib/discovery.d.ts.map +1 -1
- package/dist/lib/discovery.js +17 -7
- package/dist/lib/discovery.js.map +1 -1
- package/dist/lib/icons.d.ts +2 -1
- package/dist/lib/icons.d.ts.map +1 -1
- package/dist/lib/icons.js +55 -19
- package/dist/lib/icons.js.map +1 -1
- package/dist/pages/page.d.ts +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +47 -10
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +2 -1
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/alfakit-toaster.js +2 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone/runtime.js +1 -11
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone/toaster-claims.js +16 -0
- package/dist/standalone/toaster-claims.js.map +1 -0
- package/dist/starter-kit/widgets-loaded.js +63 -6
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +1 -1
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/themes.css +92 -0
- package/package.json +2 -2
- package/src/components/cms/blocks.tsx +7 -1
- package/src/components/cms/head.tsx +19 -4
- package/src/components/cms/page.tsx +17 -2
- package/src/components/fields/file-upload.tsx +5 -4
- package/src/components/fields/rich-editor.tsx +7 -2
- package/src/components/fields/slider.tsx +3 -1
- package/src/components/panel/app-sidebar.tsx +28 -7
- package/src/components/panel/global-search.tsx +4 -1
- package/src/components/panel/shortcut-keys.tsx +1 -1
- package/src/components/table/list-tabs.tsx +3 -1
- package/src/components/table/table-views.tsx +3 -1
- package/src/context.tsx +46 -1
- package/src/create-alfakit.tsx +13 -2
- package/src/layouts/app-layout.tsx +16 -3
- package/src/lib/discovery.tsx +23 -8
- package/src/lib/icons.tsx +90 -24
- package/src/pages/page.tsx +85 -27
- package/src/standalone/runtime.ts +2 -14
- package/src/standalone/toaster-claims.ts +16 -0
- package/src/starter-kit/widgets-loaded.tsx +84 -1
- package/src/starter-kit/widgets-triggers.tsx +4 -1
|
@@ -22,7 +22,7 @@ function ShortcutKeys({ binding, className }) {
|
|
|
22
22
|
children: t("shortcuts.then", {}, "then")
|
|
23
23
|
}), /* @__PURE__ */ jsx("kbd", {
|
|
24
24
|
dir: "ltr",
|
|
25
|
-
className: "bg-muted text-
|
|
25
|
+
className: "bg-muted text-foreground/70 rounded border px-1.5 font-mono text-[10px] leading-5",
|
|
26
26
|
children: step
|
|
27
27
|
})]
|
|
28
28
|
}, index))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shortcut-keys.js","names":[],"sources":["../../../src/components/panel/shortcut-keys.tsx"],"sourcesContent":["import { shortcutSteps } from '@alfakit/core'\nimport { useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { cn } from '../../lib/utils'\n\n/**\n * A key binding as `<kbd>` keys (`G` then `D`, `⌘K`). The keys depend on the visitor's\n * platform (⌘ or Ctrl), so they render after hydration.\n */\nexport function ShortcutKeys({ binding, className }: { binding: string; className?: string }) {\n const t = useT()\n const hydrated = useHydrated()\n\n if (!hydrated) return null\n\n const steps = shortcutSteps(binding)\n\n return (\n <span data-slot=\"shortcut-keys\" className={cn('inline-flex items-center gap-1', className)}>\n {steps.map((step, index) => (\n <span key={index} className=\"inline-flex items-center gap-1\">\n {index > 0 && (\n <span className=\"text-muted-foreground text-[10px]\">\n {t('shortcuts.then', {}, 'then')}\n </span>\n )}\n <kbd\n dir=\"ltr\"\n className=\"bg-muted text-
|
|
1
|
+
{"version":3,"file":"shortcut-keys.js","names":[],"sources":["../../../src/components/panel/shortcut-keys.tsx"],"sourcesContent":["import { shortcutSteps } from '@alfakit/core'\nimport { useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { cn } from '../../lib/utils'\n\n/**\n * A key binding as `<kbd>` keys (`G` then `D`, `⌘K`). The keys depend on the visitor's\n * platform (⌘ or Ctrl), so they render after hydration.\n */\nexport function ShortcutKeys({ binding, className }: { binding: string; className?: string }) {\n const t = useT()\n const hydrated = useHydrated()\n\n if (!hydrated) return null\n\n const steps = shortcutSteps(binding)\n\n return (\n <span data-slot=\"shortcut-keys\" className={cn('inline-flex items-center gap-1', className)}>\n {steps.map((step, index) => (\n <span key={index} className=\"inline-flex items-center gap-1\">\n {index > 0 && (\n <span className=\"text-muted-foreground text-[10px]\">\n {t('shortcuts.then', {}, 'then')}\n </span>\n )}\n <kbd\n dir=\"ltr\"\n className=\"bg-muted text-foreground/70 rounded border px-1.5 font-mono text-[10px] leading-5\"\n >\n {step}\n </kbd>\n </span>\n ))}\n </span>\n )\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,aAAa,EAAE,SAAS,aAAsD;CAC5F,MAAM,IAAI,KAAK;CAGf,IAAI,CAFa,YAEL,GAAG,OAAO;CAEtB,MAAM,QAAQ,cAAc,OAAO;CAEnC,OACE,oBAAC,QAAD;EAAM,aAAU;EAAgB,WAAW,GAAG,kCAAkC,SAAS;EACtF,UAAA,MAAM,KAAK,MAAM,UAChB,qBAAC,QAAD;GAAkB,WAAU;GAA5B,UAAA,CACG,QAAQ,KACP,oBAAC,QAAD;IAAM,WAAU;IACb,UAAA,EAAE,kBAAkB,CAAC,GAAG,MAAM;GAC3B,CAAA,GAER,oBAAC,OAAD;IACE,KAAI;IACJ,WAAU;IAET,UAAA;GACE,CAAA,CACD;EAZK,GAAA,KAYL,CACP;CACG,CAAA;AAEV"}
|
|
@@ -47,7 +47,7 @@ function ListTabs({ tabs, activeTab, tabsQueryKey, label, idBase }) {
|
|
|
47
47
|
const active = tab.key === activeTab;
|
|
48
48
|
const ids = idBase ? listTabIds(idBase, tab.key) : null;
|
|
49
49
|
return /* @__PURE__ */ jsxs("button", {
|
|
50
|
-
...extraProps(tab.extraAttributes, cn("inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none", "focus-visible:border-ring focus-visible:ring-ring/50", active ? "bg-background text-foreground shadow-sm" : "hover:text-foreground")),
|
|
50
|
+
...extraProps(tab.extraAttributes, cn("inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none", "focus-visible:border-ring focus-visible:ring-ring/50", active ? "bg-background text-foreground shadow-sm" : "text-foreground/70 hover:text-foreground")),
|
|
51
51
|
ref: (element) => {
|
|
52
52
|
buttons.current[index] = element;
|
|
53
53
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-tabs.js","names":[],"sources":["../../../src/components/table/list-tabs.tsx"],"sourcesContent":["import type { ListTabSchema, ListTabsProps } from '@alfakit/core'\nimport { listTabIds, listTabUrl, tabKeyTarget, withDeferredTabBadges } from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport type { KeyboardEvent, ReactNode } from 'react'\nimport { useMemo, useRef } from 'react'\nimport { useAlfakitProps, useDirection, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { badgeColorClasses, colorClass } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Slot } from '../panel/slot'\n\n/**\n * Tabs above a resource's list table (`ListRecords::getTabs()`). Choosing one reloads the\n * page with `?activeTab=` (the server narrows the query); filters and search are kept.\n * Keyboard: one tab stop (the active tab), arrow keys / Home / End move focus (RTL-aware),\n * Enter / Space select. With `idBase` the tabs control the panel `ListTabsPanel` renders.\n */\nexport function ListTabs({\n tabs,\n activeTab,\n tabsQueryKey,\n label,\n idBase,\n}: Required<Pick<ListTabsProps, 'tabs'>> & ListTabsProps & { label?: string; idBase?: string }) {\n const t = useT()\n const direction = useDirection()\n const buttons = useRef<Array<HTMLButtonElement | null>>([])\n // `deferBadge()` tabs get their badge from a deferred prop loaded after the page.\n const deferred = useAlfakitProps().alfakitTabBadges\n const shown = useMemo(() => withDeferredTabBadges(tabs, deferred), [tabs, deferred])\n const select = (tab: ListTabSchema) => {\n if (tab.key === activeTab || typeof window === 'undefined') return\n\n router.visit(listTabUrl(window.location.href, tab.key, tabsQueryKey), {\n preserveState: true,\n preserveScroll: true,\n replace: true,\n })\n }\n\n const focusable = shown.some((tab) => tab.key === activeTab) ? activeTab : shown[0]?.key\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const target = tabKeyTarget(event.key, index, shown.length, direction)\n\n if (target === null) return\n\n event.preventDefault()\n buttons.current[target]?.focus()\n }\n\n return (\n <div\n data-slot=\"list-tabs\"\n role=\"tablist\"\n aria-label={label}\n className=\"bg-muted text-muted-foreground relative inline-flex h-9 w-fit max-w-full items-center overflow-x-auto rounded-lg p-[3px]\"\n >\n <Slot name=\"resource.list-records.tabs.start\" />\n {shown.map((tab, index) => {\n const active = tab.key === activeTab\n const ids = idBase ? listTabIds(idBase, tab.key) : null\n\n return (\n <button\n key={tab.key}\n {...extraProps(\n tab.extraAttributes,\n cn(\n 'inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none',\n 'focus-visible:border-ring focus-visible:ring-ring/50',\n active
|
|
1
|
+
{"version":3,"file":"list-tabs.js","names":[],"sources":["../../../src/components/table/list-tabs.tsx"],"sourcesContent":["import type { ListTabSchema, ListTabsProps } from '@alfakit/core'\nimport { listTabIds, listTabUrl, tabKeyTarget, withDeferredTabBadges } from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport type { KeyboardEvent, ReactNode } from 'react'\nimport { useMemo, useRef } from 'react'\nimport { useAlfakitProps, useDirection, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { badgeColorClasses, colorClass } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Slot } from '../panel/slot'\n\n/**\n * Tabs above a resource's list table (`ListRecords::getTabs()`). Choosing one reloads the\n * page with `?activeTab=` (the server narrows the query); filters and search are kept.\n * Keyboard: one tab stop (the active tab), arrow keys / Home / End move focus (RTL-aware),\n * Enter / Space select. With `idBase` the tabs control the panel `ListTabsPanel` renders.\n */\nexport function ListTabs({\n tabs,\n activeTab,\n tabsQueryKey,\n label,\n idBase,\n}: Required<Pick<ListTabsProps, 'tabs'>> & ListTabsProps & { label?: string; idBase?: string }) {\n const t = useT()\n const direction = useDirection()\n const buttons = useRef<Array<HTMLButtonElement | null>>([])\n // `deferBadge()` tabs get their badge from a deferred prop loaded after the page.\n const deferred = useAlfakitProps().alfakitTabBadges\n const shown = useMemo(() => withDeferredTabBadges(tabs, deferred), [tabs, deferred])\n const select = (tab: ListTabSchema) => {\n if (tab.key === activeTab || typeof window === 'undefined') return\n\n router.visit(listTabUrl(window.location.href, tab.key, tabsQueryKey), {\n preserveState: true,\n preserveScroll: true,\n replace: true,\n })\n }\n\n const focusable = shown.some((tab) => tab.key === activeTab) ? activeTab : shown[0]?.key\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const target = tabKeyTarget(event.key, index, shown.length, direction)\n\n if (target === null) return\n\n event.preventDefault()\n buttons.current[target]?.focus()\n }\n\n return (\n <div\n data-slot=\"list-tabs\"\n role=\"tablist\"\n aria-label={label}\n className=\"bg-muted text-muted-foreground relative inline-flex h-9 w-fit max-w-full items-center overflow-x-auto rounded-lg p-[3px]\"\n >\n <Slot name=\"resource.list-records.tabs.start\" />\n {shown.map((tab, index) => {\n const active = tab.key === activeTab\n const ids = idBase ? listTabIds(idBase, tab.key) : null\n\n return (\n <button\n key={tab.key}\n {...extraProps(\n tab.extraAttributes,\n cn(\n 'inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none',\n 'focus-visible:border-ring focus-visible:ring-ring/50',\n active\n ? 'bg-background text-foreground shadow-sm'\n : 'text-foreground/70 hover:text-foreground',\n ),\n )}\n ref={(element) => {\n buttons.current[index] = element\n }}\n type=\"button\"\n role=\"tab\"\n id={ids?.tab}\n aria-controls={ids?.panel}\n aria-selected={active}\n tabIndex={tab.key === focusable ? 0 : -1}\n data-state={active ? 'active' : 'inactive'}\n onClick={() => select(tab)}\n onKeyDown={(event) => onKeyDown(event, index)}\n >\n {tab.icon && tab.iconPosition !== 'after' && (\n <Icon name={tab.icon} className=\"size-4\" />\n )}\n {tab.label}\n {tab.icon && tab.iconPosition === 'after' && (\n <Icon name={tab.icon} className=\"size-4\" />\n )}\n {tab.badge !== undefined && tab.badge !== null && (\n <span\n data-slot=\"badge\"\n className={cn(\n 'ms-1 rounded-md px-1.5 py-0 text-xs font-medium',\n colorClass(badgeColorClasses, tab.badgeColor, 'gray'),\n )}\n >\n {tab.badge}\n </span>\n )}\n {tab.badgePending && (\n <span\n data-slot=\"badge\"\n aria-busy=\"true\"\n aria-label={t('loading', {}, 'Loading')}\n className=\"bg-muted-foreground/20 ms-1 inline-block h-4 w-5 animate-pulse rounded-md\"\n />\n )}\n </button>\n )\n })}\n <Slot name=\"resource.list-records.tabs.end\" />\n </div>\n )\n}\n\n/** The table under list tabs, as the tab panel of the active tab. */\nexport function ListTabsPanel({\n idBase,\n activeTab,\n children,\n}: {\n idBase: string\n activeTab?: string\n children: ReactNode\n}) {\n const ids = listTabIds(idBase, activeTab ?? '')\n\n return (\n <div\n data-slot=\"list-tabs-panel\"\n role=\"tabpanel\"\n id={ids.panel}\n aria-labelledby={activeTab ? ids.tab : undefined}\n >\n {children}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,SAAgB,SAAS,EACvB,MACA,WACA,cACA,OACA,UAC8F;CAC9F,MAAM,IAAI,KAAK;CACf,MAAM,YAAY,aAAa;CAC/B,MAAM,UAAU,OAAwC,CAAC,CAAC;CAE1D,MAAM,WAAW,gBAAgB,CAAC,CAAC;CACnC,MAAM,QAAQ,cAAc,sBAAsB,MAAM,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC;CACnF,MAAM,UAAU,QAAuB;EACrC,IAAI,IAAI,QAAQ,aAAa,OAAO,WAAW,aAAa;EAE5D,OAAO,MAAM,WAAW,OAAO,SAAS,MAAM,IAAI,KAAK,YAAY,GAAG;GACpE,eAAe;GACf,gBAAgB;GAChB,SAAS;EACX,CAAC;CACH;CAEA,MAAM,YAAY,MAAM,MAAM,QAAQ,IAAI,QAAQ,SAAS,IAAI,YAAY,MAAM,EAAE,EAAE;CACrF,MAAM,aAAa,OAAyC,UAAkB;EAC5E,MAAM,SAAS,aAAa,MAAM,KAAK,OAAO,MAAM,QAAQ,SAAS;EAErE,IAAI,WAAW,MAAM;EAErB,MAAM,eAAe;EACrB,QAAQ,QAAQ,OAAO,EAAE,MAAM;CACjC;CAEA,OACE,qBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,cAAY;EACZ,WAAU;EAJZ,UAAA;GAME,oBAAC,MAAD,EAAM,MAAK,mCAAoC,CAAA;GAC9C,MAAM,KAAK,KAAK,UAAU;IACzB,MAAM,SAAS,IAAI,QAAQ;IAC3B,MAAM,MAAM,SAAS,WAAW,QAAQ,IAAI,GAAG,IAAI;IAEnD,OACE,qBAAC,UAAD;KAEE,GAAI,WACF,IAAI,iBACJ,GACE,wPACA,wDACA,SACI,4CACA,0CACN,CACF;KACA,MAAM,YAAY;MAChB,QAAQ,QAAQ,SAAS;KAC3B;KACA,MAAK;KACL,MAAK;KACL,IAAI,KAAK;KACT,iBAAe,KAAK;KACpB,iBAAe;KACf,UAAU,IAAI,QAAQ,YAAY,IAAI;KACtC,cAAY,SAAS,WAAW;KAChC,eAAe,OAAO,GAAG;KACzB,YAAY,UAAU,UAAU,OAAO,KAAK;KAvB9C,UAAA;MAyBG,IAAI,QAAQ,IAAI,iBAAiB,WAChC,oBAAC,MAAD;OAAM,MAAM,IAAI;OAAM,WAAU;MAAU,CAAA;MAE3C,IAAI;MACJ,IAAI,QAAQ,IAAI,iBAAiB,WAChC,oBAAC,MAAD;OAAM,MAAM,IAAI;OAAM,WAAU;MAAU,CAAA;MAE3C,IAAI,UAAU,KAAA,KAAa,IAAI,UAAU,QACxC,oBAAC,QAAD;OACE,aAAU;OACV,WAAW,GACT,mDACA,WAAW,mBAAmB,IAAI,YAAY,MAAM,CACtD;OAEC,UAAA,IAAI;MACD,CAAA;MAEP,IAAI,gBACH,oBAAC,QAAD;OACE,aAAU;OACV,aAAU;OACV,cAAY,EAAE,WAAW,CAAC,GAAG,SAAS;OACtC,WAAU;MACX,CAAA;KAEG;IAlDD,GAAA,IAAI,GAkDH;GAEZ,CAAC;GACD,oBAAC,MAAD,EAAM,MAAK,iCAAkC,CAAA;EAC1C;;AAET;;AAGA,SAAgB,cAAc,EAC5B,QACA,WACA,YAKC;CACD,MAAM,MAAM,WAAW,QAAQ,aAAa,EAAE;CAE9C,OACE,oBAAC,OAAD;EACE,aAAU;EACV,MAAK;EACL,IAAI,IAAI;EACR,mBAAiB,YAAY,IAAI,MAAM,KAAA;EAEtC;CACE,CAAA;AAET"}
|
|
@@ -182,7 +182,7 @@ function TableViews({ table, token, component }) {
|
|
|
182
182
|
"aria-selected": selected,
|
|
183
183
|
tabIndex: index === (focusable === -1 ? 0 : focusable) ? 0 : -1,
|
|
184
184
|
"data-state": selected ? "active" : "inactive",
|
|
185
|
-
className: cn("inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none", "focus-visible:border-ring focus-visible:ring-ring/50", selected ? "bg-background text-foreground shadow-sm" : "hover:text-foreground"),
|
|
185
|
+
className: cn("inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none", "focus-visible:border-ring focus-visible:ring-ring/50", selected ? "bg-background text-foreground shadow-sm" : "text-foreground/70 hover:text-foreground"),
|
|
186
186
|
onClick: () => !selected && visitTableView(table, item?.key ?? null),
|
|
187
187
|
onKeyDown: (event) => onTabKeyDown(event, index),
|
|
188
188
|
children: item ? viewLabel(item) : allLabel
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table-views.js","names":[],"sources":["../../../src/components/table/table-views.tsx"],"sourcesContent":["import type { TableSchema, TableViewItemSchema, TableViewOperation } from '@alfakit/core'\nimport {\n activeTableView,\n groupTableViews,\n inertiaReload,\n requestTableView,\n tabKeyTarget,\n tableViewError,\n tableViewFollowUp,\n visitTableView,\n} from '@alfakit/core'\nimport { BookmarkIcon, ChevronDownIcon, RotateCcwIcon, StarIcon, UsersIcon } from 'lucide-react'\nimport { useRef, useState, type KeyboardEvent } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\n\ntype NameDialog = { mode: 'create' } | { mode: 'rename'; view: TableViewItemSchema } | null\n\n/**\n * Saved views above a table (`Table::savedViews()` / `views()`): the views as tabs (or a\n * dropdown) and a menu to save the current state as a new view, update, rename, share\n * or delete the active one, pick a default and reset changes. Each view is a URL\n * (`?view=slug`), so it can be shared.\n */\nexport function TableViews({\n table,\n token,\n component,\n}: {\n table: TableSchema\n token: string\n component: string\n}) {\n const t = useT()\n const panel = usePanel()\n const direction = useDirection()\n const views = table.views\n const [dialog, setDialog] = useState<NameDialog>(null)\n const [confirmDelete, setConfirmDelete] = useState<TableViewItemSchema | null>(null)\n const [name, setName] = useState('')\n const [shared, setShared] = useState(false)\n const [error, setError] = useState<string | undefined>()\n const [busy, setBusy] = useState(false)\n const tabs = useRef<Array<HTMLButtonElement | null>>([])\n\n if (!views) return null\n\n const active = activeTableView(views)\n const groups = groupTableViews(views.items)\n const endpoint = panel.endpoints.tableViews\n\n const run = async (operation: TableViewOperation, success?: string): Promise<boolean> => {\n if (!endpoint) return false\n\n setBusy(true)\n setError(undefined)\n\n try {\n const response = await requestTableView(endpoint, token, component, operation)\n const next = tableViewFollowUp(table, operation, response)\n\n if ('open' in next) {\n visitTableView(table, next.open)\n } else {\n inertiaReload()\n }\n\n if (success) {\n showToast({\n id: 'alfakit-table-view',\n title: success,\n status: 'success',\n format: 'alfakit',\n })\n }\n\n return true\n } catch (failure) {\n const message = tableViewError(failure)\n\n if (dialog) {\n setError(message ?? t('table.views.failed', {}, \"Couldn't save the view\"))\n } else {\n showToast({\n id: 'alfakit-table-view-failed',\n title: t('table.views.failed', {}, \"Couldn't save the view\"),\n body: message,\n status: 'danger',\n format: 'alfakit',\n })\n }\n\n return false\n } finally {\n setBusy(false)\n }\n }\n\n const openDialog = (next: NameDialog) => {\n setName(next?.mode === 'rename' ? next.view.label : '')\n setShared(false)\n setError(undefined)\n setDialog(next)\n }\n\n const submitName = async () => {\n if (!dialog) return\n\n const ok =\n dialog.mode === 'create'\n ? await run({ operation: 'create', name, shared }, t('table.views.saved', {}, 'View saved'))\n : await run({ operation: 'rename', view: dialog.view.key, name })\n\n if (ok) setDialog(null)\n }\n\n const allLabel = t('table.views.all', {}, 'All')\n const items: Array<TableViewItemSchema | null> = [null, ...views.items]\n const activeKey = views.active ?? null\n const focusable = items.findIndex((item) => (item?.key ?? null) === activeKey)\n\n const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const target = tabKeyTarget(event.key, index, items.length, direction)\n\n if (target === null) return\n\n event.preventDefault()\n tabs.current[target]?.focus()\n }\n\n const viewLabel = (item: TableViewItemSchema) => (\n <>\n {item.icon && <Icon name={item.icon} className=\"size-4\" />}\n <span className=\"truncate\">{item.label}</span>\n {item.type === 'shared' && (\n <Icon\n name=\"alfakit::tables.views.shared\"\n fallback={UsersIcon}\n className=\"text-muted-foreground size-3.5\"\n aria-label={t('table.views.shared_badge', {}, 'Shared')}\n />\n )}\n {item.default && (\n <Icon\n name=\"alfakit::tables.views.default\"\n fallback={StarIcon}\n className=\"text-muted-foreground size-3.5\"\n aria-label={t('table.views.default_badge', {}, 'Default')}\n />\n )}\n </>\n )\n\n const switcher =\n views.layout === 'dropdown' ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" data-slot=\"table-views-trigger\">\n <Icon name=\"alfakit::tables.views.trigger\" fallback={BookmarkIcon} />\n <span className=\"max-w-48 truncate\">{active?.label ?? allLabel}</span>\n <ChevronDownIcon className=\"opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-56\">\n <DropdownMenuRadioGroup\n value={activeKey ?? ''}\n onValueChange={(key) => visitTableView(table, key === '' ? null : key)}\n >\n <DropdownMenuRadioItem value=\"\">{allLabel}</DropdownMenuRadioItem>\n {(\n [\n ['preset', t('table.views.presets', {}, 'Views')],\n ['shared', t('table.views.shared', {}, 'Team views')],\n ['personal', t('table.views.personal', {}, 'My views')],\n ] as const\n ).map(([type, label]) =>\n groups[type].length > 0 ? (\n <div key={type} role=\"group\" aria-label={label}>\n <DropdownMenuSeparator />\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n {label}\n </DropdownMenuLabel>\n {groups[type].map((item) => (\n <DropdownMenuRadioItem key={item.key} value={item.key} className=\"gap-2\">\n {viewLabel(item)}\n </DropdownMenuRadioItem>\n ))}\n </div>\n ) : null,\n )}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <div\n role=\"tablist\"\n aria-label={t('table.views.label', {}, 'Views')}\n data-slot=\"table-views-tabs\"\n className=\"bg-muted text-muted-foreground inline-flex h-9 w-fit max-w-full items-center overflow-x-auto rounded-lg p-[3px]\"\n >\n {items.map((item, index) => {\n const selected = (item?.key ?? null) === activeKey\n\n return (\n <button\n key={item?.key ?? ''}\n ref={(element) => {\n tabs.current[index] = element\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n tabIndex={index === (focusable === -1 ? 0 : focusable) ? 0 : -1}\n data-state={selected ? 'active' : 'inactive'}\n className={cn(\n 'inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none',\n 'focus-visible:border-ring focus-visible:ring-ring/50',\n selected ? 'bg-background text-foreground shadow-sm' : 'hover:text-foreground',\n )}\n onClick={() => !selected && visitTableView(table, item?.key ?? null)}\n onKeyDown={(event) => onTabKeyDown(event, index)}\n >\n {item ? viewLabel(item) : allLabel}\n </button>\n )\n })}\n </div>\n )\n\n const canUpdate = Boolean(active?.editable && views.canSave)\n const hasMenu = Boolean(views.canSave || views.canSetDefault || views.modified)\n\n return (\n <div data-slot=\"table-views\" className=\"flex flex-wrap items-center gap-2\">\n <Slot name=\"table.views.before\" scope={component} />\n {switcher}\n {views.modified && (\n <span data-slot=\"table-views-modified\" className=\"text-muted-foreground text-xs italic\">\n {t('table.views.modified', {}, 'Modified')}\n </span>\n )}\n {hasMenu && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={busy}\n aria-label={t('table.views.actions', {}, 'View options')}\n >\n <ChevronDownIcon />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-52\">\n {views.canSave && (\n <DropdownMenuItem onSelect={() => openDialog({ mode: 'create' })}>\n {t('table.views.save_new', {}, 'Save as new view')}\n </DropdownMenuItem>\n )}\n {canUpdate && active && views.modified && (\n <DropdownMenuItem\n onSelect={() =>\n void run(\n { operation: 'update', view: active.key },\n t('table.views.saved', {}, 'View saved'),\n )\n }\n >\n {t('table.views.update', {}, 'Update view')}\n </DropdownMenuItem>\n )}\n {views.modified && (\n <DropdownMenuItem onSelect={() => visitTableView(table, activeKey)}>\n <RotateCcwIcon />\n {t('table.views.reset', {}, 'Reset changes')}\n </DropdownMenuItem>\n )}\n {canUpdate && active && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => openDialog({ mode: 'rename', view: active })}>\n {t('table.views.rename', {}, 'Rename view')}\n </DropdownMenuItem>\n {views.canShare && (\n <DropdownMenuItem\n onSelect={() =>\n void run({\n operation: 'share',\n view: active.key,\n shared: active.type !== 'shared',\n })\n }\n >\n {active.type === 'shared'\n ? t('table.views.unshare', {}, 'Stop sharing')\n : t('table.views.share', {}, 'Share with team')}\n </DropdownMenuItem>\n )}\n <DropdownMenuItem variant=\"destructive\" onSelect={() => setConfirmDelete(active)}>\n {t('table.views.delete', {}, 'Delete view')}\n </DropdownMenuItem>\n </>\n )}\n {views.canSetDefault && (\n <>\n <DropdownMenuSeparator />\n {(active?.default ?? false) || (!active && views.userDefault === '') ? (\n <DropdownMenuItem onSelect={() => void run({ operation: 'default', view: null })}>\n {t('table.views.clear_default', {}, 'Clear my default')}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem\n onSelect={() => void run({ operation: 'default', view: active?.key ?? '' })}\n >\n {t('table.views.set_default', {}, 'Set as my default')}\n </DropdownMenuItem>\n )}\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <Slot name=\"table.views.after\" scope={component} />\n\n <Dialog open={dialog !== null} onOpenChange={(open) => !open && setDialog(null)}>\n <DialogContent className=\"sm:max-w-md\">\n <form\n data-slot=\"table-view-form\"\n className=\"flex flex-col gap-4\"\n onSubmit={(event) => {\n event.preventDefault()\n void submitName()\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'rename'\n ? t('table.views.rename', {}, 'Rename view')\n : t('table.views.save_new', {}, 'Save as new view')}\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {t(\n 'table.views.name_help',\n {},\n 'Saves the filters, search, sort, columns and grouping.',\n )}\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-2\">\n <Label htmlFor={`${component}-view-name`}>{t('table.views.name', {}, 'Name')}</Label>\n <Input\n id={`${component}-view-name`}\n value={name}\n maxLength={120}\n autoFocus\n required\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? `${component}-view-name-error` : undefined}\n onChange={(event) => setName(event.target.value)}\n />\n {error && (\n <p id={`${component}-view-name-error`} className=\"text-destructive text-sm\">\n {error}\n </p>\n )}\n </div>\n {dialog?.mode === 'create' && views.canShare && (\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id={`${component}-view-shared`}\n checked={shared}\n onCheckedChange={(checked) => setShared(checked === true)}\n />\n <Label htmlFor={`${component}-view-shared`}>\n {t('table.views.share', {}, 'Share with team')}\n </Label>\n </div>\n )}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setDialog(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={busy || name.trim() === ''}>\n {t('save', {}, 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n\n <Dialog\n open={confirmDelete !== null}\n onOpenChange={(open) => !open && setConfirmDelete(null)}\n >\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('table.views.delete', {}, 'Delete view')}</DialogTitle>\n <DialogDescription>\n {t(\n 'table.views.delete_confirm',\n { name: confirmDelete?.label ?? '' },\n 'Delete the view \":name\"?',\n )}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setConfirmDelete(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"destructive\"\n disabled={busy}\n onClick={async () => {\n if (!confirmDelete) return\n\n if (\n await run(\n { operation: 'delete', view: confirmDelete.key },\n t('table.views.deleted', {}, 'View deleted'),\n )\n ) {\n setConfirmDelete(null)\n }\n }}\n >\n {t('table.views.delete', {}, 'Delete view')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"table-views.js","names":[],"sources":["../../../src/components/table/table-views.tsx"],"sourcesContent":["import type { TableSchema, TableViewItemSchema, TableViewOperation } from '@alfakit/core'\nimport {\n activeTableView,\n groupTableViews,\n inertiaReload,\n requestTableView,\n tabKeyTarget,\n tableViewError,\n tableViewFollowUp,\n visitTableView,\n} from '@alfakit/core'\nimport { BookmarkIcon, ChevronDownIcon, RotateCcwIcon, StarIcon, UsersIcon } from 'lucide-react'\nimport { useRef, useState, type KeyboardEvent } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\n\ntype NameDialog = { mode: 'create' } | { mode: 'rename'; view: TableViewItemSchema } | null\n\n/**\n * Saved views above a table (`Table::savedViews()` / `views()`): the views as tabs (or a\n * dropdown) and a menu to save the current state as a new view, update, rename, share\n * or delete the active one, pick a default and reset changes. Each view is a URL\n * (`?view=slug`), so it can be shared.\n */\nexport function TableViews({\n table,\n token,\n component,\n}: {\n table: TableSchema\n token: string\n component: string\n}) {\n const t = useT()\n const panel = usePanel()\n const direction = useDirection()\n const views = table.views\n const [dialog, setDialog] = useState<NameDialog>(null)\n const [confirmDelete, setConfirmDelete] = useState<TableViewItemSchema | null>(null)\n const [name, setName] = useState('')\n const [shared, setShared] = useState(false)\n const [error, setError] = useState<string | undefined>()\n const [busy, setBusy] = useState(false)\n const tabs = useRef<Array<HTMLButtonElement | null>>([])\n\n if (!views) return null\n\n const active = activeTableView(views)\n const groups = groupTableViews(views.items)\n const endpoint = panel.endpoints.tableViews\n\n const run = async (operation: TableViewOperation, success?: string): Promise<boolean> => {\n if (!endpoint) return false\n\n setBusy(true)\n setError(undefined)\n\n try {\n const response = await requestTableView(endpoint, token, component, operation)\n const next = tableViewFollowUp(table, operation, response)\n\n if ('open' in next) {\n visitTableView(table, next.open)\n } else {\n inertiaReload()\n }\n\n if (success) {\n showToast({\n id: 'alfakit-table-view',\n title: success,\n status: 'success',\n format: 'alfakit',\n })\n }\n\n return true\n } catch (failure) {\n const message = tableViewError(failure)\n\n if (dialog) {\n setError(message ?? t('table.views.failed', {}, \"Couldn't save the view\"))\n } else {\n showToast({\n id: 'alfakit-table-view-failed',\n title: t('table.views.failed', {}, \"Couldn't save the view\"),\n body: message,\n status: 'danger',\n format: 'alfakit',\n })\n }\n\n return false\n } finally {\n setBusy(false)\n }\n }\n\n const openDialog = (next: NameDialog) => {\n setName(next?.mode === 'rename' ? next.view.label : '')\n setShared(false)\n setError(undefined)\n setDialog(next)\n }\n\n const submitName = async () => {\n if (!dialog) return\n\n const ok =\n dialog.mode === 'create'\n ? await run({ operation: 'create', name, shared }, t('table.views.saved', {}, 'View saved'))\n : await run({ operation: 'rename', view: dialog.view.key, name })\n\n if (ok) setDialog(null)\n }\n\n const allLabel = t('table.views.all', {}, 'All')\n const items: Array<TableViewItemSchema | null> = [null, ...views.items]\n const activeKey = views.active ?? null\n const focusable = items.findIndex((item) => (item?.key ?? null) === activeKey)\n\n const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {\n const target = tabKeyTarget(event.key, index, items.length, direction)\n\n if (target === null) return\n\n event.preventDefault()\n tabs.current[target]?.focus()\n }\n\n const viewLabel = (item: TableViewItemSchema) => (\n <>\n {item.icon && <Icon name={item.icon} className=\"size-4\" />}\n <span className=\"truncate\">{item.label}</span>\n {item.type === 'shared' && (\n <Icon\n name=\"alfakit::tables.views.shared\"\n fallback={UsersIcon}\n className=\"text-muted-foreground size-3.5\"\n aria-label={t('table.views.shared_badge', {}, 'Shared')}\n />\n )}\n {item.default && (\n <Icon\n name=\"alfakit::tables.views.default\"\n fallback={StarIcon}\n className=\"text-muted-foreground size-3.5\"\n aria-label={t('table.views.default_badge', {}, 'Default')}\n />\n )}\n </>\n )\n\n const switcher =\n views.layout === 'dropdown' ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" data-slot=\"table-views-trigger\">\n <Icon name=\"alfakit::tables.views.trigger\" fallback={BookmarkIcon} />\n <span className=\"max-w-48 truncate\">{active?.label ?? allLabel}</span>\n <ChevronDownIcon className=\"opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-56\">\n <DropdownMenuRadioGroup\n value={activeKey ?? ''}\n onValueChange={(key) => visitTableView(table, key === '' ? null : key)}\n >\n <DropdownMenuRadioItem value=\"\">{allLabel}</DropdownMenuRadioItem>\n {(\n [\n ['preset', t('table.views.presets', {}, 'Views')],\n ['shared', t('table.views.shared', {}, 'Team views')],\n ['personal', t('table.views.personal', {}, 'My views')],\n ] as const\n ).map(([type, label]) =>\n groups[type].length > 0 ? (\n <div key={type} role=\"group\" aria-label={label}>\n <DropdownMenuSeparator />\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">\n {label}\n </DropdownMenuLabel>\n {groups[type].map((item) => (\n <DropdownMenuRadioItem key={item.key} value={item.key} className=\"gap-2\">\n {viewLabel(item)}\n </DropdownMenuRadioItem>\n ))}\n </div>\n ) : null,\n )}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : (\n <div\n role=\"tablist\"\n aria-label={t('table.views.label', {}, 'Views')}\n data-slot=\"table-views-tabs\"\n className=\"bg-muted text-muted-foreground inline-flex h-9 w-fit max-w-full items-center overflow-x-auto rounded-lg p-[3px]\"\n >\n {items.map((item, index) => {\n const selected = (item?.key ?? null) === activeKey\n\n return (\n <button\n key={item?.key ?? ''}\n ref={(element) => {\n tabs.current[index] = element\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={selected}\n tabIndex={index === (focusable === -1 ? 0 : focusable) ? 0 : -1}\n data-state={selected ? 'active' : 'inactive'}\n className={cn(\n 'inline-flex h-[calc(100%-1px)] shrink-0 items-center justify-center gap-1.5 rounded-md border border-transparent px-2.5 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-none',\n 'focus-visible:border-ring focus-visible:ring-ring/50',\n selected\n ? 'bg-background text-foreground shadow-sm'\n : 'text-foreground/70 hover:text-foreground',\n )}\n onClick={() => !selected && visitTableView(table, item?.key ?? null)}\n onKeyDown={(event) => onTabKeyDown(event, index)}\n >\n {item ? viewLabel(item) : allLabel}\n </button>\n )\n })}\n </div>\n )\n\n const canUpdate = Boolean(active?.editable && views.canSave)\n const hasMenu = Boolean(views.canSave || views.canSetDefault || views.modified)\n\n return (\n <div data-slot=\"table-views\" className=\"flex flex-wrap items-center gap-2\">\n <Slot name=\"table.views.before\" scope={component} />\n {switcher}\n {views.modified && (\n <span data-slot=\"table-views-modified\" className=\"text-muted-foreground text-xs italic\">\n {t('table.views.modified', {}, 'Modified')}\n </span>\n )}\n {hasMenu && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={busy}\n aria-label={t('table.views.actions', {}, 'View options')}\n >\n <ChevronDownIcon />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-52\">\n {views.canSave && (\n <DropdownMenuItem onSelect={() => openDialog({ mode: 'create' })}>\n {t('table.views.save_new', {}, 'Save as new view')}\n </DropdownMenuItem>\n )}\n {canUpdate && active && views.modified && (\n <DropdownMenuItem\n onSelect={() =>\n void run(\n { operation: 'update', view: active.key },\n t('table.views.saved', {}, 'View saved'),\n )\n }\n >\n {t('table.views.update', {}, 'Update view')}\n </DropdownMenuItem>\n )}\n {views.modified && (\n <DropdownMenuItem onSelect={() => visitTableView(table, activeKey)}>\n <RotateCcwIcon />\n {t('table.views.reset', {}, 'Reset changes')}\n </DropdownMenuItem>\n )}\n {canUpdate && active && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => openDialog({ mode: 'rename', view: active })}>\n {t('table.views.rename', {}, 'Rename view')}\n </DropdownMenuItem>\n {views.canShare && (\n <DropdownMenuItem\n onSelect={() =>\n void run({\n operation: 'share',\n view: active.key,\n shared: active.type !== 'shared',\n })\n }\n >\n {active.type === 'shared'\n ? t('table.views.unshare', {}, 'Stop sharing')\n : t('table.views.share', {}, 'Share with team')}\n </DropdownMenuItem>\n )}\n <DropdownMenuItem variant=\"destructive\" onSelect={() => setConfirmDelete(active)}>\n {t('table.views.delete', {}, 'Delete view')}\n </DropdownMenuItem>\n </>\n )}\n {views.canSetDefault && (\n <>\n <DropdownMenuSeparator />\n {(active?.default ?? false) || (!active && views.userDefault === '') ? (\n <DropdownMenuItem onSelect={() => void run({ operation: 'default', view: null })}>\n {t('table.views.clear_default', {}, 'Clear my default')}\n </DropdownMenuItem>\n ) : (\n <DropdownMenuItem\n onSelect={() => void run({ operation: 'default', view: active?.key ?? '' })}\n >\n {t('table.views.set_default', {}, 'Set as my default')}\n </DropdownMenuItem>\n )}\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n <Slot name=\"table.views.after\" scope={component} />\n\n <Dialog open={dialog !== null} onOpenChange={(open) => !open && setDialog(null)}>\n <DialogContent className=\"sm:max-w-md\">\n <form\n data-slot=\"table-view-form\"\n className=\"flex flex-col gap-4\"\n onSubmit={(event) => {\n event.preventDefault()\n void submitName()\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'rename'\n ? t('table.views.rename', {}, 'Rename view')\n : t('table.views.save_new', {}, 'Save as new view')}\n </DialogTitle>\n <DialogDescription className=\"sr-only\">\n {t(\n 'table.views.name_help',\n {},\n 'Saves the filters, search, sort, columns and grouping.',\n )}\n </DialogDescription>\n </DialogHeader>\n <div className=\"flex flex-col gap-2\">\n <Label htmlFor={`${component}-view-name`}>{t('table.views.name', {}, 'Name')}</Label>\n <Input\n id={`${component}-view-name`}\n value={name}\n maxLength={120}\n autoFocus\n required\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? `${component}-view-name-error` : undefined}\n onChange={(event) => setName(event.target.value)}\n />\n {error && (\n <p id={`${component}-view-name-error`} className=\"text-destructive text-sm\">\n {error}\n </p>\n )}\n </div>\n {dialog?.mode === 'create' && views.canShare && (\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id={`${component}-view-shared`}\n checked={shared}\n onCheckedChange={(checked) => setShared(checked === true)}\n />\n <Label htmlFor={`${component}-view-shared`}>\n {t('table.views.share', {}, 'Share with team')}\n </Label>\n </div>\n )}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setDialog(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={busy || name.trim() === ''}>\n {t('save', {}, 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n\n <Dialog\n open={confirmDelete !== null}\n onOpenChange={(open) => !open && setConfirmDelete(null)}\n >\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('table.views.delete', {}, 'Delete view')}</DialogTitle>\n <DialogDescription>\n {t(\n 'table.views.delete_confirm',\n { name: confirmDelete?.label ?? '' },\n 'Delete the view \":name\"?',\n )}\n </DialogDescription>\n </DialogHeader>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setConfirmDelete(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"destructive\"\n disabled={busy}\n onClick={async () => {\n if (!confirmDelete) return\n\n if (\n await run(\n { operation: 'delete', view: confirmDelete.key },\n t('table.views.deleted', {}, 'View deleted'),\n )\n ) {\n setConfirmDelete(null)\n }\n }}\n >\n {t('table.views.delete', {}, 'Delete view')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useActionRunner, useFormatter, usePageSchema, useT } from "../../context.js";
|
|
2
|
+
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
2
3
|
import { Icon } from "../../lib/icons.js";
|
|
3
4
|
import { Badge } from "../ui/badge.js";
|
|
4
5
|
import { Button } from "../ui/button.js";
|
|
5
|
-
import { useHydrated } from "../../hooks/use-hydrated.js";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { ArrowRightIcon } from "lucide-react";
|
|
8
8
|
//#region src/components/versions/record-versions.tsx
|
package/dist/context.d.ts
CHANGED
|
@@ -18,6 +18,14 @@ export interface AlfakitContextValue {
|
|
|
18
18
|
* change). Panels leave it out (a partial reload); components outside panels fetch theirs.
|
|
19
19
|
*/
|
|
20
20
|
refresh?: (targets: string[]) => Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Whether every page is rendered only after the split built-ins and discovered components
|
|
23
|
+
* it names have loaded, on the server and before hydration (`createAlfakit()` resolves
|
|
24
|
+
* pages that way). Without it (starter kits, standalone components) those components show
|
|
25
|
+
* after hydration: a long-lived SSR server keeps code earlier requests loaded, a fresh
|
|
26
|
+
* browser has none of it, so rendering them early would make the markups differ.
|
|
27
|
+
*/
|
|
28
|
+
preloadsPageComponents?: boolean;
|
|
21
29
|
}
|
|
22
30
|
/**
|
|
23
31
|
* Outside panels there is no shared `alfakit` prop and no `page` prop: `<AlfakitComponent>`
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","names":[],"sources":["../src/context.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"context.d.ts","names":[],"sources":["../src/context.tsx"],"mappings":";;;YAyBY,eAAe;iBAEV;EACf,UAAU,kBAAkB;EAC5B,OAAO,eAAe;EACtB,SAAS;EACT,YAAY;;;;;;EAMZ,QAAQ,gBAAgB;;;;;EAKxB,WAAW,sBAAsB;;;;;;;;EAQjC;;;;;;iBAqDe;EACf,OAAO;EACP,MAAM;;wBAKQ,qBACd,OACA;EAEA,OAAO;EACP,UAAU;oBACX,IAAA;wBAIe,iBAAiB;wBAIjB,kBACd,OACA;EAEA,OAAO;EACP,UAAU;oBACX,IAAA;wBAIe,cAAc;;wBAmBd,sBAAsB;;wBAKtB,YAAY;wBAQZ,iBAAiB;;wBAQjB,oBAAoB,YAAY;wBAShC,mBAAmB;;wBAWnB,QAAQ;;wBAOR,aAAa;;wBAOb,mBAAmB;wBAInB;;wBAQA,gBAAgB;wBAehB,mBAAmB;wBAInB,eAAe,kBAAkB"}
|
package/dist/context.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LocaleFormatter, pollComponent, resolvePanelNavigation, translator } from "@alfakit/core";
|
|
2
2
|
import { usePage } from "@inertiajs/react";
|
|
3
|
-
import { createContext, useContext, useMemo } from "react";
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo, useRef } from "react";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
//#region src/context.tsx
|
|
6
6
|
const AlfakitContext = createContext(null);
|
|
@@ -17,6 +17,31 @@ function useOptionalPageProps() {
|
|
|
17
17
|
return NO_PROPS;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* The Inertia page URL, or undefined outside an Inertia app. Persistent chrome (the panel
|
|
22
|
+
* layout stays mounted across visits) closes its phone sheets when it changes.
|
|
23
|
+
*/
|
|
24
|
+
function useOptionalPageUrl() {
|
|
25
|
+
try {
|
|
26
|
+
return usePage().url;
|
|
27
|
+
} catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Calls `close` when the user moves to another page. The panel layout persists across
|
|
33
|
+
* visits, so menus and sheets that used to be reset by remounting close here instead.
|
|
34
|
+
* Partial reloads (deferred props, polling) keep the URL and leave them open.
|
|
35
|
+
*/
|
|
36
|
+
function useCloseOnVisit(close) {
|
|
37
|
+
const url = useOptionalPageUrl();
|
|
38
|
+
const previous = useRef(url);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (previous.current === url) return;
|
|
41
|
+
previous.current = url;
|
|
42
|
+
close();
|
|
43
|
+
}, [url, close]);
|
|
44
|
+
}
|
|
20
45
|
const StandaloneContext = createContext(null);
|
|
21
46
|
function StandaloneProvider({ value, children }) {
|
|
22
47
|
return /* @__PURE__ */ jsx(StandaloneContext.Provider, {
|
|
@@ -114,6 +139,6 @@ function useRegistry() {
|
|
|
114
139
|
return useAlfakit().registry;
|
|
115
140
|
}
|
|
116
141
|
//#endregion
|
|
117
|
-
export { AlfakitProvider, StandaloneProvider, useActionRunner, useAlfakit, useAlfakitProps, useChoice, useDirection, useFormatter, useLocalization, useOptionalAlfakit, useOptionalRegistry, usePageSchema, usePanel, usePollComponent, useRefreshComponents, useRegistry, useStandalone, useT };
|
|
142
|
+
export { AlfakitProvider, StandaloneProvider, useActionRunner, useAlfakit, useAlfakitProps, useChoice, useCloseOnVisit, useDirection, useFormatter, useLocalization, useOptionalAlfakit, useOptionalPageUrl, useOptionalRegistry, usePageSchema, usePanel, usePollComponent, useRefreshComponents, useRegistry, useStandalone, useT };
|
|
118
143
|
|
|
119
144
|
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":["globalTranslator"],"sources":["../src/context.tsx"],"sourcesContent":["import {\n translator as globalTranslator,\n LocaleFormatter,\n pollComponent,\n resolvePanelNavigation,\n type ActionRunner,\n type AlfakitPageProps,\n type ComponentRegistry,\n type LocalizationSchema,\n type PageSchema,\n type PanelSchema,\n type Translator,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { createContext, useContext, useMemo, type ComponentType, type ReactNode } from 'react'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyComponent = ComponentType<any>\n\nexport interface AlfakitContextValue {\n registry: ComponentRegistry<AnyComponent>\n pages: Record<string, AnyComponent>\n actions: ActionRunner\n translator: Translator\n /**\n * Polls a component (`table`, `widget:<id>`) and returns an unsubscribe. Panels use the\n * shared `pollComponent()` (an Inertia partial reload); components outside panels fetch\n * their own schemas by token.\n */\n poll?: (target: string, interval: string | number | null | undefined) => () => void\n /**\n * Fetches fresh schemas of components now (`live()` tables and widgets after a record\n * change). Panels leave it out (a partial reload); components outside panels fetch theirs.\n */\n refresh?: (targets: string[]) => Promise<void>\n}\n\nconst AlfakitContext = createContext<AlfakitContextValue | null>(null)\n\nconst NO_PROPS: AlfakitPageProps = {} as AlfakitPageProps\n\n/**\n * The Inertia page props, or an empty object outside an Inertia app (component tests,\n * Storybook, a plain server render of an `<AlfakitComponent>`). `usePage()` only reads a\n * context before throwing, so the hook order is the same on both paths.\n */\nfunction useOptionalPageProps(): AlfakitPageProps {\n try {\n return usePage<AlfakitPageProps>().props\n } catch {\n return NO_PROPS\n }\n}\n\n/**\n * Outside panels there is no shared `alfakit` prop and no `page` prop: `<AlfakitComponent>`\n * provides both from its payload, and the hooks below read them first.\n */\nexport interface StandaloneContextValue {\n panel: PanelSchema\n page: PageSchema\n}\n\nconst StandaloneContext = createContext<StandaloneContextValue | null>(null)\n\nexport function StandaloneProvider({\n value,\n children,\n}: {\n value: StandaloneContextValue\n children: ReactNode\n}) {\n return <StandaloneContext.Provider value={value}>{children}</StandaloneContext.Provider>\n}\n\nexport function useStandalone(): StandaloneContextValue | null {\n return useContext(StandaloneContext)\n}\n\nexport function AlfakitProvider({\n value,\n children,\n}: {\n value: AlfakitContextValue\n children: ReactNode\n}) {\n return <AlfakitContext.Provider value={value}>{children}</AlfakitContext.Provider>\n}\n\nexport function useAlfakit(): AlfakitContextValue {\n const context = useContext(AlfakitContext)\n\n if (!context) {\n throw new Error('Alfakit components must be rendered inside createAlfakit().')\n }\n\n return context\n}\n\n/**\n * The component registry when rendered inside `createAlfakit()`, otherwise null (primitives\n * rendered on their own in tests or Storybook).\n */\nexport function useOptionalRegistry(): ComponentRegistry<AnyComponent> | null {\n return useContext(AlfakitContext)?.registry ?? null\n}\n\n/** The context the component renders in, or null outside `createAlfakit()` and `<AlfakitComponent>`. */\nexport function useOptionalAlfakit(): AlfakitContextValue | null {\n return useContext(AlfakitContext)\n}\n\n/** The shared `alfakit` prop (panel, navigation, user, endpoints…). */\nexport function usePanel(): PanelSchema {\n const standalone = useContext(StandaloneContext)\n const props = useOptionalPageProps()\n\n // The navigation structure is a once prop; its active items and badges are merged in here.\n return resolvePanelNavigation(standalone?.panel ?? props.alfakit, props.alfakitNavigationBadges)\n}\n\nexport function usePageSchema(): PageSchema | undefined {\n const standalone = useContext(StandaloneContext)\n const page = useOptionalPageProps().page\n\n return standalone?.page ?? page\n}\n\n/** Poll a component (`table`, `widget:<id>`); the returned function stops it. */\nexport function usePollComponent(): NonNullable<AlfakitContextValue['poll']> {\n return useContext(AlfakitContext)?.poll ?? pollComponent\n}\n\n/** How `live()` components refresh: their own fetch outside panels, else a partial reload. */\nexport function useRefreshComponents(): AlfakitContextValue['refresh'] {\n return useContext(AlfakitContext)?.refresh\n}\n\nexport function useAlfakitProps(): AlfakitPageProps {\n return useOptionalPageProps()\n}\n\nfunction useTranslator(): Translator {\n // Primitives may render outside createAlfakit() (tests, Storybook); fall back to the\n // shared translator so they still show their English defaults.\n return useContext(AlfakitContext)?.translator ?? globalTranslator\n}\n\n/** `t('table.showing', { first: 1, last: 10, total: 42 }, 'Fallback')` */\nexport function useT(): Translator['t'] {\n const translator = useTranslator()\n\n return translator.t.bind(translator)\n}\n\n/** Plural-aware lines: `choice('table.selected', 3)` (Laravel `trans_choice` syntax). */\nexport function useChoice(): Translator['choice'] {\n const translator = useTranslator()\n\n return translator.choice.bind(translator)\n}\n\n/** The current language, direction, currency, timezone and language list. */\nexport function useLocalization(): LocalizationSchema {\n return usePanel().localization\n}\n\nexport function useDirection(): 'ltr' | 'rtl' {\n const standalone = useContext(StandaloneContext)\n const panel = useOptionalPageProps().alfakit\n\n return (standalone?.panel ?? panel)?.localization?.direction ?? 'ltr'\n}\n\n/** `Intl` formatting in the user's locale: `format.money(12.5)`, `format.relative(date)`. */\nexport function useFormatter(): LocaleFormatter {\n const standalone = useContext(StandaloneContext)\n const panel = useOptionalPageProps().alfakit\n const localization = (standalone?.panel ?? panel)?.localization\n\n const tag = localization?.tag\n const currency = localization?.currency\n const timezone = localization?.timezone\n\n return useMemo(\n () => new LocaleFormatter(tag ?? 'en', currency ?? 'USD', timezone),\n [tag, currency, timezone],\n )\n}\n\nexport function useActionRunner(): ActionRunner {\n return useAlfakit().actions\n}\n\nexport function useRegistry(): ComponentRegistry<AnyComponent> {\n return useAlfakit().registry\n}\n"],"mappings":";;;;;AAqCA,MAAM,iBAAiB,cAA0C,IAAI;AAErE,MAAM,WAA6B,CAAC;;;;;;AAOpC,SAAS,uBAAyC;CAChD,IAAI;EACF,OAAO,QAA0B,CAAC,CAAC;CACrC,QAAQ;EACN,OAAO;CACT;AACF;AAWA,MAAM,oBAAoB,cAA6C,IAAI;AAE3E,SAAgB,mBAAmB,EACjC,OACA,YAIC;CACD,OAAO,oBAAC,kBAAkB,UAAnB;EAAmC;EAAQ;CAAqC,CAAA;AACzF;AAEA,SAAgB,gBAA+C;CAC7D,OAAO,WAAW,iBAAiB;AACrC;AAEA,SAAgB,gBAAgB,EAC9B,OACA,YAIC;CACD,OAAO,oBAAC,eAAe,UAAhB;EAAgC;EAAQ;CAAkC,CAAA;AACnF;AAEA,SAAgB,aAAkC;CAChD,MAAM,UAAU,WAAW,cAAc;CAEzC,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,6DAA6D;CAG/E,OAAO;AACT;;;;;AAMA,SAAgB,sBAA8D;CAC5E,OAAO,WAAW,cAAc,CAAC,EAAE,YAAY;AACjD;;AAGA,SAAgB,qBAAiD;CAC/D,OAAO,WAAW,cAAc;AAClC;;AAGA,SAAgB,WAAwB;CACtC,MAAM,aAAa,WAAW,iBAAiB;CAC/C,MAAM,QAAQ,qBAAqB;CAGnC,OAAO,uBAAuB,YAAY,SAAS,MAAM,SAAS,MAAM,uBAAuB;AACjG;AAEA,SAAgB,gBAAwC;CACtD,MAAM,aAAa,WAAW,iBAAiB;CAC/C,MAAM,OAAO,qBAAqB,CAAC,CAAC;CAEpC,OAAO,YAAY,QAAQ;AAC7B;;AAGA,SAAgB,mBAA6D;CAC3E,OAAO,WAAW,cAAc,CAAC,EAAE,QAAQ;AAC7C;;AAGA,SAAgB,uBAAuD;CACrE,OAAO,WAAW,cAAc,CAAC,EAAE;AACrC;AAEA,SAAgB,kBAAoC;CAClD,OAAO,qBAAqB;AAC9B;AAEA,SAAS,gBAA4B;CAGnC,OAAO,WAAW,cAAc,CAAC,EAAE,cAAcA;AACnD;;AAGA,SAAgB,OAAwB;CACtC,MAAM,aAAa,cAAc;CAEjC,OAAO,WAAW,EAAE,KAAK,UAAU;AACrC;;AAGA,SAAgB,YAAkC;CAChD,MAAM,aAAa,cAAc;CAEjC,OAAO,WAAW,OAAO,KAAK,UAAU;AAC1C;;AAGA,SAAgB,kBAAsC;CACpD,OAAO,SAAS,CAAC,CAAC;AACpB;AAEA,SAAgB,eAA8B;CAC5C,MAAM,aAAa,WAAW,iBAAiB;CAC/C,MAAM,QAAQ,qBAAqB,CAAC,CAAC;CAErC,QAAQ,YAAY,SAAS,MAAA,EAAQ,cAAc,aAAa;AAClE;;AAGA,SAAgB,eAAgC;CAC9C,MAAM,aAAa,WAAW,iBAAiB;CAC/C,MAAM,QAAQ,qBAAqB,CAAC,CAAC;CACrC,MAAM,gBAAgB,YAAY,SAAS,MAAA,EAAQ;CAEnD,MAAM,MAAM,cAAc;CAC1B,MAAM,WAAW,cAAc;CAC/B,MAAM,WAAW,cAAc;CAE/B,OAAO,cACC,IAAI,gBAAgB,OAAO,MAAM,YAAY,OAAO,QAAQ,GAClE;EAAC;EAAK;EAAU;CAAQ,CAC1B;AACF;AAEA,SAAgB,kBAAgC;CAC9C,OAAO,WAAW,CAAC,CAAC;AACtB;AAEA,SAAgB,cAA+C;CAC7D,OAAO,WAAW,CAAC,CAAC;AACtB"}
|
|
1
|
+
{"version":3,"file":"context.js","names":["globalTranslator"],"sources":["../src/context.tsx"],"sourcesContent":["import {\n translator as globalTranslator,\n LocaleFormatter,\n pollComponent,\n resolvePanelNavigation,\n type ActionRunner,\n type AlfakitPageProps,\n type ComponentRegistry,\n type LocalizationSchema,\n type PageSchema,\n type PanelSchema,\n type Translator,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n type ComponentType,\n type ReactNode,\n} from 'react'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type AnyComponent = ComponentType<any>\n\nexport interface AlfakitContextValue {\n registry: ComponentRegistry<AnyComponent>\n pages: Record<string, AnyComponent>\n actions: ActionRunner\n translator: Translator\n /**\n * Polls a component (`table`, `widget:<id>`) and returns an unsubscribe. Panels use the\n * shared `pollComponent()` (an Inertia partial reload); components outside panels fetch\n * their own schemas by token.\n */\n poll?: (target: string, interval: string | number | null | undefined) => () => void\n /**\n * Fetches fresh schemas of components now (`live()` tables and widgets after a record\n * change). Panels leave it out (a partial reload); components outside panels fetch theirs.\n */\n refresh?: (targets: string[]) => Promise<void>\n /**\n * Whether every page is rendered only after the split built-ins and discovered components\n * it names have loaded, on the server and before hydration (`createAlfakit()` resolves\n * pages that way). Without it (starter kits, standalone components) those components show\n * after hydration: a long-lived SSR server keeps code earlier requests loaded, a fresh\n * browser has none of it, so rendering them early would make the markups differ.\n */\n preloadsPageComponents?: boolean\n}\n\nconst AlfakitContext = createContext<AlfakitContextValue | null>(null)\n\nconst NO_PROPS: AlfakitPageProps = {} as AlfakitPageProps\n\n/**\n * The Inertia page props, or an empty object outside an Inertia app (component tests,\n * Storybook, a plain server render of an `<AlfakitComponent>`). `usePage()` only reads a\n * context before throwing, so the hook order is the same on both paths.\n */\nfunction useOptionalPageProps(): AlfakitPageProps {\n try {\n return usePage<AlfakitPageProps>().props\n } catch {\n return NO_PROPS\n }\n}\n\n/**\n * The Inertia page URL, or undefined outside an Inertia app. Persistent chrome (the panel\n * layout stays mounted across visits) closes its phone sheets when it changes.\n */\nexport function useOptionalPageUrl(): string | undefined {\n try {\n return usePage().url\n } catch {\n return undefined\n }\n}\n\n/**\n * Calls `close` when the user moves to another page. The panel layout persists across\n * visits, so menus and sheets that used to be reset by remounting close here instead.\n * Partial reloads (deferred props, polling) keep the URL and leave them open.\n */\nexport function useCloseOnVisit(close: () => void): void {\n const url = useOptionalPageUrl()\n const previous = useRef(url)\n\n useEffect(() => {\n if (previous.current === url) return\n\n previous.current = url\n close()\n }, [url, close])\n}\n\n/**\n * Outside panels there is no shared `alfakit` prop and no `page` prop: `<AlfakitComponent>`\n * provides both from its payload, and the hooks below read them first.\n */\nexport interface StandaloneContextValue {\n panel: PanelSchema\n page: PageSchema\n}\n\nconst StandaloneContext = createContext<StandaloneContextValue | null>(null)\n\nexport function StandaloneProvider({\n value,\n children,\n}: {\n value: StandaloneContextValue\n children: ReactNode\n}) {\n return <StandaloneContext.Provider value={value}>{children}</StandaloneContext.Provider>\n}\n\nexport function useStandalone(): StandaloneContextValue | null {\n return useContext(StandaloneContext)\n}\n\nexport function AlfakitProvider({\n value,\n children,\n}: {\n value: AlfakitContextValue\n children: ReactNode\n}) {\n return <AlfakitContext.Provider value={value}>{children}</AlfakitContext.Provider>\n}\n\nexport function useAlfakit(): AlfakitContextValue {\n const context = useContext(AlfakitContext)\n\n if (!context) {\n throw new Error('Alfakit components must be rendered inside createAlfakit().')\n }\n\n return context\n}\n\n/**\n * The component registry when rendered inside `createAlfakit()`, otherwise null (primitives\n * rendered on their own in tests or Storybook).\n */\nexport function useOptionalRegistry(): ComponentRegistry<AnyComponent> | null {\n return useContext(AlfakitContext)?.registry ?? null\n}\n\n/** The context the component renders in, or null outside `createAlfakit()` and `<AlfakitComponent>`. */\nexport function useOptionalAlfakit(): AlfakitContextValue | null {\n return useContext(AlfakitContext)\n}\n\n/** The shared `alfakit` prop (panel, navigation, user, endpoints…). */\nexport function usePanel(): PanelSchema {\n const standalone = useContext(StandaloneContext)\n const props = useOptionalPageProps()\n\n // The navigation structure is a once prop; its active items and badges are merged in here.\n return resolvePanelNavigation(standalone?.panel ?? props.alfakit, props.alfakitNavigationBadges)\n}\n\nexport function usePageSchema(): PageSchema | undefined {\n const standalone = useContext(StandaloneContext)\n const page = useOptionalPageProps().page\n\n return standalone?.page ?? page\n}\n\n/** Poll a component (`table`, `widget:<id>`); the returned function stops it. */\nexport function usePollComponent(): NonNullable<AlfakitContextValue['poll']> {\n return useContext(AlfakitContext)?.poll ?? pollComponent\n}\n\n/** How `live()` components refresh: their own fetch outside panels, else a partial reload. */\nexport function useRefreshComponents(): AlfakitContextValue['refresh'] {\n return useContext(AlfakitContext)?.refresh\n}\n\nexport function useAlfakitProps(): AlfakitPageProps {\n return useOptionalPageProps()\n}\n\nfunction useTranslator(): Translator {\n // Primitives may render outside createAlfakit() (tests, Storybook); fall back to the\n // shared translator so they still show their English defaults.\n return useContext(AlfakitContext)?.translator ?? globalTranslator\n}\n\n/** `t('table.showing', { first: 1, last: 10, total: 42 }, 'Fallback')` */\nexport function useT(): Translator['t'] {\n const translator = useTranslator()\n\n return translator.t.bind(translator)\n}\n\n/** Plural-aware lines: `choice('table.selected', 3)` (Laravel `trans_choice` syntax). */\nexport function useChoice(): Translator['choice'] {\n const translator = useTranslator()\n\n return translator.choice.bind(translator)\n}\n\n/** The current language, direction, currency, timezone and language list. */\nexport function useLocalization(): LocalizationSchema {\n return usePanel().localization\n}\n\nexport function useDirection(): 'ltr' | 'rtl' {\n const standalone = useContext(StandaloneContext)\n const panel = useOptionalPageProps().alfakit\n\n return (standalone?.panel ?? panel)?.localization?.direction ?? 'ltr'\n}\n\n/** `Intl` formatting in the user's locale: `format.money(12.5)`, `format.relative(date)`. */\nexport function useFormatter(): LocaleFormatter {\n const standalone = useContext(StandaloneContext)\n const panel = useOptionalPageProps().alfakit\n const localization = (standalone?.panel ?? panel)?.localization\n\n const tag = localization?.tag\n const currency = localization?.currency\n const timezone = localization?.timezone\n\n return useMemo(\n () => new LocaleFormatter(tag ?? 'en', currency ?? 'USD', timezone),\n [tag, currency, timezone],\n )\n}\n\nexport function useActionRunner(): ActionRunner {\n return useAlfakit().actions\n}\n\nexport function useRegistry(): ComponentRegistry<AnyComponent> {\n return useAlfakit().registry\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-alfakit.d.ts","names":[],"sources":["../src/create-alfakit.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"create-alfakit.d.ts","names":[],"sources":["../src/create-alfakit.tsx"],"mappings":";;;;;iBAuIiB,6BAA6B,eAAe;;EAE3D,WAAW,KAAK,cAAc;EAC9B;;;;;;;;;;;;;;wBAwLoB,cAAc,UAAS,uBAA4B"}
|
package/dist/create-alfakit.js
CHANGED
|
@@ -3,8 +3,9 @@ import { preloadIcons } from "./lib/icons.js";
|
|
|
3
3
|
import { TooltipProvider } from "./components/ui/tooltip.js";
|
|
4
4
|
import { discoverComponents } from "./lib/discovery.js";
|
|
5
5
|
import { builtInComponents, createDefaultRegistry } from "./registry.js";
|
|
6
|
+
import { claimToaster } from "./standalone/toaster-claims.js";
|
|
6
7
|
import { FlashToasts, showToast } from "./components/notifications/flash-toasts.js";
|
|
7
|
-
import { AlfakitPage } from "./pages/page.js";
|
|
8
|
+
import { AlfakitPage, PanelLayoutHost } from "./pages/page.js";
|
|
8
9
|
import { ActionModals } from "./components/actions/action-modals.js";
|
|
9
10
|
import { RealtimeNotifications } from "./components/notifications/realtime-notifications.js";
|
|
10
11
|
import { PwaManager } from "./components/panel/pwa.js";
|
|
@@ -15,7 +16,7 @@ import { Toaster } from "./components/ui/sonner.js";
|
|
|
15
16
|
import { panelLayouts } from "./layouts/panel-layouts.js";
|
|
16
17
|
import { ActionRunner, PANEL_TOAST_OFFSET, Translator, applyDocumentLocale, applyPanelTheme, configureCharts, configureRealtime, debugRecorder, documentNonce, effectiveErrorNotifications, initializeAppearance, installErrorNotifications, installPrefetchInvalidation, installSpaUrlExceptions, overlayPlacementDirection, registerRichEditorExtensions, reloadOnExpiredPage, resolveRequestErrorNotification, resolveToastPosition, setIconAliases, setRequestErrorReporter, whenIdle } from "@alfakit/core";
|
|
17
18
|
import { createInertiaApp, router, usePage } from "@inertiajs/react";
|
|
18
|
-
import { StrictMode, createElement, useLayoutEffect } from "react";
|
|
19
|
+
import { StrictMode, createElement, useEffect, useLayoutEffect } from "react";
|
|
19
20
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
20
21
|
import { Direction } from "radix-ui";
|
|
21
22
|
//#region src/create-alfakit.tsx
|
|
@@ -40,6 +41,7 @@ function AlfakitRoot({ children, toasterId }) {
|
|
|
40
41
|
useLayoutEffect(() => {
|
|
41
42
|
applyPanelTheme(panel);
|
|
42
43
|
}, [panel]);
|
|
44
|
+
useEffect(() => claimToaster(), []);
|
|
43
45
|
return /* @__PURE__ */ jsx(Direction.Provider, {
|
|
44
46
|
dir: direction,
|
|
45
47
|
children: /* @__PURE__ */ jsxs(TooltipProvider, {
|
|
@@ -47,7 +49,7 @@ function AlfakitRoot({ children, toasterId }) {
|
|
|
47
49
|
children: [
|
|
48
50
|
/* @__PURE__ */ jsx(SchemaContractGuard, {
|
|
49
51
|
panel,
|
|
50
|
-
children
|
|
52
|
+
children: /* @__PURE__ */ jsx(PanelLayoutHost, { children })
|
|
51
53
|
}),
|
|
52
54
|
/* @__PURE__ */ jsx(ActionModals, {}),
|
|
53
55
|
/* @__PURE__ */ jsx(FlashToasts, {}),
|
|
@@ -94,6 +96,7 @@ function bootAlfakit(options = {}) {
|
|
|
94
96
|
registry,
|
|
95
97
|
pages,
|
|
96
98
|
translator,
|
|
99
|
+
preloadsPageComponents: true,
|
|
97
100
|
actions: new ActionRunner({
|
|
98
101
|
endpoints: {
|
|
99
102
|
action: "",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-alfakit.js","names":[],"sources":["../src/create-alfakit.tsx"],"sourcesContent":["import {\n ActionRunner,\n PANEL_TOAST_OFFSET,\n Translator,\n applyDocumentLocale,\n applyPanelTheme,\n configureCharts,\n configureRealtime,\n debugRecorder,\n documentNonce,\n effectiveErrorNotifications,\n initializeAppearance,\n installErrorNotifications,\n installPrefetchInvalidation,\n installSpaUrlExceptions,\n overlayPlacementDirection,\n registerRichEditorExtensions,\n reloadOnExpiredPage,\n resolveRequestErrorNotification,\n resolveToastPosition,\n setIconAliases,\n setRequestErrorReporter,\n whenIdle,\n type AlfakitOptions,\n type AlfakitPageProps,\n type ErrorNotificationsSchema,\n type PanelSchema,\n} from '@alfakit/core'\nimport type { InertiaAppSSRResponse, Page } from '@inertiajs/core'\nimport { createInertiaApp, router, usePage } from '@inertiajs/react'\nimport { Direction } from 'radix-ui'\nimport {\n StrictMode,\n createElement,\n useLayoutEffect,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { ActionModals } from './components/actions/action-modals'\nimport { DevToolsSlot } from './components/dev-tools/dev-tools-slot'\nimport { FlashToasts, showToast } from './components/notifications/flash-toasts'\nimport { RealtimeNotifications } from './components/notifications/realtime-notifications'\nimport { ImpersonationBanner } from './components/panel/impersonation-banner'\nimport { PwaManager } from './components/panel/pwa'\nimport { SchemaContractGuard } from './components/panel/schema-contract-guard'\nimport { Toaster } from './components/ui/sonner'\nimport { TooltipProvider } from './components/ui/tooltip'\nimport { AlfakitProvider, useAlfakit, type AlfakitContextValue, type AnyComponent } from './context'\nimport { panelLayouts } from './layouts/panel-layouts'\nimport { discoverComponents } from './lib/discovery'\nimport { preloadIcons } from './lib/icons'\nimport { AlfakitPage } from './pages/page'\nimport { builtInComponents, createDefaultRegistry } from './registry'\n\n/**\n * Rendered inside Inertia's page tree, so it follows the current page: the reading\n * direction for Radix primitives, modals and toasts.\n */\nexport function AlfakitRoot({\n children,\n toasterId,\n}: {\n children: ReactNode\n /** Starter kit mode: Alfakit's toaster gets an id so the host's id-less one never doubles its toasts. */\n toasterId?: string\n}) {\n const panel = usePage<AlfakitPageProps>().props.alfakit\n const { translator, registry } = useAlfakit()\n const localization = panel?.localization\n const direction = localization?.direction ?? 'ltr'\n\n // Every visit (including the redirect after a language switch) may carry new strings,\n // so the translator is updated before children render.\n if (panel) {\n translator.setTranslations(panel.translations ?? {})\n translator.setLocale(localization?.tag ?? 'en')\n // Chrome icons replaced with `Icons::register()`, also in a server render, so the server\n // and the browser draw the same glyph (W-V3-28).\n setIconAliases(panel.theme?.icons)\n }\n\n // Starter kit mode: the host app owns `<html lang dir>` (its own pages and layout are not\n // mirrored); `AlfakitStarterKitRoot` puts the direction on Alfakit's own root element.\n const ownsDocument = panel?.starterKit !== true\n\n useLayoutEffect(() => {\n if (ownsDocument) applyDocumentLocale(localization)\n }, [localization, ownsDocument])\n\n useLayoutEffect(() => {\n applyPanelTheme(panel)\n }, [panel])\n\n return (\n <Direction.Provider dir={direction}>\n <TooltipProvider delayDuration={0}>\n <SchemaContractGuard panel={panel}>{children}</SchemaContractGuard>\n <ActionModals />\n <FlashToasts />\n <RealtimeNotifications />\n {/* `Panel::pwa()`: service worker and the \"new version\" toast. */}\n <PwaManager />\n {/* Schema inspector: renders (and loads its code) only with `alfakitDevTools`. */}\n <DevToolsSlot />\n {/* Replace with createAlfakit({ components: { 'panel:impersonation-banner': … } }). */}\n {panel?.impersonation &&\n createElement(registry.resolve('panel:impersonation-banner') ?? ImpersonationBanner, {\n impersonation: panel.impersonation,\n })}\n <Toaster\n id={toasterId}\n position={resolveToastPosition(\n panel?.toastPosition,\n overlayPlacementDirection(direction, panel?.starterKit),\n )}\n offset={PANEL_TOAST_OFFSET}\n mobileOffset={PANEL_TOAST_OFFSET}\n dir={direction}\n richColors\n closeButton\n />\n </TooltipProvider>\n </Direction.Provider>\n )\n}\n\nexport interface CreateAlfakitOptions extends AlfakitOptions<AnyComponent> {\n /** Wrap the whole app (e.g. with your own providers). */\n withApp?: (app: ReactNode) => ReactNode\n strictMode?: boolean\n}\n\n/** Renders one Inertia page to HTML: what `createInertiaApp` returns on the server. */\nexport type AlfakitRenderFunction = (\n page: Page,\n renderToString: (element: ReactElement) => string,\n) => Promise<InertiaAppSSRResponse>\n\n/**\n * The Inertia app shared by `createAlfakit()` (browser) and `createAlfakitServer()` (Node):\n * the same registry, pages, layout and providers. In the browser it mounts, or hydrates\n * server-rendered markup; on the server (no `window`) Inertia returns a render function.\n */\nexport function bootAlfakit(\n options: CreateAlfakitOptions = {},\n): Promise<void | AlfakitRenderFunction> {\n // Auto-registered files sit between the built-ins and the explicit `components` / `pages`.\n const discovered = discoverComponents(options.discover)\n const registry = createDefaultRegistry()\n .registerMany(panelLayouts)\n .registerMany(discovered.components)\n .registerMany(options.components ?? {})\n const pages: Record<string, AnyComponent> = {\n 'Alfakit/Page': AlfakitPage,\n ...discovered.pages,\n ...options.pages,\n }\n const isServer = typeof window === 'undefined'\n\n // Every panel page draws named icons: fetch `lucide-react/dynamic` while booting.\n preloadIcons()\n\n // `'js:name'` chart option callbacks and Chart.js plugins for every chart widget.\n configureCharts(options)\n // `createAlfakit({ echo })`: Laravel Echo for `Panel::realtime()` without `window.Echo`.\n if (options.echo !== undefined && !isServer) configureRealtime(null, { echo: options.echo })\n // Rich content plugin extensions, loaded when an editor needs them.\n registerRichEditorExtensions(options.richEditorExtensions ?? {})\n\n // The browser's current panel, read by the SPA-exception and error-notification listeners.\n let currentPanel: PanelSchema | null = null\n // The open page's error notifications (its override, else the panel's).\n let currentErrorNotifications: ErrorNotificationsSchema | false | undefined\n\n // One translator and action runner for the browser session; a server render gets fresh\n // ones per request so concurrent renders never share a locale or endpoints.\n const createContext = (): AlfakitContextValue => {\n const translator = new Translator()\n\n return {\n registry,\n pages,\n translator,\n // The runner's endpoints are refreshed on every navigation from the shared props.\n actions: new ActionRunner({\n endpoints: { action: '', modal: '' },\n // A failed action request is a toast, never a silent no-op.\n onError: (error) => {\n const notification = resolveRequestErrorNotification(\n error,\n currentErrorNotifications,\n (key, replacements, fallback) => translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n },\n }),\n }\n }\n\n const browserContext = isServer ? null : createContext()\n\n const sync = (context: AlfakitContextValue, page: Page, applyToDocument: boolean) => {\n const props = page.props as unknown as AlfakitPageProps\n const panel = props.alfakit\n\n if (!panel) return\n\n if (context === browserContext) {\n currentPanel = panel\n currentErrorNotifications = effectiveErrorNotifications(panel, props.page)\n }\n\n context.translator.setTranslations(panel.translations ?? {})\n context.translator.setLocale(panel.localization?.tag ?? 'en')\n context.actions.setEndpoints({\n action: panel.endpoints.action ?? '',\n modal: panel.endpoints.modal ?? '',\n })\n\n if (applyToDocument) {\n applyDocumentLocale(panel.localization)\n applyPanelTheme(panel)\n // Internal requests and form stores are only recorded while the page carries the debug prop.\n debugRecorder.setEnabled(props.alfakitDebug !== undefined)\n }\n }\n\n if (browserContext) {\n router.on('navigate', (event) => sync(browserContext, event.detail.page, true))\n reloadOnExpiredPage()\n // Prefetched pages and deferred table totals are dropped after every mutation.\n installPrefetchInvalidation()\n installSpaUrlExceptions(() => currentPanel)\n installErrorNotifications(\n () => ({ errorNotifications: currentErrorNotifications }),\n (notification) => showToast(notification),\n )\n // Failed internal requests without their own handler (form live refreshes, step\n // validation) show the configured error notification too.\n setRequestErrorReporter((error) => {\n const notification = resolveRequestErrorNotification(\n error,\n currentErrorNotifications,\n (key, replacements, fallback) => browserContext.translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n })\n }\n\n return createInertiaApp<AlfakitPageProps>({\n title: (title) => title,\n progress: options.progress === undefined ? { color: 'var(--primary)' } : options.progress,\n // The page's CSP nonce, so the progress bar's injected <style> passes a nonce-based policy.\n nonce: documentNonce(),\n resolve: (name, page) => {\n const component = pages[name]\n\n if (!component) {\n throw new Error(\n `Alfakit: page component \"${name}\" is not registered. Pass it to createAlfakit({ pages }).`,\n )\n }\n\n // The page's auto-registered components load first, so the server render and the\n // hydration both show them (no flash, no mismatch).\n // Built-ins split per type (`builtInComponents`) load the same way.\n const pending = [\n builtInComponents.preloadFor(page),\n discovered.discovery.preloadFor(page),\n ].filter((promise) => promise !== undefined)\n\n return pending.length > 0 ? Promise.all(pending).then(() => component) : component\n },\n layout: () => AlfakitRoot,\n strictMode: options.strictMode ?? true,\n withApp: (app, { page, ssr }) => {\n const context = browserContext ?? createContext()\n\n sync(context, page as Page, !ssr)\n\n // Panel pages only: other pages (`pages` option) follow the application's own dark\n // mode, like standalone components do.\n const panel = (page.props as unknown as Partial<AlfakitPageProps>).alfakit\n\n if (!ssr && panel) {\n initializeAppearance(panel)\n }\n\n const tree = <AlfakitProvider value={context}>{app}</AlfakitProvider>\n\n const wrapped = options.withApp ? options.withApp(tree) : tree\n\n return options.strictMode === false ? <>{wrapped}</> : <StrictMode>{wrapped}</StrictMode>\n },\n }) as Promise<void | AlfakitRenderFunction>\n}\n\n/**\n * Boot the Alfakit panel frontend. Call once from your panel entry file. Markup rendered by\n * `createAlfakitServer()` (`@alfakit/react/server`) is hydrated instead of re-rendered.\n *\n * ```tsx\n * createAlfakit({\n * // Files in resources/js/alfakit/fields, columns, … register themselves.\n * discover: import.meta.glob('./{fields,columns,entries,widgets,pages,components,blocks}/**\\/*.tsx'),\n * components: { 'map-picker': MapPicker },\n * })\n * ```\n */\nexport async function createAlfakit(options: CreateAlfakitOptions = {}): Promise<void> {\n await bootAlfakit(options)\n\n // After the first render: the remaining split built-ins (action modals' fields).\n whenIdle(() => void builtInComponents.preloadAll())\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-alfakit.js","names":[],"sources":["../src/create-alfakit.tsx"],"sourcesContent":["import {\n ActionRunner,\n PANEL_TOAST_OFFSET,\n Translator,\n applyDocumentLocale,\n applyPanelTheme,\n configureCharts,\n configureRealtime,\n debugRecorder,\n documentNonce,\n effectiveErrorNotifications,\n initializeAppearance,\n installErrorNotifications,\n installPrefetchInvalidation,\n installSpaUrlExceptions,\n overlayPlacementDirection,\n registerRichEditorExtensions,\n reloadOnExpiredPage,\n resolveRequestErrorNotification,\n resolveToastPosition,\n setIconAliases,\n setRequestErrorReporter,\n whenIdle,\n type AlfakitOptions,\n type AlfakitPageProps,\n type ErrorNotificationsSchema,\n type PanelSchema,\n} from '@alfakit/core'\nimport type { InertiaAppSSRResponse, Page } from '@inertiajs/core'\nimport { createInertiaApp, router, usePage } from '@inertiajs/react'\nimport { Direction } from 'radix-ui'\nimport {\n StrictMode,\n createElement,\n useEffect,\n useLayoutEffect,\n type ReactElement,\n type ReactNode,\n} from 'react'\nimport { ActionModals } from './components/actions/action-modals'\nimport { DevToolsSlot } from './components/dev-tools/dev-tools-slot'\nimport { FlashToasts, showToast } from './components/notifications/flash-toasts'\nimport { RealtimeNotifications } from './components/notifications/realtime-notifications'\nimport { ImpersonationBanner } from './components/panel/impersonation-banner'\nimport { PwaManager } from './components/panel/pwa'\nimport { SchemaContractGuard } from './components/panel/schema-contract-guard'\nimport { Toaster } from './components/ui/sonner'\nimport { TooltipProvider } from './components/ui/tooltip'\nimport { AlfakitProvider, useAlfakit, type AlfakitContextValue, type AnyComponent } from './context'\nimport { panelLayouts } from './layouts/panel-layouts'\nimport { discoverComponents } from './lib/discovery'\nimport { preloadIcons } from './lib/icons'\nimport { AlfakitPage, PanelLayoutHost } from './pages/page'\nimport { builtInComponents, createDefaultRegistry } from './registry'\nimport { claimToaster } from './standalone/toaster-claims'\n\n/**\n * Rendered inside Inertia's page tree, so it follows the current page: the reading\n * direction for Radix primitives, modals and toasts.\n */\nexport function AlfakitRoot({\n children,\n toasterId,\n}: {\n children: ReactNode\n /** Starter kit mode: Alfakit's toaster gets an id so the host's id-less one never doubles its toasts. */\n toasterId?: string\n}) {\n const panel = usePage<AlfakitPageProps>().props.alfakit\n const { translator, registry } = useAlfakit()\n const localization = panel?.localization\n const direction = localization?.direction ?? 'ltr'\n\n // Every visit (including the redirect after a language switch) may carry new strings,\n // so the translator is updated before children render.\n if (panel) {\n translator.setTranslations(panel.translations ?? {})\n translator.setLocale(localization?.tag ?? 'en')\n // Chrome icons replaced with `Icons::register()`, also in a server render, so the server\n // and the browser draw the same glyph (W-V3-28).\n setIconAliases(panel.theme?.icons)\n }\n\n // Starter kit mode: the host app owns `<html lang dir>` (its own pages and layout are not\n // mirrored); `AlfakitStarterKitRoot` puts the direction on Alfakit's own root element.\n const ownsDocument = panel?.starterKit !== true\n\n useLayoutEffect(() => {\n if (ownsDocument) applyDocumentLocale(localization)\n }, [localization, ownsDocument])\n\n useLayoutEffect(() => {\n applyPanelTheme(panel)\n }, [panel])\n\n // This root renders the toaster: starter kit header components and standalone components\n // on the same page don't mount a second one.\n useEffect(() => claimToaster(), [])\n\n return (\n <Direction.Provider dir={direction}>\n <TooltipProvider delayDuration={0}>\n {/* The panel layout stays mounted across visits between Alfakit pages. */}\n <SchemaContractGuard panel={panel}>\n <PanelLayoutHost>{children}</PanelLayoutHost>\n </SchemaContractGuard>\n <ActionModals />\n <FlashToasts />\n <RealtimeNotifications />\n {/* `Panel::pwa()`: service worker and the \"new version\" toast. */}\n <PwaManager />\n {/* Schema inspector: renders (and loads its code) only with `alfakitDevTools`. */}\n <DevToolsSlot />\n {/* Replace with createAlfakit({ components: { 'panel:impersonation-banner': … } }). */}\n {panel?.impersonation &&\n createElement(registry.resolve('panel:impersonation-banner') ?? ImpersonationBanner, {\n impersonation: panel.impersonation,\n })}\n <Toaster\n id={toasterId}\n position={resolveToastPosition(\n panel?.toastPosition,\n overlayPlacementDirection(direction, panel?.starterKit),\n )}\n offset={PANEL_TOAST_OFFSET}\n mobileOffset={PANEL_TOAST_OFFSET}\n dir={direction}\n richColors\n closeButton\n />\n </TooltipProvider>\n </Direction.Provider>\n )\n}\n\nexport interface CreateAlfakitOptions extends AlfakitOptions<AnyComponent> {\n /** Wrap the whole app (e.g. with your own providers). */\n withApp?: (app: ReactNode) => ReactNode\n strictMode?: boolean\n}\n\n/** Renders one Inertia page to HTML: what `createInertiaApp` returns on the server. */\nexport type AlfakitRenderFunction = (\n page: Page,\n renderToString: (element: ReactElement) => string,\n) => Promise<InertiaAppSSRResponse>\n\n/**\n * The Inertia app shared by `createAlfakit()` (browser) and `createAlfakitServer()` (Node):\n * the same registry, pages, layout and providers. In the browser it mounts, or hydrates\n * server-rendered markup; on the server (no `window`) Inertia returns a render function.\n */\nexport function bootAlfakit(\n options: CreateAlfakitOptions = {},\n): Promise<void | AlfakitRenderFunction> {\n // Auto-registered files sit between the built-ins and the explicit `components` / `pages`.\n const discovered = discoverComponents(options.discover)\n const registry = createDefaultRegistry()\n .registerMany(panelLayouts)\n .registerMany(discovered.components)\n .registerMany(options.components ?? {})\n const pages: Record<string, AnyComponent> = {\n 'Alfakit/Page': AlfakitPage,\n ...discovered.pages,\n ...options.pages,\n }\n const isServer = typeof window === 'undefined'\n\n // Every panel page draws named icons: fetch `lucide-react/dynamic` while booting.\n preloadIcons()\n\n // `'js:name'` chart option callbacks and Chart.js plugins for every chart widget.\n configureCharts(options)\n // `createAlfakit({ echo })`: Laravel Echo for `Panel::realtime()` without `window.Echo`.\n if (options.echo !== undefined && !isServer) configureRealtime(null, { echo: options.echo })\n // Rich content plugin extensions, loaded when an editor needs them.\n registerRichEditorExtensions(options.richEditorExtensions ?? {})\n\n // The browser's current panel, read by the SPA-exception and error-notification listeners.\n let currentPanel: PanelSchema | null = null\n // The open page's error notifications (its override, else the panel's).\n let currentErrorNotifications: ErrorNotificationsSchema | false | undefined\n\n // One translator and action runner for the browser session; a server render gets fresh\n // ones per request so concurrent renders never share a locale or endpoints.\n const createContext = (): AlfakitContextValue => {\n const translator = new Translator()\n\n return {\n registry,\n pages,\n translator,\n // `resolve` below loads the page's split built-ins and discovered files first.\n preloadsPageComponents: true,\n // The runner's endpoints are refreshed on every navigation from the shared props.\n actions: new ActionRunner({\n endpoints: { action: '', modal: '' },\n // A failed action request is a toast, never a silent no-op.\n onError: (error) => {\n const notification = resolveRequestErrorNotification(\n error,\n currentErrorNotifications,\n (key, replacements, fallback) => translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n },\n }),\n }\n }\n\n const browserContext = isServer ? null : createContext()\n\n const sync = (context: AlfakitContextValue, page: Page, applyToDocument: boolean) => {\n const props = page.props as unknown as AlfakitPageProps\n const panel = props.alfakit\n\n if (!panel) return\n\n if (context === browserContext) {\n currentPanel = panel\n currentErrorNotifications = effectiveErrorNotifications(panel, props.page)\n }\n\n context.translator.setTranslations(panel.translations ?? {})\n context.translator.setLocale(panel.localization?.tag ?? 'en')\n context.actions.setEndpoints({\n action: panel.endpoints.action ?? '',\n modal: panel.endpoints.modal ?? '',\n })\n\n if (applyToDocument) {\n applyDocumentLocale(panel.localization)\n applyPanelTheme(panel)\n // Internal requests and form stores are only recorded while the page carries the debug prop.\n debugRecorder.setEnabled(props.alfakitDebug !== undefined)\n }\n }\n\n if (browserContext) {\n router.on('navigate', (event) => sync(browserContext, event.detail.page, true))\n reloadOnExpiredPage()\n // Prefetched pages and deferred table totals are dropped after every mutation.\n installPrefetchInvalidation()\n installSpaUrlExceptions(() => currentPanel)\n installErrorNotifications(\n () => ({ errorNotifications: currentErrorNotifications }),\n (notification) => showToast(notification),\n )\n // Failed internal requests without their own handler (form live refreshes, step\n // validation) show the configured error notification too.\n setRequestErrorReporter((error) => {\n const notification = resolveRequestErrorNotification(\n error,\n currentErrorNotifications,\n (key, replacements, fallback) => browserContext.translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n })\n }\n\n return createInertiaApp<AlfakitPageProps>({\n title: (title) => title,\n progress: options.progress === undefined ? { color: 'var(--primary)' } : options.progress,\n // The page's CSP nonce, so the progress bar's injected <style> passes a nonce-based policy.\n nonce: documentNonce(),\n resolve: (name, page) => {\n const component = pages[name]\n\n if (!component) {\n throw new Error(\n `Alfakit: page component \"${name}\" is not registered. Pass it to createAlfakit({ pages }).`,\n )\n }\n\n // The page's auto-registered components load first, so the server render and the\n // hydration both show them (no flash, no mismatch).\n // Built-ins split per type (`builtInComponents`) load the same way.\n const pending = [\n builtInComponents.preloadFor(page),\n discovered.discovery.preloadFor(page),\n ].filter((promise) => promise !== undefined)\n\n return pending.length > 0 ? Promise.all(pending).then(() => component) : component\n },\n layout: () => AlfakitRoot,\n strictMode: options.strictMode ?? true,\n withApp: (app, { page, ssr }) => {\n const context = browserContext ?? createContext()\n\n sync(context, page as Page, !ssr)\n\n // Panel pages only: other pages (`pages` option) follow the application's own dark\n // mode, like standalone components do.\n const panel = (page.props as unknown as Partial<AlfakitPageProps>).alfakit\n\n if (!ssr && panel) {\n initializeAppearance(panel)\n }\n\n const tree = <AlfakitProvider value={context}>{app}</AlfakitProvider>\n\n const wrapped = options.withApp ? options.withApp(tree) : tree\n\n return options.strictMode === false ? <>{wrapped}</> : <StrictMode>{wrapped}</StrictMode>\n },\n }) as Promise<void | AlfakitRenderFunction>\n}\n\n/**\n * Boot the Alfakit panel frontend. Call once from your panel entry file. Markup rendered by\n * `createAlfakitServer()` (`@alfakit/react/server`) is hydrated instead of re-rendered.\n *\n * ```tsx\n * createAlfakit({\n * // Files in resources/js/alfakit/fields, columns, … register themselves.\n * discover: import.meta.glob('./{fields,columns,entries,widgets,pages,components,blocks}/**\\/*.tsx'),\n * components: { 'map-picker': MapPicker },\n * })\n * ```\n */\nexport async function createAlfakit(options: CreateAlfakitOptions = {}): Promise<void> {\n await bootAlfakit(options)\n\n // After the first render: the remaining split built-ins (action modals' fields).\n whenIdle(() => void builtInComponents.preloadAll())\n}\n"],"mappings":"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"}
|
package/dist/eject-manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@alfakit/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"adapter": "react",
|
|
5
|
-
"generatedAt": "2026-10-
|
|
5
|
+
"generatedAt": "2026-10-27T10:00:09.916Z",
|
|
6
6
|
"components": {
|
|
7
7
|
"TextInput": {
|
|
8
8
|
"file": "src/components/fields/text-input.tsx",
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { AlfakitProvider, StandaloneProvider, useActionRunner, useAlfakit, useAl
|
|
|
2
2
|
import { PageActionProvider, usePageActionContext } from "./components/actions/action-context.js";
|
|
3
3
|
import { cn } from "./lib/utils.js";
|
|
4
4
|
import { badgeColorClasses, colorClass, cssColor, ghostButtonColorClasses, iconBackgroundColorClasses, iconSizeClasses, outlineButtonColorClasses, solidButtonColorClasses, switchOffColorClasses, switchOnColorClasses, textColorClasses } from "./lib/colors.js";
|
|
5
|
+
import { useHydrated } from "./hooks/use-hydrated.js";
|
|
5
6
|
import { Icon } from "./lib/icons.js";
|
|
6
7
|
import { Badge, badgeVariants } from "./components/ui/badge.js";
|
|
7
8
|
import { Button, buttonVariants } from "./components/ui/button.js";
|
|
@@ -59,7 +60,6 @@ import { ToggleField } from "./components/fields/toggle.js";
|
|
|
59
60
|
import { ToggleButtonsField } from "./components/fields/toggle-buttons.js";
|
|
60
61
|
import { FileManager } from "./components/file-manager/file-manager-lazy.js";
|
|
61
62
|
import { FileManagerPickerField } from "./components/file-manager/picker-field.js";
|
|
62
|
-
import { useHydrated } from "./hooks/use-hydrated.js";
|
|
63
63
|
import { HelpdeskChat } from "./components/helpdesk/chat-widget.js";
|
|
64
64
|
import { HelpdeskInbox } from "./components/helpdesk/inbox-lazy.js";
|
|
65
65
|
import { Kanban } from "./components/kanban/kanban-lazy.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-layout.d.ts","names":[],"sources":["../../src/layouts/app-layout.tsx"],"mappings":";;;iBAwBiB;EACf,UAAU;EACV,cAAc;EACd,kBAAkB;;
|
|
1
|
+
{"version":3,"file":"app-layout.d.ts","names":[],"sources":["../../src/layouts/app-layout.tsx"],"mappings":";;;iBAwBiB;EACf,UAAU;EACV,cAAc;EACd,kBAAkB;;wBAmCJ,YAAY,UAAU,aAAa,mBAAmB,iCAAc,IAAA"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useAlfakitProps, useRegistry } from "../context.js";
|
|
1
|
+
import { useAlfakitProps, useCloseOnVisit, useRegistry } from "../context.js";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
3
|
import { Slot } from "../components/panel/slot.js";
|
|
4
|
-
import { SidebarInset, SidebarProvider, SidebarTrigger } from "../components/ui/sidebar.js";
|
|
4
|
+
import { SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from "../components/ui/sidebar.js";
|
|
5
5
|
import { TopbarLocaleSwitcher } from "../components/panel/locale-switcher.js";
|
|
6
6
|
import { AppSidebar } from "../components/panel/app-sidebar.js";
|
|
7
7
|
import { BottomNav } from "../components/panel/bottom-nav.js";
|
|
@@ -9,7 +9,7 @@ import { Topbar } from "../components/panel/topbar.js";
|
|
|
9
9
|
import { MAX_WIDTHS } from "./max-widths.js";
|
|
10
10
|
import { TopNavLayout } from "./top-nav-layout.js";
|
|
11
11
|
import { hasMobilePreset, hasTopbar, sidebarDefaultOpen, sidebarWidthStyle } from "@alfakit/core";
|
|
12
|
-
import { createElement } from "react";
|
|
12
|
+
import { createElement, useCallback } from "react";
|
|
13
13
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
14
|
//#region src/layouts/app-layout.tsx
|
|
15
15
|
/** Sidebar + topbar shell used by every authenticated page. */
|
|
@@ -28,6 +28,15 @@ function MobileTopbar() {
|
|
|
28
28
|
})]
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The layout stays mounted across visits: the phone sidebar sheet closes when the user
|
|
33
|
+
* moves to another page.
|
|
34
|
+
*/
|
|
35
|
+
function CloseMobileSidebarOnVisit() {
|
|
36
|
+
const { setOpenMobile } = useSidebar();
|
|
37
|
+
useCloseOnVisit(useCallback(() => setOpenMobile(false), [setOpenMobile]));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
31
40
|
function AppLayout({ children, breadcrumbs, maxContentWidth }) {
|
|
32
41
|
const props = useAlfakitProps();
|
|
33
42
|
const registry = useRegistry();
|
|
@@ -45,6 +54,7 @@ function AppLayout({ children, breadcrumbs, maxContentWidth }) {
|
|
|
45
54
|
defaultOpen: sidebarDefaultOpen(props.alfakitSidebarOpen, layout),
|
|
46
55
|
style: sidebarWidthStyle(props.alfakit),
|
|
47
56
|
children: [
|
|
57
|
+
/* @__PURE__ */ jsx(CloseMobileSidebarOnVisit, {}),
|
|
48
58
|
/* @__PURE__ */ jsx(Slot, { name: "body.start" }),
|
|
49
59
|
/* @__PURE__ */ jsx(Slot, { name: "layout.start" }),
|
|
50
60
|
createElement(Sidebar),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-layout.js","names":[],"sources":["../../src/layouts/app-layout.tsx"],"sourcesContent":["import {\n hasMobilePreset,\n hasTopbar,\n sidebarDefaultOpen,\n sidebarWidthStyle,\n type BreadcrumbSchema,\n type Width,\n} from '@alfakit/core'\nimport { createElement, type CSSProperties, type ReactNode } from 'react'\nimport { AppSidebar } from '../components/panel/app-sidebar'\nimport { BottomNav } from '../components/panel/bottom-nav'\nimport { TopbarLocaleSwitcher } from '../components/panel/locale-switcher'\nimport { Slot } from '../components/panel/slot'\nimport { Topbar } from '../components/panel/topbar'\nimport { SidebarInset, SidebarProvider, SidebarTrigger } from '../components/ui/sidebar'\nimport { useAlfakitProps, useRegistry } from '../context'\nimport { cn } from '../lib/utils'\nimport { MAX_WIDTHS } from './max-widths'\nimport { TopNavLayout } from './top-nav-layout'\n\n// Re-exported for code that imported it from here; the map lives in `./max-widths` so the\n// starter kit's content layout doesn't pull the panel chrome in.\nexport { MAX_WIDTHS } from './max-widths'\n\nexport interface AppLayoutProps {\n children: ReactNode\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n}\n\n/** Sidebar + topbar shell used by every authenticated page. */\n/**\n * Stands in for the top bar on phones when the panel has `topbar(false)`: the sidebar\n * toggle has to stay reachable.\n */\nexport function MobileTopbar() {\n return (\n <header\n data-slot=\"topbar\"\n data-variant=\"mobile\"\n className=\"bg-background/80 sticky top-0 z-20 flex h-12 shrink-0 items-center gap-2 border-b px-4 backdrop-blur md:hidden\"\n >\n <SidebarTrigger className=\"-ms-1\" />\n <div className=\"ms-auto flex items-center gap-1\">\n <TopbarLocaleSwitcher />\n </div>\n </header>\n )\n}\n\nexport function AppLayout({ children, breadcrumbs, maxContentWidth }: AppLayoutProps) {\n const props = useAlfakitProps()\n const registry = useRegistry()\n const layout = props.alfakit.theme?.design?.layout ?? 'sidebar'\n // Replace the sidebar or top bar everywhere with createAlfakit({ components: { 'panel:sidebar': … } }).\n const Sidebar = registry.resolve('panel:sidebar') ?? AppSidebar\n const Bar = registry.resolve('panel:topbar') ?? Topbar\n // The mobile preset's bottom bar (`Panel::mobile()`), hidden from `md` up.\n const Bottom = registry.resolve('panel:bottom-nav') ?? BottomNav\n const width = maxContentWidth ?? props.alfakit.maxContentWidth ?? '7xl'\n\n if (layout === 'topbar') {\n return (\n <TopNavLayout breadcrumbs={breadcrumbs} maxContentWidth={maxContentWidth}>\n {children}\n </TopNavLayout>\n )\n }\n\n return (\n // The rail layout starts as icons only until the user expands it.\n <SidebarProvider\n defaultOpen={sidebarDefaultOpen(props.alfakitSidebarOpen, layout)}\n style={sidebarWidthStyle(props.alfakit) as CSSProperties | undefined}\n >\n <Slot name=\"body.start\" />\n <Slot name=\"layout.start\" />\n {createElement(Sidebar)}\n <SidebarInset>\n <Slot name=\"topbar.before\" />\n {/* `topbar(false)`: no top bar, except a slim one with the sidebar toggle on phones. */}\n {hasTopbar(props.alfakit) ? createElement(Bar, { breadcrumbs }) : <MobileTopbar />}\n <Slot name=\"topbar.after\" />\n <Slot name=\"content.before\" />\n <div\n data-slot=\"content\"\n className={cn(\n 'mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6',\n MAX_WIDTHS[width] ?? 'max-w-7xl',\n )}\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n <Slot name=\"content.after\" />\n <Slot name=\"footer\" />\n </SidebarInset>\n {hasMobilePreset(props.alfakit) && createElement(Bottom)}\n <Slot name=\"layout.end\" />\n <Slot name=\"body.end\" />\n </SidebarProvider>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,eAAe;CAC7B,OACE,qBAAC,UAAD;EACE,aAAU;EACV,gBAAa;EACb,WAAU;EAHZ,UAAA,CAKE,oBAAC,gBAAD,EAAgB,WAAU,QAAS,CAAA,GACnC,oBAAC,OAAD;GAAK,WAAU;GACb,UAAA,oBAAC,sBAAD,CAAuB,CAAA;EACpB,CAAA,CACC;;AAEZ;AAEA,SAAgB,UAAU,EAAE,UAAU,aAAa,mBAAmC;CACpF,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,WAAW,YAAY;CAC7B,MAAM,SAAS,MAAM,QAAQ,OAAO,QAAQ,UAAU;CAEtD,MAAM,UAAU,SAAS,QAAQ,eAAe,KAAK;CACrD,MAAM,MAAM,SAAS,QAAQ,cAAc,KAAK;CAEhD,MAAM,SAAS,SAAS,QAAQ,kBAAkB,KAAK;CACvD,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,mBAAmB;CAElE,IAAI,WAAW,UACb,OACE,oBAAC,cAAD;EAA2B;EAA8B;EACtD;CACW,CAAA;CAIlB,OAEE,qBAAC,iBAAD;EACE,aAAa,mBAAmB,MAAM,oBAAoB,MAAM;EAChE,OAAO,kBAAkB,MAAM,OAAO;EAFxC,UAAA;GAIE,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA;GACzB,oBAAC,MAAD,EAAM,MAAK,eAAgB,CAAA;GAC1B,cAAc,OAAO;GACtB,qBAAC,cAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;IAE3B,UAAU,MAAM,OAAO,IAAI,cAAc,KAAK,EAAE,YAAY,CAAC,IAAI,oBAAC,cAAD,CAAe,CAAA;IACjF,oBAAC,MAAD,EAAM,MAAK,eAAgB,CAAA;IAC3B,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA;IAC7B,qBAAC,OAAD;KACE,aAAU;KACV,WAAW,GACT,gEACA,WAAW,UAAU,WACvB;KALF,UAAA;MAOE,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;MAC3B;MACD,oBAAC,MAAD,EAAM,MAAK,cAAe,CAAA;KACvB;;IACL,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;IAC5B,oBAAC,MAAD,EAAM,MAAK,SAAU,CAAA;GACT,EAAA,CAAA;GACb,gBAAgB,MAAM,OAAO,KAAK,cAAc,MAAM;GACvD,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA;GACzB,oBAAC,MAAD,EAAM,MAAK,WAAY,CAAA;EACR;;AAErB"}
|
|
1
|
+
{"version":3,"file":"app-layout.js","names":[],"sources":["../../src/layouts/app-layout.tsx"],"sourcesContent":["import {\n hasMobilePreset,\n hasTopbar,\n sidebarDefaultOpen,\n sidebarWidthStyle,\n type BreadcrumbSchema,\n type Width,\n} from '@alfakit/core'\nimport { createElement, useCallback, type CSSProperties, type ReactNode } from 'react'\nimport { AppSidebar } from '../components/panel/app-sidebar'\nimport { BottomNav } from '../components/panel/bottom-nav'\nimport { TopbarLocaleSwitcher } from '../components/panel/locale-switcher'\nimport { Slot } from '../components/panel/slot'\nimport { Topbar } from '../components/panel/topbar'\nimport { SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from '../components/ui/sidebar'\nimport { useAlfakitProps, useCloseOnVisit, useRegistry } from '../context'\nimport { cn } from '../lib/utils'\nimport { MAX_WIDTHS } from './max-widths'\nimport { TopNavLayout } from './top-nav-layout'\n\n// Re-exported for code that imported it from here; the map lives in `./max-widths` so the\n// starter kit's content layout doesn't pull the panel chrome in.\nexport { MAX_WIDTHS } from './max-widths'\n\nexport interface AppLayoutProps {\n children: ReactNode\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n}\n\n/** Sidebar + topbar shell used by every authenticated page. */\n/**\n * Stands in for the top bar on phones when the panel has `topbar(false)`: the sidebar\n * toggle has to stay reachable.\n */\nexport function MobileTopbar() {\n return (\n <header\n data-slot=\"topbar\"\n data-variant=\"mobile\"\n className=\"bg-background/80 sticky top-0 z-20 flex h-12 shrink-0 items-center gap-2 border-b px-4 backdrop-blur md:hidden\"\n >\n <SidebarTrigger className=\"-ms-1\" />\n <div className=\"ms-auto flex items-center gap-1\">\n <TopbarLocaleSwitcher />\n </div>\n </header>\n )\n}\n\n/**\n * The layout stays mounted across visits: the phone sidebar sheet closes when the user\n * moves to another page.\n */\nfunction CloseMobileSidebarOnVisit() {\n const { setOpenMobile } = useSidebar()\n\n useCloseOnVisit(useCallback(() => setOpenMobile(false), [setOpenMobile]))\n\n return null\n}\n\nexport function AppLayout({ children, breadcrumbs, maxContentWidth }: AppLayoutProps) {\n const props = useAlfakitProps()\n const registry = useRegistry()\n const layout = props.alfakit.theme?.design?.layout ?? 'sidebar'\n // Replace the sidebar or top bar everywhere with createAlfakit({ components: { 'panel:sidebar': … } }).\n const Sidebar = registry.resolve('panel:sidebar') ?? AppSidebar\n const Bar = registry.resolve('panel:topbar') ?? Topbar\n // The mobile preset's bottom bar (`Panel::mobile()`), hidden from `md` up.\n const Bottom = registry.resolve('panel:bottom-nav') ?? BottomNav\n const width = maxContentWidth ?? props.alfakit.maxContentWidth ?? '7xl'\n\n if (layout === 'topbar') {\n return (\n <TopNavLayout breadcrumbs={breadcrumbs} maxContentWidth={maxContentWidth}>\n {children}\n </TopNavLayout>\n )\n }\n\n return (\n // The rail layout starts as icons only until the user expands it.\n <SidebarProvider\n defaultOpen={sidebarDefaultOpen(props.alfakitSidebarOpen, layout)}\n style={sidebarWidthStyle(props.alfakit) as CSSProperties | undefined}\n >\n <CloseMobileSidebarOnVisit />\n <Slot name=\"body.start\" />\n <Slot name=\"layout.start\" />\n {createElement(Sidebar)}\n <SidebarInset>\n <Slot name=\"topbar.before\" />\n {/* `topbar(false)`: no top bar, except a slim one with the sidebar toggle on phones. */}\n {hasTopbar(props.alfakit) ? createElement(Bar, { breadcrumbs }) : <MobileTopbar />}\n <Slot name=\"topbar.after\" />\n <Slot name=\"content.before\" />\n <div\n data-slot=\"content\"\n className={cn(\n 'mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6',\n MAX_WIDTHS[width] ?? 'max-w-7xl',\n )}\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n <Slot name=\"content.after\" />\n <Slot name=\"footer\" />\n </SidebarInset>\n {hasMobilePreset(props.alfakit) && createElement(Bottom)}\n <Slot name=\"layout.end\" />\n <Slot name=\"body.end\" />\n </SidebarProvider>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,eAAe;CAC7B,OACE,qBAAC,UAAD;EACE,aAAU;EACV,gBAAa;EACb,WAAU;EAHZ,UAAA,CAKE,oBAAC,gBAAD,EAAgB,WAAU,QAAS,CAAA,GACnC,oBAAC,OAAD;GAAK,WAAU;GACb,UAAA,oBAAC,sBAAD,CAAuB,CAAA;EACpB,CAAA,CACC;;AAEZ;;;;;AAMA,SAAS,4BAA4B;CACnC,MAAM,EAAE,kBAAkB,WAAW;CAErC,gBAAgB,kBAAkB,cAAc,KAAK,GAAG,CAAC,aAAa,CAAC,CAAC;CAExE,OAAO;AACT;AAEA,SAAgB,UAAU,EAAE,UAAU,aAAa,mBAAmC;CACpF,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,WAAW,YAAY;CAC7B,MAAM,SAAS,MAAM,QAAQ,OAAO,QAAQ,UAAU;CAEtD,MAAM,UAAU,SAAS,QAAQ,eAAe,KAAK;CACrD,MAAM,MAAM,SAAS,QAAQ,cAAc,KAAK;CAEhD,MAAM,SAAS,SAAS,QAAQ,kBAAkB,KAAK;CACvD,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,mBAAmB;CAElE,IAAI,WAAW,UACb,OACE,oBAAC,cAAD;EAA2B;EAA8B;EACtD;CACW,CAAA;CAIlB,OAEE,qBAAC,iBAAD;EACE,aAAa,mBAAmB,MAAM,oBAAoB,MAAM;EAChE,OAAO,kBAAkB,MAAM,OAAO;EAFxC,UAAA;GAIE,oBAAC,2BAAD,CAA4B,CAAA;GAC5B,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA;GACzB,oBAAC,MAAD,EAAM,MAAK,eAAgB,CAAA;GAC1B,cAAc,OAAO;GACtB,qBAAC,cAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;IAE3B,UAAU,MAAM,OAAO,IAAI,cAAc,KAAK,EAAE,YAAY,CAAC,IAAI,oBAAC,cAAD,CAAe,CAAA;IACjF,oBAAC,MAAD,EAAM,MAAK,eAAgB,CAAA;IAC3B,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA;IAC7B,qBAAC,OAAD;KACE,aAAU;KACV,WAAW,GACT,gEACA,WAAW,UAAU,WACvB;KALF,UAAA;MAOE,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;MAC3B;MACD,oBAAC,MAAD,EAAM,MAAK,cAAe,CAAA;KACvB;;IACL,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;IAC5B,oBAAC,MAAD,EAAM,MAAK,SAAU,CAAA;GACT,EAAA,CAAA;GACb,gBAAgB,MAAM,OAAO,KAAK,cAAc,MAAM;GACvD,oBAAC,MAAD,EAAM,MAAK,aAAc,CAAA;GACzB,oBAAC,MAAD,EAAM,MAAK,WAAY,CAAA;EACR;;AAErB"}
|