@alfakit/react 0.1.0 → 0.1.2

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/components/actions/action-button.d.ts.map +1 -1
  3. package/dist/components/actions/action-button.js +5 -4
  4. package/dist/components/actions/action-button.js.map +1 -1
  5. package/dist/components/actions/action-modals.d.ts.map +1 -1
  6. package/dist/components/actions/action-modals.js +2 -5
  7. package/dist/components/actions/action-modals.js.map +1 -1
  8. package/dist/components/fields/color-picker.js +1 -0
  9. package/dist/components/fields/color-picker.js.map +1 -1
  10. package/dist/components/fields/file-upload.js +11 -10
  11. package/dist/components/fields/file-upload.js.map +1 -1
  12. package/dist/components/fields/one-time-code-input.js +1 -1
  13. package/dist/components/fields/one-time-code-input.js.map +1 -1
  14. package/dist/components/fields/repeater-table.js +12 -1
  15. package/dist/components/fields/repeater-table.js.map +1 -1
  16. package/dist/components/fields/select.js +3 -3
  17. package/dist/components/fields/select.js.map +1 -1
  18. package/dist/components/file-manager/file-manager.js +1 -1
  19. package/dist/components/file-manager/file-manager.js.map +1 -1
  20. package/dist/components/form/form-block.d.ts.map +1 -1
  21. package/dist/components/form/form-block.js +13 -1
  22. package/dist/components/form/form-block.js.map +1 -1
  23. package/dist/components/form/form-context.js +6 -2
  24. package/dist/components/form/form-context.js.map +1 -1
  25. package/dist/components/layout/fused-group.js +1 -0
  26. package/dist/components/layout/fused-group.js.map +1 -1
  27. package/dist/components/layout/tabs.d.ts.map +1 -1
  28. package/dist/components/layout/tabs.js +54 -15
  29. package/dist/components/layout/tabs.js.map +1 -1
  30. package/dist/components/panel/global-search.js +1 -0
  31. package/dist/components/panel/global-search.js.map +1 -1
  32. package/dist/components/panel/shortcut-keys.js +1 -0
  33. package/dist/components/panel/shortcut-keys.js.map +1 -1
  34. package/dist/components/panel/tenant-menu.js +2 -2
  35. package/dist/components/panel/tenant-menu.js.map +1 -1
  36. package/dist/components/table/cells.js +9 -1
  37. package/dist/components/table/cells.js.map +1 -1
  38. package/dist/components/table/grouping.js +11 -1
  39. package/dist/components/table/grouping.js.map +1 -1
  40. package/dist/components.css +347 -0
  41. package/dist/eject-manifest.json +2 -2
  42. package/dist/standalone/alfakit-toaster.d.ts.map +1 -1
  43. package/dist/standalone/alfakit-toaster.js +8 -1
  44. package/dist/standalone/alfakit-toaster.js.map +1 -1
  45. package/dist/standalone.css +47 -0
  46. package/dist/starter-kit/widgets-loaded.js +1 -1
  47. package/dist/starter-kit/widgets-loaded.js.map +1 -1
  48. package/dist/starter-kit/widgets-triggers.js +1 -0
  49. package/dist/starter-kit/widgets-triggers.js.map +1 -1
  50. package/dist/themes.css +2 -334
  51. package/package.json +2 -2
  52. package/src/components/actions/action-button.tsx +7 -4
  53. package/src/components/actions/action-modals.tsx +3 -1
  54. package/src/components/fields/color-picker.tsx +1 -0
  55. package/src/components/fields/file-upload.tsx +21 -10
  56. package/src/components/fields/one-time-code-input.tsx +1 -1
  57. package/src/components/fields/repeater-table.tsx +12 -1
  58. package/src/components/fields/select.tsx +7 -3
  59. package/src/components/file-manager/file-manager.tsx +1 -1
  60. package/src/components/form/form-block.tsx +24 -0
  61. package/src/components/form/form-context.tsx +2 -5
  62. package/src/components/layout/fused-group.tsx +5 -1
  63. package/src/components/layout/tabs.tsx +72 -17
  64. package/src/components/panel/global-search.tsx +1 -1
  65. package/src/components/panel/shortcut-keys.tsx +4 -1
  66. package/src/components/panel/tenant-menu.tsx +2 -2
  67. package/src/components/table/cells.tsx +18 -3
  68. package/src/components/table/grouping.tsx +15 -1
  69. package/src/standalone/alfakit-toaster.tsx +21 -2
  70. package/src/starter-kit/widgets-loaded.tsx +1 -1
  71. package/src/starter-kit/widgets-triggers.tsx +1 -1
@@ -1,7 +1,8 @@
1
1
  import { useOptionalAlfakit } from "../context.js";
2
2
  import { claimToaster, createStandaloneContext, ensureFlashToasts } from "./runtime.js";
3
+ import { setToasterId } from "../components/notifications/flash-toasts.js";
3
4
  import { Toaster } from "../components/ui/sonner.js";
4
- import { resolveToastPosition } from "@alfakit/core";
5
+ import { STARTER_KIT_TOASTER_ID, resolveToastPosition } from "@alfakit/core";
5
6
  import { useEffect, useMemo } from "react";
6
7
  import { jsx } from "react/jsx-runtime";
7
8
  //#region src/standalone/alfakit-toaster.tsx
@@ -24,8 +25,14 @@ function AlfakitToaster({ position, dir = "ltr", toaster = true, claimed = false
24
25
  useEffect(() => {
25
26
  ensureFlashToasts(context.registry);
26
27
  }, [context]);
28
+ useEffect(() => {
29
+ if (!toaster) return void 0;
30
+ setToasterId(STARTER_KIT_TOASTER_ID);
31
+ return () => setToasterId(void 0);
32
+ }, [toaster]);
27
33
  if (!toaster) return null;
28
34
  return /* @__PURE__ */ jsx(Toaster, {
35
+ id: STARTER_KIT_TOASTER_ID,
29
36
  position: resolveToastPosition(position, dir),
30
37
  dir,
31
38
  richColors: true,
@@ -1 +1 @@
1
- {"version":3,"file":"alfakit-toaster.js","names":[],"sources":["../../src/standalone/alfakit-toaster.tsx"],"sourcesContent":["import { resolveToastPosition, type ToastPosition } from '@alfakit/core'\nimport { useEffect, useMemo } from 'react'\nimport { Toaster } from '../components/ui/sonner'\nimport { useOptionalAlfakit, type AlfakitContextValue } from '../context'\nimport { claimToaster, createStandaloneContext, ensureFlashToasts } from './runtime'\n\nexport interface AlfakitToasterProps {\n /** Where toasts appear; `start`/`end` follow the reading direction. */\n position?: ToastPosition\n dir?: 'ltr' | 'rtl'\n /**\n * Render sonner's `<Toaster>` too (default). Pass `false` when your layout already mounts\n * one: flashed Alfakit notifications are then shown through it.\n */\n toaster?: boolean\n /** @internal Set by `<AlfakitComponent>` when it takes the toaster role. */\n claimed?: boolean\n}\n\n/**\n * Shows notifications flashed by standalone components (`Notification::make()->send()`\n * from `submit()` or an action) as toasts. Place it once in your app layout; a page whose\n * layout has none gets one from its first `<AlfakitComponent>`.\n *\n * ```tsx\n * <AlfakitToaster />\n * ```\n */\nexport function AlfakitToaster({\n position,\n dir = 'ltr',\n toaster = true,\n claimed = false,\n}: AlfakitToasterProps) {\n const parent = useOptionalAlfakit()\n\n // Outside <AlfakitComponent> the flash reader needs a registry for custom toast views.\n const context = useMemo<AlfakitContextValue>(\n () => parent ?? createStandaloneContext({ action: '', modal: '' }, undefined, undefined),\n [parent],\n )\n\n useEffect(() => (claimed ? undefined : claimToaster()), [claimed])\n\n useEffect(() => {\n ensureFlashToasts(context.registry)\n }, [context])\n\n if (!toaster) return null\n\n return <Toaster position={resolveToastPosition(position, dir)} dir={dir} richColors closeButton />\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,SAAgB,eAAe,EAC7B,UACA,MAAM,OACN,UAAU,MACV,UAAU,SACY;CACtB,MAAM,SAAS,mBAAmB;CAGlC,MAAM,UAAU,cACR,UAAU,wBAAwB;EAAE,QAAQ;EAAI,OAAO;CAAG,GAAG,KAAA,GAAW,KAAA,CAAS,GACvF,CAAC,MAAM,CACT;CAEA,gBAAiB,UAAU,KAAA,IAAY,aAAa,GAAI,CAAC,OAAO,CAAC;CAEjE,gBAAgB;EACd,kBAAkB,QAAQ,QAAQ;CACpC,GAAG,CAAC,OAAO,CAAC;CAEZ,IAAI,CAAC,SAAS,OAAO;CAErB,OAAO,oBAAC,SAAD;EAAS,UAAU,qBAAqB,UAAU,GAAG;EAAQ;EAAK,YAAA;EAAW,aAAA;CAAa,CAAA;AACnG"}
1
+ {"version":3,"file":"alfakit-toaster.js","names":[],"sources":["../../src/standalone/alfakit-toaster.tsx"],"sourcesContent":["import { resolveToastPosition, STARTER_KIT_TOASTER_ID, type ToastPosition } from '@alfakit/core'\nimport { useEffect, useMemo } from 'react'\nimport { setToasterId } from '../components/notifications/flash-toasts'\nimport { Toaster } from '../components/ui/sonner'\nimport { useOptionalAlfakit, type AlfakitContextValue } from '../context'\nimport { claimToaster, createStandaloneContext, ensureFlashToasts } from './runtime'\n\nexport interface AlfakitToasterProps {\n /** Where toasts appear; `start`/`end` follow the reading direction. */\n position?: ToastPosition\n dir?: 'ltr' | 'rtl'\n /**\n * Render sonner's `<Toaster>` too (default). Pass `false` when your layout already mounts\n * one: flashed Alfakit notifications are then shown through it.\n */\n toaster?: boolean\n /** @internal Set by `<AlfakitComponent>` when it takes the toaster role. */\n claimed?: boolean\n}\n\n/**\n * Shows notifications flashed by standalone components (`Notification::make()->send()`\n * from `submit()` or an action) as toasts. Place it once in your app layout; a page whose\n * layout has none gets one from its first `<AlfakitComponent>`.\n *\n * ```tsx\n * <AlfakitToaster />\n * ```\n */\nexport function AlfakitToaster({\n position,\n dir = 'ltr',\n toaster = true,\n claimed = false,\n}: AlfakitToasterProps) {\n const parent = useOptionalAlfakit()\n\n // Outside <AlfakitComponent> the flash reader needs a registry for custom toast views.\n const context = useMemo<AlfakitContextValue>(\n () => parent ?? createStandaloneContext({ action: '', modal: '' }, undefined, undefined),\n [parent],\n )\n\n useEffect(() => (claimed ? undefined : claimToaster()), [claimed])\n\n useEffect(() => {\n ensureFlashToasts(context.registry)\n }, [context])\n\n // Our own toaster gets an id, so a `<Toaster />` the app's layout already mounts (the\n // starter kits have one) doesn't show every Alfakit toast a second time.\n useEffect(() => {\n if (!toaster) return undefined\n\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => setToasterId(undefined)\n }, [toaster])\n\n if (!toaster) return null\n\n return (\n <Toaster\n id={STARTER_KIT_TOASTER_ID}\n position={resolveToastPosition(position, dir)}\n dir={dir}\n richColors\n closeButton\n />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,SAAgB,eAAe,EAC7B,UACA,MAAM,OACN,UAAU,MACV,UAAU,SACY;CACtB,MAAM,SAAS,mBAAmB;CAGlC,MAAM,UAAU,cACR,UAAU,wBAAwB;EAAE,QAAQ;EAAI,OAAO;CAAG,GAAG,KAAA,GAAW,KAAA,CAAS,GACvF,CAAC,MAAM,CACT;CAEA,gBAAiB,UAAU,KAAA,IAAY,aAAa,GAAI,CAAC,OAAO,CAAC;CAEjE,gBAAgB;EACd,kBAAkB,QAAQ,QAAQ;CACpC,GAAG,CAAC,OAAO,CAAC;CAIZ,gBAAgB;EACd,IAAI,CAAC,SAAS,OAAO,KAAA;EAErB,aAAa,sBAAsB;EAEnC,aAAa,aAAa,KAAA,CAAS;CACrC,GAAG,CAAC,OAAO,CAAC;CAEZ,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,oBAAC,SAAD;EACE,IAAI;EACJ,UAAU,qBAAqB,UAAU,GAAG;EACvC;EACL,YAAA;EACA,aAAA;CACD,CAAA;AAEL"}
@@ -13,6 +13,8 @@
13
13
  * your own `:root`. Shared by the React, Vue and Svelte renderers (copied into each dist).
14
14
  */
15
15
 
16
+ @import './components.css';
17
+
16
18
  @custom-variant dark (&:is(.dark *));
17
19
 
18
20
  /*
@@ -148,3 +150,48 @@
148
150
  content: attr(data-placeholder);
149
151
  @apply text-muted-foreground pointer-events-none float-left;
150
152
  }
153
+
154
+ /*
155
+ * "Developed by Alfa Six" (`[data-slot='credit']`) in the footer of panel pages. Starter-kit
156
+ * panels load this stylesheet instead of `themes.css`, so its rules are repeated here
157
+ * (keep them in step with the Credit section of themes.css).
158
+ */
159
+ [data-slot='credit'] {
160
+ display: flex;
161
+ justify-content: center;
162
+ padding: 0.75rem 1rem 1rem;
163
+ font-size: 0.75rem;
164
+ line-height: 1rem;
165
+ color: var(--muted-foreground);
166
+ }
167
+
168
+ [data-slot='credit'] a {
169
+ display: inline-flex;
170
+ align-items: center;
171
+ gap: 0.375rem;
172
+ color: inherit;
173
+ text-decoration: none;
174
+ transition: color 150ms;
175
+ }
176
+
177
+ [data-slot='credit'] svg {
178
+ width: auto;
179
+ height: 1.125rem;
180
+ flex-shrink: 0;
181
+ color: #073b78;
182
+ }
183
+
184
+ .dark [data-slot='credit'] svg {
185
+ color: #ffffff;
186
+ }
187
+
188
+ [data-slot='credit'] a:hover,
189
+ [data-slot='credit'] a:focus-visible {
190
+ color: var(--foreground);
191
+ text-decoration: underline;
192
+ text-underline-offset: 4px;
193
+ }
194
+
195
+ [data-slot='credit'] strong {
196
+ font-weight: 600;
197
+ }
@@ -161,7 +161,7 @@ function TenantChooser({ panel }) {
161
161
  children: [/* @__PURE__ */ jsx(Icon, {
162
162
  name: "alfakit::panels.tenant-menu.registration-button",
163
163
  fallback: PlusIcon
164
- }), t("tenancy.register", {}, "New")]
164
+ }), tenancy.registrationLabel ?? t("tenancy.register", {}, "New")]
165
165
  })] })
166
166
  ]
167
167
  })] });
@@ -1 +1 @@
1
- {"version":3,"file":"widgets-loaded.js","names":["DropdownMenu","Avatar"],"sources":["../../src/starter-kit/widgets-loaded.tsx"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n Translator,\n initials,\n isAlfakitPage,\n loadStarterKitPanel,\n loadedStarterKitPanel,\n openRequestedStarterKitSearch,\n starterKitWidgetsPage,\n starterKitWidgetsPanel,\n tenantNameText,\n type PanelSchema,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { router, usePage } from '@inertiajs/react'\nimport { ChevronsUpDownIcon, PlusIcon } from 'lucide-react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useMemo, useState, type ReactNode } from 'react'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { RealtimeNotifications } from '../components/notifications/realtime-notifications'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { TenantName } from '../components/panel/nav-label'\nimport { TenantMenu } from '../components/panel/tenant-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '../components/ui/avatar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../components/ui/dropdown-menu'\nimport { TooltipProvider } from '../components/ui/tooltip'\nimport {\n AlfakitProvider,\n StandaloneProvider,\n useT,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { Icon } from '../lib/icons'\nimport {\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * The loaded header components of starter kit mode: the panel's own search palette,\n * notifications menu and tenant menu, rendered outside Alfakit pages with the panel data\n * from `widgets.endpoint`. This module only loads in the browser.\n */\n\n// One context for the browser session: its own translator (the panel's strings), an empty\n// registry (only `panel:icon` overrides are looked up here) and an idle action runner.\nlet widgetsContext: AlfakitContextValue | null = null\n\nfunction useWidgetsContext(panel: PanelSchema): AlfakitContextValue {\n widgetsContext ??= {\n registry: new ComponentRegistry<AnyComponent>(),\n pages: {},\n translator: new Translator(),\n actions: new ActionRunner({ endpoints: { action: '', modal: '' } }),\n }\n\n widgetsContext.translator.setTranslations(panel.translations ?? {})\n widgetsContext.translator.setLocale(panel.localization?.tag ?? 'en')\n\n return widgetsContext\n}\n\n/** The panel the components render with, once its data has loaded (null until then). */\nfunction useWidgetsPanel(source: StarterKitWidgets): PanelSchema | null {\n const endpoint = source.widgets.endpoint\n const [loaded, setLoaded] = useState(() => loadedStarterKitPanel(endpoint))\n const base = loadedStarterKitPanel(endpoint) ?? loaded\n\n useEffect(() => {\n let active = true\n\n // Another endpoint (a tenant switch) loads its own panel data.\n void loadStarterKitPanel(endpoint).then(\n (panel) => active && setLoaded(panel),\n () => {},\n )\n\n return () => {\n active = false\n }\n }, [endpoint])\n\n const { navigation, widgets } = source\n\n return useMemo(\n () => (base ? starterKitWidgetsPanel(base, { navigation, widgets }) : null),\n [base, navigation, widgets],\n )\n}\n\nfunction WidgetShell({ panel, children }: { panel: PanelSchema; children: ReactNode }) {\n const context = useWidgetsContext(panel)\n const page = useMemo(() => starterKitWidgetsPage(), [])\n\n return (\n <AlfakitProvider value={context}>\n <StandaloneProvider value={{ panel, page }}>\n <Direction.Provider dir={panel.localization?.direction ?? 'ltr'}>\n <TooltipProvider delayDuration={0}>{children}</TooltipProvider>\n </Direction.Provider>\n </StandaloneProvider>\n </AlfakitProvider>\n )\n}\n\n/** The panel's command palette and global search. */\nexport function LoadedSearch({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n // A click or the key bindings before the palette loaded open it now (after its own\n // effects registered the shortcut).\n useEffect(() => {\n if (panel) openRequestedStarterKitSearch()\n }, [panel])\n\n if (!panel) return <SearchTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <GlobalSearch />\n </WidgetShell>\n )\n}\n\n/** The notifications bell, with realtime notifications on the host's own pages. */\nexport function LoadedNotifications({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // Alfakit pages subscribe to realtime notifications themselves.\n const alfakitPage = isAlfakitPage(usePage().props)\n\n if (!panel) return <NotificationsTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <NotificationsMenu />\n {!alfakitPage && <RealtimeNotifications />}\n </WidgetShell>\n )\n}\n\n/** The panel's tenant menu inside a tenant, a menu to choose one elsewhere. */\nexport function LoadedTenantSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <TenantSwitcherTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n {panel.tenancy?.current ? <TenantMenu variant=\"topbar\" /> : <TenantChooser panel={panel} />}\n </WidgetShell>\n )\n}\n\n/** The user's tenants on a page outside the panel (no current tenant). */\nfunction TenantChooser({ panel }: { panel: PanelSchema }) {\n const t = useT()\n const tenancy = panel.tenancy\n\n if (!tenancy || (tenancy.tenants.length === 0 && !tenancy.registrationUrl)) return null\n\n const label = t('starter_kit.choose_tenant', {}, 'Choose a tenant')\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"tenant-menu\"\n className=\"hover:bg-accent data-[state=open]:bg-accent inline-flex h-9 max-w-60 shrink-0 items-center gap-2 rounded-md px-2 text-sm font-medium\"\n >\n <span className=\"sr-only lg:not-sr-only lg:truncate\">{label}</span>\n <Icon\n name=\"alfakit::panels.tenant-menu.toggle-button\"\n fallback={ChevronsUpDownIcon}\n className=\"text-muted-foreground size-4 shrink-0\"\n />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"min-w-56 rounded-lg\" side=\"bottom\" align=\"end\" sideOffset={4}>\n {tenancy.tenants.length > 0 && (\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">{label}</DropdownMenuLabel>\n )}\n {tenancy.tenants.map((tenant) => (\n <DropdownMenuItem key={String(tenant.id)} onSelect={() => router.visit(tenant.url)}>\n <Avatar className=\"size-6 rounded-sm\">\n {tenant.avatar && <AvatarImage src={tenant.avatar} alt=\"\" />}\n <AvatarFallback className=\"rounded-sm text-[10px]\">\n {initials(tenantNameText(tenant))}\n </AvatarFallback>\n </Avatar>\n <TenantName tenant={tenant} className=\"flex-1 truncate\" />\n </DropdownMenuItem>\n ))}\n {tenancy.registrationUrl && (\n <>\n {tenancy.tenants.length > 0 && <DropdownMenuSeparator />}\n <DropdownMenuItem onSelect={() => router.visit(tenancy.registrationUrl!)}>\n <Icon name=\"alfakit::panels.tenant-menu.registration-button\" fallback={PlusIcon} />\n {t('tenancy.register', {}, 'New')}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"widgets-loaded.js","names":["DropdownMenu","Avatar"],"sources":["../../src/starter-kit/widgets-loaded.tsx"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n Translator,\n initials,\n isAlfakitPage,\n loadStarterKitPanel,\n loadedStarterKitPanel,\n openRequestedStarterKitSearch,\n starterKitWidgetsPage,\n starterKitWidgetsPanel,\n tenantNameText,\n type PanelSchema,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { router, usePage } from '@inertiajs/react'\nimport { ChevronsUpDownIcon, PlusIcon } from 'lucide-react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useMemo, useState, type ReactNode } from 'react'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { RealtimeNotifications } from '../components/notifications/realtime-notifications'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { TenantName } from '../components/panel/nav-label'\nimport { TenantMenu } from '../components/panel/tenant-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '../components/ui/avatar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../components/ui/dropdown-menu'\nimport { TooltipProvider } from '../components/ui/tooltip'\nimport {\n AlfakitProvider,\n StandaloneProvider,\n useT,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { Icon } from '../lib/icons'\nimport {\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * The loaded header components of starter kit mode: the panel's own search palette,\n * notifications menu and tenant menu, rendered outside Alfakit pages with the panel data\n * from `widgets.endpoint`. This module only loads in the browser.\n */\n\n// One context for the browser session: its own translator (the panel's strings), an empty\n// registry (only `panel:icon` overrides are looked up here) and an idle action runner.\nlet widgetsContext: AlfakitContextValue | null = null\n\nfunction useWidgetsContext(panel: PanelSchema): AlfakitContextValue {\n widgetsContext ??= {\n registry: new ComponentRegistry<AnyComponent>(),\n pages: {},\n translator: new Translator(),\n actions: new ActionRunner({ endpoints: { action: '', modal: '' } }),\n }\n\n widgetsContext.translator.setTranslations(panel.translations ?? {})\n widgetsContext.translator.setLocale(panel.localization?.tag ?? 'en')\n\n return widgetsContext\n}\n\n/** The panel the components render with, once its data has loaded (null until then). */\nfunction useWidgetsPanel(source: StarterKitWidgets): PanelSchema | null {\n const endpoint = source.widgets.endpoint\n const [loaded, setLoaded] = useState(() => loadedStarterKitPanel(endpoint))\n const base = loadedStarterKitPanel(endpoint) ?? loaded\n\n useEffect(() => {\n let active = true\n\n // Another endpoint (a tenant switch) loads its own panel data.\n void loadStarterKitPanel(endpoint).then(\n (panel) => active && setLoaded(panel),\n () => {},\n )\n\n return () => {\n active = false\n }\n }, [endpoint])\n\n const { navigation, widgets } = source\n\n return useMemo(\n () => (base ? starterKitWidgetsPanel(base, { navigation, widgets }) : null),\n [base, navigation, widgets],\n )\n}\n\nfunction WidgetShell({ panel, children }: { panel: PanelSchema; children: ReactNode }) {\n const context = useWidgetsContext(panel)\n const page = useMemo(() => starterKitWidgetsPage(), [])\n\n return (\n <AlfakitProvider value={context}>\n <StandaloneProvider value={{ panel, page }}>\n <Direction.Provider dir={panel.localization?.direction ?? 'ltr'}>\n <TooltipProvider delayDuration={0}>{children}</TooltipProvider>\n </Direction.Provider>\n </StandaloneProvider>\n </AlfakitProvider>\n )\n}\n\n/** The panel's command palette and global search. */\nexport function LoadedSearch({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n // A click or the key bindings before the palette loaded open it now (after its own\n // effects registered the shortcut).\n useEffect(() => {\n if (panel) openRequestedStarterKitSearch()\n }, [panel])\n\n if (!panel) return <SearchTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <GlobalSearch />\n </WidgetShell>\n )\n}\n\n/** The notifications bell, with realtime notifications on the host's own pages. */\nexport function LoadedNotifications({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // Alfakit pages subscribe to realtime notifications themselves.\n const alfakitPage = isAlfakitPage(usePage().props)\n\n if (!panel) return <NotificationsTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <NotificationsMenu />\n {!alfakitPage && <RealtimeNotifications />}\n </WidgetShell>\n )\n}\n\n/** The panel's tenant menu inside a tenant, a menu to choose one elsewhere. */\nexport function LoadedTenantSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <TenantSwitcherTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n {panel.tenancy?.current ? <TenantMenu variant=\"topbar\" /> : <TenantChooser panel={panel} />}\n </WidgetShell>\n )\n}\n\n/** The user's tenants on a page outside the panel (no current tenant). */\nfunction TenantChooser({ panel }: { panel: PanelSchema }) {\n const t = useT()\n const tenancy = panel.tenancy\n\n if (!tenancy || (tenancy.tenants.length === 0 && !tenancy.registrationUrl)) return null\n\n const label = t('starter_kit.choose_tenant', {}, 'Choose a tenant')\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"tenant-menu\"\n className=\"hover:bg-accent data-[state=open]:bg-accent inline-flex h-9 max-w-60 shrink-0 items-center gap-2 rounded-md px-2 text-sm font-medium\"\n >\n <span className=\"sr-only lg:not-sr-only lg:truncate\">{label}</span>\n <Icon\n name=\"alfakit::panels.tenant-menu.toggle-button\"\n fallback={ChevronsUpDownIcon}\n className=\"text-muted-foreground size-4 shrink-0\"\n />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"min-w-56 rounded-lg\" side=\"bottom\" align=\"end\" sideOffset={4}>\n {tenancy.tenants.length > 0 && (\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">{label}</DropdownMenuLabel>\n )}\n {tenancy.tenants.map((tenant) => (\n <DropdownMenuItem key={String(tenant.id)} onSelect={() => router.visit(tenant.url)}>\n <Avatar className=\"size-6 rounded-sm\">\n {tenant.avatar && <AvatarImage src={tenant.avatar} alt=\"\" />}\n <AvatarFallback className=\"rounded-sm text-[10px]\">\n {initials(tenantNameText(tenant))}\n </AvatarFallback>\n </Avatar>\n <TenantName tenant={tenant} className=\"flex-1 truncate\" />\n </DropdownMenuItem>\n ))}\n {tenancy.registrationUrl && (\n <>\n {tenancy.tenants.length > 0 && <DropdownMenuSeparator />}\n <DropdownMenuItem onSelect={() => router.visit(tenancy.registrationUrl!)}>\n <Icon name=\"alfakit::panels.tenant-menu.registration-button\" fallback={PlusIcon} />\n {tenancy.registrationLabel ?? t('tenancy.register', {}, 'New')}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"],"mappings":"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"}
@@ -35,6 +35,7 @@ function SearchTrigger({ source, onLoad }) {
35
35
  children: placeholder
36
36
  }),
37
37
  hydrated && binding && /* @__PURE__ */ jsx("kbd", {
38
+ dir: "ltr",
38
39
  className: "bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline",
39
40
  children: formatKeyBinding(binding)
40
41
  })
@@ -1 +1 @@
1
- {"version":3,"file":"widgets-triggers.js","names":[],"sources":["../../src/starter-kit/widgets-triggers.tsx"],"sourcesContent":["import {\n formatKeyBinding,\n initials,\n registerStarterKitSearchShortcut,\n requestStarterKitSearch,\n tenantNameText,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { BellIcon, ChevronsUpDownIcon, SearchIcon } from 'lucide-react'\nimport { useEffect } from 'react'\nimport { useHydrated } from '../hooks/use-hydrated'\n\n/**\n * What the starter kit header components show until their code and the panel data have\n * loaded: the same look as the loaded triggers, from the shared prop alone.\n */\nexport interface WidgetProps {\n source: StarterKitWidgets\n}\n\nconst triggerClasses =\n 'border-input bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 inline-flex h-8 shrink-0 items-center rounded-md border text-sm shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'\n\n/** The search field until the palette has loaded: the same look, and it remembers a click. */\nexport function SearchTrigger({ source, onLoad }: WidgetProps & { onLoad?: () => void }) {\n const hydrated = useHydrated()\n const search = source.widgets.search\n const keys = JSON.stringify(search?.keyBindings ?? ['mod+k'])\n const placeholder = search?.placeholder ?? ''\n // `onLoad` is a stable function (it starts loading the palette's code).\n useEffect(\n () =>\n registerStarterKitSearchShortcut(JSON.parse(keys) as string[], placeholder, () => onLoad?.()),\n [keys, placeholder, onLoad],\n )\n\n const binding = search?.keyBindings[0]\n\n return (\n <button\n type=\"button\"\n className={`${triggerClasses} text-muted-foreground size-8 justify-center gap-2 px-0 font-normal sm:w-56 sm:justify-start sm:px-2.5`}\n onPointerEnter={() => onLoad?.()}\n onClick={() => {\n requestStarterKitSearch()\n onLoad?.()\n }}\n >\n <SearchIcon className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"sr-only sm:not-sr-only sm:flex-1 sm:text-start\">{placeholder}</span>\n {hydrated && binding && (\n <kbd className=\"bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline\">\n {formatKeyBinding(binding)}\n </kbd>\n )}\n </button>\n )\n}\n\n/** The bell and its unread count until the menu has loaded. */\nexport function NotificationsTrigger({ source }: WidgetProps) {\n const notifications = source.widgets.notifications\n const count = notifications?.unreadCount ?? 0\n\n return (\n <button\n type=\"button\"\n data-slot=\"notifications-trigger\"\n aria-label={notifications?.label}\n className=\"hover:bg-accent hover:text-accent-foreground relative inline-flex size-9 items-center justify-center rounded-md\"\n >\n <BellIcon className=\"size-4\" aria-hidden=\"true\" />\n {count > 0 && (\n <span className=\"bg-primary text-primary-foreground absolute -end-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium\">\n {count > 99 ? '99+' : count}\n </span>\n )}\n </button>\n )\n}\n\n/** The current tenant (or an empty avatar) until the switcher has loaded: a placeholder. */\nexport function TenantSwitcherTrigger({ source }: WidgetProps) {\n const current = source.widgets.tenancy?.current\n\n return (\n <button\n type=\"button\"\n data-slot=\"tenant-menu\"\n aria-hidden=\"true\"\n tabIndex={-1}\n className=\"hover:bg-accent inline-flex h-9 max-w-60 shrink-0 items-center gap-2 rounded-md px-2 text-sm font-medium\"\n >\n <span className=\"bg-muted flex size-6 items-center justify-center rounded-md text-[10px]\">\n {current ? initials(tenantNameText(current)) : ''}\n </span>\n {current && (\n <span className=\"sr-only min-w-0 truncate lg:not-sr-only\">{tenantNameText(current)}</span>\n )}\n <ChevronsUpDownIcon className=\"text-muted-foreground size-4 shrink-0\" aria-hidden=\"true\" />\n </button>\n )\n}\n"],"mappings":";;;;;;AAoBA,MAAM,iBACJ;;AAGF,SAAgB,cAAc,EAAE,QAAQ,UAAiD;CACvF,MAAM,WAAW,YAAY;CAC7B,MAAM,SAAS,OAAO,QAAQ;CAC9B,MAAM,OAAO,KAAK,UAAU,QAAQ,eAAe,CAAC,OAAO,CAAC;CAC5D,MAAM,cAAc,QAAQ,eAAe;CAE3C,gBAEI,iCAAiC,KAAK,MAAM,IAAI,GAAe,mBAAmB,SAAS,CAAC,GAC9F;EAAC;EAAM;EAAa;CAAM,CAC5B;CAEA,MAAM,UAAU,QAAQ,YAAY;CAEpC,OACE,qBAAC,UAAD;EACE,MAAK;EACL,WAAW,GAAG,eAAe;EAC7B,sBAAsB,SAAS;EAC/B,eAAe;GACb,wBAAwB;GACxB,SAAS;EACX;EAPF,UAAA;GASE,oBAAC,YAAD;IAAY,WAAU;IAAS,eAAY;GAAQ,CAAA;GACnD,oBAAC,QAAD;IAAM,WAAU;IAAkD,UAAA;GAAkB,CAAA;GACnF,YAAY,WACX,oBAAC,OAAD;IAAK,WAAU;IACZ,UAAA,iBAAiB,OAAO;GACtB,CAAA;EAED;;AAEZ;;AAGA,SAAgB,qBAAqB,EAAE,UAAuB;CAC5D,MAAM,gBAAgB,OAAO,QAAQ;CACrC,MAAM,QAAQ,eAAe,eAAe;CAE5C,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAU;EACV,cAAY,eAAe;EAC3B,WAAU;EAJZ,UAAA,CAME,oBAAC,UAAD;GAAU,WAAU;GAAS,eAAY;EAAQ,CAAA,GAChD,QAAQ,KACP,oBAAC,QAAD;GAAM,WAAU;GACb,UAAA,QAAQ,KAAK,QAAQ;EAClB,CAAA,CAEF;;AAEZ;;AAGA,SAAgB,sBAAsB,EAAE,UAAuB;CAC7D,MAAM,UAAU,OAAO,QAAQ,SAAS;CAExC,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAU;EACV,eAAY;EACZ,UAAU;EACV,WAAU;EALZ,UAAA;GAOE,oBAAC,QAAD;IAAM,WAAU;IACb,UAAA,UAAU,SAAS,eAAe,OAAO,CAAC,IAAI;GAC3C,CAAA;GACL,WACC,oBAAC,QAAD;IAAM,WAAU;IAA2C,UAAA,eAAe,OAAO;GAAQ,CAAA;GAE3F,oBAAC,oBAAD;IAAoB,WAAU;IAAwC,eAAY;GAAQ,CAAA;EACpF;;AAEZ"}
1
+ {"version":3,"file":"widgets-triggers.js","names":[],"sources":["../../src/starter-kit/widgets-triggers.tsx"],"sourcesContent":["import {\n formatKeyBinding,\n initials,\n registerStarterKitSearchShortcut,\n requestStarterKitSearch,\n tenantNameText,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { BellIcon, ChevronsUpDownIcon, SearchIcon } from 'lucide-react'\nimport { useEffect } from 'react'\nimport { useHydrated } from '../hooks/use-hydrated'\n\n/**\n * What the starter kit header components show until their code and the panel data have\n * loaded: the same look as the loaded triggers, from the shared prop alone.\n */\nexport interface WidgetProps {\n source: StarterKitWidgets\n}\n\nconst triggerClasses =\n 'border-input bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 inline-flex h-8 shrink-0 items-center rounded-md border text-sm shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'\n\n/** The search field until the palette has loaded: the same look, and it remembers a click. */\nexport function SearchTrigger({ source, onLoad }: WidgetProps & { onLoad?: () => void }) {\n const hydrated = useHydrated()\n const search = source.widgets.search\n const keys = JSON.stringify(search?.keyBindings ?? ['mod+k'])\n const placeholder = search?.placeholder ?? ''\n // `onLoad` is a stable function (it starts loading the palette's code).\n useEffect(\n () =>\n registerStarterKitSearchShortcut(JSON.parse(keys) as string[], placeholder, () => onLoad?.()),\n [keys, placeholder, onLoad],\n )\n\n const binding = search?.keyBindings[0]\n\n return (\n <button\n type=\"button\"\n className={`${triggerClasses} text-muted-foreground size-8 justify-center gap-2 px-0 font-normal sm:w-56 sm:justify-start sm:px-2.5`}\n onPointerEnter={() => onLoad?.()}\n onClick={() => {\n requestStarterKitSearch()\n onLoad?.()\n }}\n >\n <SearchIcon className=\"size-4\" aria-hidden=\"true\" />\n <span className=\"sr-only sm:not-sr-only sm:flex-1 sm:text-start\">{placeholder}</span>\n {hydrated && binding && (\n <kbd dir=\"ltr\" className=\"bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline\">\n {formatKeyBinding(binding)}\n </kbd>\n )}\n </button>\n )\n}\n\n/** The bell and its unread count until the menu has loaded. */\nexport function NotificationsTrigger({ source }: WidgetProps) {\n const notifications = source.widgets.notifications\n const count = notifications?.unreadCount ?? 0\n\n return (\n <button\n type=\"button\"\n data-slot=\"notifications-trigger\"\n aria-label={notifications?.label}\n className=\"hover:bg-accent hover:text-accent-foreground relative inline-flex size-9 items-center justify-center rounded-md\"\n >\n <BellIcon className=\"size-4\" aria-hidden=\"true\" />\n {count > 0 && (\n <span className=\"bg-primary text-primary-foreground absolute -end-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium\">\n {count > 99 ? '99+' : count}\n </span>\n )}\n </button>\n )\n}\n\n/** The current tenant (or an empty avatar) until the switcher has loaded: a placeholder. */\nexport function TenantSwitcherTrigger({ source }: WidgetProps) {\n const current = source.widgets.tenancy?.current\n\n return (\n <button\n type=\"button\"\n data-slot=\"tenant-menu\"\n aria-hidden=\"true\"\n tabIndex={-1}\n className=\"hover:bg-accent inline-flex h-9 max-w-60 shrink-0 items-center gap-2 rounded-md px-2 text-sm font-medium\"\n >\n <span className=\"bg-muted flex size-6 items-center justify-center rounded-md text-[10px]\">\n {current ? initials(tenantNameText(current)) : ''}\n </span>\n {current && (\n <span className=\"sr-only min-w-0 truncate lg:not-sr-only\">{tenantNameText(current)}</span>\n )}\n <ChevronsUpDownIcon className=\"text-muted-foreground size-4 shrink-0\" aria-hidden=\"true\" />\n </button>\n )\n}\n"],"mappings":";;;;;;AAoBA,MAAM,iBACJ;;AAGF,SAAgB,cAAc,EAAE,QAAQ,UAAiD;CACvF,MAAM,WAAW,YAAY;CAC7B,MAAM,SAAS,OAAO,QAAQ;CAC9B,MAAM,OAAO,KAAK,UAAU,QAAQ,eAAe,CAAC,OAAO,CAAC;CAC5D,MAAM,cAAc,QAAQ,eAAe;CAE3C,gBAEI,iCAAiC,KAAK,MAAM,IAAI,GAAe,mBAAmB,SAAS,CAAC,GAC9F;EAAC;EAAM;EAAa;CAAM,CAC5B;CAEA,MAAM,UAAU,QAAQ,YAAY;CAEpC,OACE,qBAAC,UAAD;EACE,MAAK;EACL,WAAW,GAAG,eAAe;EAC7B,sBAAsB,SAAS;EAC/B,eAAe;GACb,wBAAwB;GACxB,SAAS;EACX;EAPF,UAAA;GASE,oBAAC,YAAD;IAAY,WAAU;IAAS,eAAY;GAAQ,CAAA;GACnD,oBAAC,QAAD;IAAM,WAAU;IAAkD,UAAA;GAAkB,CAAA;GACnF,YAAY,WACX,oBAAC,OAAD;IAAK,KAAI;IAAM,WAAU;IACtB,UAAA,iBAAiB,OAAO;GACtB,CAAA;EAED;;AAEZ;;AAGA,SAAgB,qBAAqB,EAAE,UAAuB;CAC5D,MAAM,gBAAgB,OAAO,QAAQ;CACrC,MAAM,QAAQ,eAAe,eAAe;CAE5C,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAU;EACV,cAAY,eAAe;EAC3B,WAAU;EAJZ,UAAA,CAME,oBAAC,UAAD;GAAU,WAAU;GAAS,eAAY;EAAQ,CAAA,GAChD,QAAQ,KACP,oBAAC,QAAD;GAAM,WAAU;GACb,UAAA,QAAQ,KAAK,QAAQ;EAClB,CAAA,CAEF;;AAEZ;;AAGA,SAAgB,sBAAsB,EAAE,UAAuB;CAC7D,MAAM,UAAU,OAAO,QAAQ,SAAS;CAExC,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAU;EACV,eAAY;EACZ,UAAU;EACV,WAAU;EALZ,UAAA;GAOE,oBAAC,QAAD;IAAM,WAAU;IACb,UAAA,UAAU,SAAS,eAAe,OAAO,CAAC,IAAI;GAC3C,CAAA;GACL,WACC,oBAAC,QAAD;IAAM,WAAU;IAA2C,UAAA,eAAe,OAAO;GAAQ,CAAA;GAE3F,oBAAC,oBAAD;IAAoB,WAAU;IAAwC,eAAY;GAAQ,CAAA;EACpF;;AAEZ"}
package/dist/themes.css CHANGED
@@ -1,3 +1,5 @@
1
+ @import './components.css';
2
+
1
3
  /*
2
4
  * Alfakit themes. Shared by every renderer and imported by their `styles.css`.
3
5
  *
@@ -1004,337 +1006,3 @@
1004
1006
  box-shadow: none;
1005
1007
  outline: none;
1006
1008
  }
1007
-
1008
- /* ------------------------------------------------------------------ Charts */
1009
-
1010
- /*
1011
- * Chart.js paints axes, legends and tooltips on the canvas, so chart widgets read these
1012
- * properties from <html> (re-read when the theme or dark mode changes). Colours take any
1013
- * CSS colour; the widths and radius are unitless numbers. Override them in a theme:
1014
- * :root[data-alfakit-theme~='acme'] { --chart-tooltip-bg: oklch(0.2 0 0); }
1015
- * Stat sparklines (`[data-slot='stat-chart']`) use the `--stat-chart-*` properties.
1016
- */
1017
- :root {
1018
- --chart-text: var(--muted-foreground);
1019
- --chart-grid: var(--border);
1020
- --chart-legend-foreground: var(--muted-foreground);
1021
- --chart-tooltip-bg: var(--popover);
1022
- --chart-tooltip-foreground: var(--popover-foreground);
1023
- --chart-tooltip-border: var(--border);
1024
- --chart-tooltip-border-width: 1;
1025
- --chart-tooltip-radius: 6;
1026
- --stat-chart-stroke-width: 2;
1027
- --stat-chart-fill-opacity: 0.12;
1028
- }
1029
-
1030
- /*
1031
- * RichEditor `grid` column blocks and `lead` / `small` text, in the editor and in rendered
1032
- * content (`RichContentRenderer`). Columns stack on small screens. Stored as
1033
- * `<div data-type="grid" class="alfakit-grid alfakit-grid-cols-2"><div data-type="gridColumn">`
1034
- * and `<p class="alfakit-lead">`; copy these rules when showing content outside a panel.
1035
- */
1036
- :where(.alfakit-grid) {
1037
- display: grid;
1038
- gap: 1rem;
1039
- grid-template-columns: minmax(0, 1fr);
1040
- margin-block: 0.5rem;
1041
- }
1042
- :where(.alfakit-grid-column) {
1043
- min-width: 0;
1044
- }
1045
- :where(.alfakit-grid-column) > :first-child {
1046
- margin-block-start: 0;
1047
- }
1048
- @media (min-width: 768px) {
1049
- :where(.alfakit-grid-cols-2) {
1050
- grid-template-columns: repeat(2, minmax(0, 1fr));
1051
- }
1052
- :where(.alfakit-grid-cols-3) {
1053
- grid-template-columns: repeat(3, minmax(0, 1fr));
1054
- }
1055
- :where(.alfakit-grid-cols-4) {
1056
- grid-template-columns: repeat(4, minmax(0, 1fr));
1057
- }
1058
- :where(.alfakit-grid-cols-5) {
1059
- grid-template-columns: repeat(5, minmax(0, 1fr));
1060
- }
1061
- :where(.alfakit-grid-cols-6) {
1062
- grid-template-columns: repeat(6, minmax(0, 1fr));
1063
- }
1064
- }
1065
- :where([contenteditable] .alfakit-grid-column) {
1066
- border: 1px dashed var(--border);
1067
- border-radius: 0.375rem;
1068
- padding: 0.25rem 0.5rem;
1069
- }
1070
- :where(.alfakit-lead) {
1071
- font-size: 1.125em;
1072
- line-height: 1.6;
1073
- }
1074
- :where(.alfakit-prose small) {
1075
- font-size: 0.8125em;
1076
- }
1077
-
1078
- /* ---------------------------------------------------------- Calendar and tree */
1079
-
1080
- /*
1081
- * The calendar widget (alfasix/alfakit-calendar) draws FullCalendar inside
1082
- * `[data-slot="calendar"]`; FullCalendar reads these `--fc-*` variables, so every theme's
1083
- * tokens (and dark mode) apply. Override them on the slot to restyle one calendar.
1084
- */
1085
- :where(.fc[data-slot='calendar']) {
1086
- --fc-small-font-size: 0.75rem;
1087
- --fc-page-bg-color: var(--card);
1088
- --fc-neutral-bg-color: var(--muted);
1089
- --fc-neutral-text-color: var(--muted-foreground);
1090
- --fc-border-color: var(--border);
1091
- --fc-button-text-color: var(--foreground);
1092
- --fc-button-bg-color: var(--background);
1093
- --fc-button-border-color: var(--border);
1094
- --fc-button-hover-bg-color: var(--accent);
1095
- --fc-button-hover-border-color: var(--border);
1096
- --fc-button-active-bg-color: var(--secondary);
1097
- --fc-button-active-border-color: var(--border);
1098
- --fc-event-bg-color: var(--primary);
1099
- --fc-event-border-color: var(--primary);
1100
- --fc-event-text-color: var(--primary-foreground);
1101
- --fc-event-selected-overlay-color: color-mix(in oklab, var(--foreground) 25%, transparent);
1102
- --fc-more-link-bg-color: var(--muted);
1103
- --fc-more-link-text-color: var(--foreground);
1104
- --fc-non-business-color: color-mix(in oklab, var(--muted) 60%, transparent);
1105
- --fc-bg-event-color: var(--primary);
1106
- --fc-bg-event-opacity: 0.2;
1107
- --fc-highlight-color: color-mix(in oklab, var(--primary) 15%, transparent);
1108
- --fc-today-bg-color: color-mix(in oklab, var(--primary) 6%, transparent);
1109
- --fc-now-indicator-color: var(--destructive);
1110
- --fc-list-event-hover-bg-color: var(--accent);
1111
- color: var(--card-foreground);
1112
- }
1113
- /*
1114
- * FullCalendar puts its `fc` class on the slot element itself and injects its own CSS
1115
- * (`.fc .fc-button`, two classes), so these rules are plain selectors anchored on
1116
- * `.fc[data-slot='calendar']` (three) rather than `:where()`, which would always lose.
1117
- * Restyle one calendar with a more specific selector or with the `--fc-*` variables.
1118
- */
1119
- .fc[data-slot='calendar'] .fc-toolbar-title {
1120
- font-size: 1.125rem;
1121
- font-weight: 600;
1122
- line-height: 1.75rem;
1123
- color: var(--foreground);
1124
- }
1125
- .fc[data-slot='calendar'] .fc-button {
1126
- display: inline-flex;
1127
- align-items: center;
1128
- justify-content: center;
1129
- min-height: 2.25rem;
1130
- padding: 0.375rem 0.75rem;
1131
- border-radius: var(--radius);
1132
- font-size: 0.875rem;
1133
- font-weight: 500;
1134
- line-height: 1.25rem;
1135
- box-shadow: none;
1136
- text-transform: capitalize;
1137
- }
1138
- .fc[data-slot='calendar'] .fc-button .fc-icon {
1139
- font-size: 1.125rem;
1140
- }
1141
- .fc[data-slot='calendar'] .fc-button-group > .fc-button:not(:first-child) {
1142
- border-start-start-radius: 0;
1143
- border-end-start-radius: 0;
1144
- }
1145
- .fc[data-slot='calendar'] .fc-button-group > .fc-button:not(:last-child) {
1146
- border-start-end-radius: 0;
1147
- border-end-end-radius: 0;
1148
- }
1149
- .fc[data-slot='calendar'] .fc-button-primary:disabled {
1150
- opacity: 0.5;
1151
- }
1152
- .fc[data-slot='calendar'] .fc-button-primary:not(:disabled).fc-button-active,
1153
- .fc[data-slot='calendar'] .fc-button-primary:not(:disabled):active {
1154
- color: var(--secondary-foreground);
1155
- }
1156
- .fc[data-slot='calendar'] .fc-button-primary:focus,
1157
- .fc[data-slot='calendar'] .fc-button-primary:not(:disabled).fc-button-active:focus,
1158
- .fc[data-slot='calendar'] .fc-button-primary:not(:disabled):active:focus {
1159
- box-shadow: none;
1160
- }
1161
- .fc[data-slot='calendar'] .fc-button:focus-visible {
1162
- outline: 2px solid var(--ring);
1163
- outline-offset: 2px;
1164
- z-index: 2;
1165
- }
1166
- .fc[data-slot='calendar'] a {
1167
- color: inherit;
1168
- }
1169
- .fc[data-slot='calendar'] .fc-daygrid-day-number,
1170
- .fc[data-slot='calendar'] .fc-col-header-cell-cushion {
1171
- text-decoration: none;
1172
- }
1173
- .fc[data-slot='calendar'] .fc-col-header-cell-cushion {
1174
- padding-block: 0.375rem;
1175
- font-size: 0.75rem;
1176
- font-weight: 500;
1177
- color: var(--muted-foreground);
1178
- }
1179
- .fc[data-slot='calendar'] .fc-daygrid-day-number {
1180
- font-size: 0.875rem;
1181
- }
1182
- .fc[data-slot='calendar'] .fc-day-other .fc-daygrid-day-number {
1183
- color: var(--muted-foreground);
1184
- }
1185
- /* Today: a tinted cell (`--fc-today-bg-color`) and the date in a primary pill. */
1186
- .fc[data-slot='calendar'] .fc-daygrid-day.fc-day-today .fc-daygrid-day-number {
1187
- display: inline-flex;
1188
- align-items: center;
1189
- justify-content: center;
1190
- min-width: 1.75rem;
1191
- height: 1.75rem;
1192
- margin: 0.25rem;
1193
- padding: 0 0.375rem;
1194
- border-radius: 9999px;
1195
- background: var(--primary);
1196
- color: var(--primary-foreground);
1197
- font-weight: 600;
1198
- }
1199
- .fc[data-slot='calendar'] .fc-col-header-cell.fc-day-today .fc-col-header-cell-cushion {
1200
- color: var(--primary);
1201
- font-weight: 600;
1202
- }
1203
- .fc[data-slot='calendar'] .fc-event {
1204
- border-radius: min(var(--radius), 0.375rem);
1205
- }
1206
- .fc[data-slot='calendar'] .fc-daygrid-event,
1207
- .fc[data-slot='calendar'] .fc-timegrid-event .fc-event-main,
1208
- .fc[data-slot='calendar'] .fc-list-event td {
1209
- font-size: 0.75rem;
1210
- }
1211
- .fc[data-slot='calendar'] .fc-daygrid-dot-event:hover,
1212
- .fc[data-slot='calendar'] .fc-daygrid-dot-event.fc-event-mirror {
1213
- background: var(--accent);
1214
- }
1215
- .fc[data-slot='calendar'] .fc-list-day-cushion,
1216
- .fc[data-slot='calendar'] .fc-popover-header {
1217
- color: var(--foreground);
1218
- }
1219
- .fc[data-slot='calendar'] .fc-popover {
1220
- border-radius: var(--radius);
1221
- box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1);
1222
- overflow: hidden;
1223
- }
1224
- .fc[data-slot='calendar'] .fc-timegrid-slot-label,
1225
- .fc[data-slot='calendar'] .fc-timegrid-axis,
1226
- .fc[data-slot='calendar'] .fc-list-event-time {
1227
- color: var(--muted-foreground);
1228
- font-size: 0.75rem;
1229
- }
1230
- /*
1231
- * The list view hides its column headers for screen readers with `left: -10000px`. Right to
1232
- * left that is scrollable overflow: the whole page gains a 10 000 px horizontal scroll and the
1233
- * top bar slides under the sidebar. Clip it in place instead (still read by screen readers).
1234
- */
1235
- .fc[data-slot='calendar'] .fc-list-table thead {
1236
- left: auto;
1237
- inset-inline-start: 0;
1238
- width: 1px;
1239
- height: 1px;
1240
- overflow: hidden;
1241
- clip-path: inset(50%);
1242
- white-space: nowrap;
1243
- }
1244
-
1245
- /*
1246
- * Fields contain what they position. Headless switches, checkboxes and radios render a
1247
- * visually hidden native input beside the control, absolutely positioned (and shifted by
1248
- * -100%); without a positioned field it lands at the page edge and, right to left, widens
1249
- * the page on phones.
1250
- */
1251
- :where([data-slot='field']) {
1252
- position: relative;
1253
- }
1254
-
1255
- /*
1256
- * Narrow screens: FullCalendar keeps its header toolbar on one row, wider than a phone.
1257
- * Its chunks (navigation, title, views) wrap instead, and the title shrinks. Flex gaps and
1258
- * `justify-content` follow the writing direction, so this works right to left too. (Plain
1259
- * selectors: FullCalendar injects its own CSS, which would beat `:where()`; the slot is
1260
- * the `.fc` element itself.)
1261
- */
1262
- @media (max-width: 39.99rem) {
1263
- [data-slot='calendar'] .fc-toolbar.fc-header-toolbar {
1264
- flex-wrap: wrap;
1265
- gap: 0.5rem;
1266
- }
1267
- [data-slot='calendar'] .fc-toolbar .fc-toolbar-chunk {
1268
- display: flex;
1269
- flex-wrap: wrap;
1270
- align-items: center;
1271
- gap: 0.25rem;
1272
- min-width: 0;
1273
- }
1274
- [data-slot='calendar'] .fc-toolbar .fc-toolbar-title {
1275
- font-size: 1rem;
1276
- overflow-wrap: anywhere;
1277
- }
1278
- [data-slot='calendar'] .fc-toolbar .fc-button {
1279
- font-size: 0.8125rem;
1280
- }
1281
- }
1282
-
1283
- /*
1284
- * The tree (alfasix/alfakit-tree): the drop indicator while dragging. Rows carry
1285
- * `data-drop="before|after|inside"`; lines are drawn above or below the row, so they
1286
- * need no direction.
1287
- */
1288
- :where([data-slot='tree-item'][data-drop='before']) {
1289
- box-shadow: inset 0 2px 0 0 var(--primary);
1290
- }
1291
- :where([data-slot='tree-item'][data-drop='after']) {
1292
- box-shadow: inset 0 -2px 0 0 var(--primary);
1293
- }
1294
- :where([data-slot='tree-item'][data-drop='inside']) {
1295
- background-color: color-mix(in oklab, var(--primary) 10%, transparent);
1296
- outline: 1px dashed var(--primary);
1297
- outline-offset: -1px;
1298
- }
1299
-
1300
- /*
1301
- * The Kanban board (alfasix/alfakit-kanban). A card being dragged leaves a dashed
1302
- * placeholder (`data-picked="pointer"`) while a copy follows the pointer
1303
- * (`data-slot="kanban-ghost"`); a card picked up with the keyboard is lifted
1304
- * (`data-picked="keyboard"`). A list the picked card may not enter has `data-refused`.
1305
- */
1306
- :where([data-slot='kanban-card'][data-picked='pointer']) {
1307
- opacity: 0.45;
1308
- }
1309
- :where([data-slot='kanban-card'][data-picked='pointer'] > [data-slot='kanban-card-body']) {
1310
- border-style: dashed;
1311
- box-shadow: none;
1312
- }
1313
- :where([data-slot='kanban-card'][data-picked='keyboard'] > [data-slot='kanban-card-body']) {
1314
- outline: 2px solid var(--primary);
1315
- outline-offset: 2px;
1316
- box-shadow: 0 10px 24px -12px color-mix(in oklab, var(--foreground) 45%, transparent);
1317
- }
1318
- :where([data-slot='kanban-card'][data-saving]) {
1319
- opacity: 0.7;
1320
- }
1321
- :where([data-slot='kanban-ghost']) {
1322
- cursor: grabbing;
1323
- list-style: none;
1324
- box-shadow: 0 18px 40px -16px color-mix(in oklab, var(--foreground) 55%, transparent);
1325
- }
1326
- :where([data-slot='kanban-dropzone'][data-refused]) {
1327
- background-color: color-mix(in oklab, var(--destructive) 8%, transparent);
1328
- outline: 1px dashed var(--destructive);
1329
- outline-offset: -1px;
1330
- }
1331
- :where(body[data-kanban-dragging]) {
1332
- cursor: grabbing;
1333
- user-select: none;
1334
- -webkit-user-select: none;
1335
- }
1336
- @media (prefers-reduced-motion: reduce) {
1337
- :where([data-slot='kanban-card'] > [data-slot='kanban-card-body']) {
1338
- transition: none;
1339
- }
1340
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfakit/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React + shadcn/ui renderer for Alfakit, the Laravel admin panel builder by Alfa Six.",
5
5
  "license": "MIT",
6
6
  "author": "Alfa Six (https://alfasixbh.com)",
@@ -96,7 +96,7 @@
96
96
  "react-day-picker": "^10.0.2",
97
97
  "sonner": "^2.0.8",
98
98
  "tailwind-merge": "^3.7.0",
99
- "@alfakit/core": "0.1.0"
99
+ "@alfakit/core": "0.1.2"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "@inertiajs/react": "^3.0.0",
@@ -3,6 +3,8 @@ import {
3
3
  bindsKeyBindings,
4
4
  formatKeyBinding,
5
5
  iconButtonSize,
6
+ isActionLoading,
7
+ isActionProcessing,
6
8
  registerKeyBindings,
7
9
  resolveActionSize,
8
10
  } from '@alfakit/core'
@@ -71,7 +73,7 @@ function buttonClasses(action: ActionSchema, variant: ActionSchema['variant']):
71
73
  return colorClass(ghostButtonColorClasses, color, 'gray')
72
74
  case 'link':
73
75
  return cn(
74
- 'h-auto p-0 underline-offset-4 hover:underline',
76
+ 'h-auto p-0 underline-offset-4 hover:underline has-[>svg]:px-0',
75
77
  colorClass(textColorClasses, color, 'primary'),
76
78
  )
77
79
  case 'icon-button':
@@ -95,9 +97,10 @@ export function ActionButton({
95
97
  }: ActionButtonProps) {
96
98
  const runner = useActionRunner()
97
99
  const t = useT()
98
- const loading = useStoreSelector(runner.store, (state) => state.loading === action.name)
100
+ // Only this run (this record's button), not the same action in every table row.
101
+ const loading = useStoreSelector(runner.store, (state) => isActionLoading(state, action, context))
99
102
  const processing = useStoreSelector(runner.store, (state) =>
100
- state.modals.some((modal) => modal.action.name === action.name && modal.processing),
103
+ isActionProcessing(state, action, context),
101
104
  )
102
105
 
103
106
  const variant =
@@ -244,7 +247,7 @@ export function ActionButton({
244
247
  <TooltipContent>
245
248
  {tooltip}
246
249
  {shortcut && (
247
- <kbd className="ms-2 rounded border border-white/20 px-1 font-mono text-[10px]">
250
+ <kbd dir="ltr" className="ms-2 rounded border border-white/20 px-1 font-mono text-[10px]">
248
251
  {formatKeyBinding(shortcut)}
249
252
  </kbd>
250
253
  )}
@@ -4,6 +4,7 @@ import {
4
4
  actionFormParams,
5
5
  actionPath,
6
6
  confirmModalClose,
7
+ formIdPrefix,
7
8
  FormStore,
8
9
  guardModalUnload,
9
10
  isModalHiddenByChild,
@@ -156,6 +157,8 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
156
157
  return new FormStore(schema.form, {
157
158
  token: context.token,
158
159
  component: actionFormComponent(action, context),
160
+ // Own field ids: the modal opens over a form whose fields may share names.
161
+ idPrefix: formIdPrefix(actionFormComponent(action, context)),
159
162
  record: context.record ?? null,
160
163
  refreshUrl: panel.endpoints.schemaRefresh,
161
164
  optionsUrl: panel.endpoints.schemaOptions,
@@ -472,7 +475,6 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
472
475
  {footer}
473
476
  </DialogFooter>
474
477
  )}
475
- <span className="sr-only">{t('close', {}, 'Close')}</span>
476
478
  </DialogContent>
477
479
  </Dialog>
478
480
  )
@@ -36,6 +36,7 @@ export function ColorPickerField({ component }: { component: ColorPickerSchema }
36
36
  <Input
37
37
  id={id}
38
38
  name={component.name}
39
+ dir="ltr"
39
40
  value={value ?? ''}
40
41
  onChange={(event) => setValue(event.target.value)}
41
42
  onBlur={blur}