@alfakit/react 0.1.1 → 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.
- package/CHANGELOG.md +8 -0
- package/dist/components/actions/action-button.d.ts.map +1 -1
- package/dist/components/actions/action-button.js +5 -4
- package/dist/components/actions/action-button.js.map +1 -1
- package/dist/components/actions/action-modals.d.ts.map +1 -1
- package/dist/components/actions/action-modals.js +2 -5
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/fields/color-picker.js +1 -0
- package/dist/components/fields/color-picker.js.map +1 -1
- package/dist/components/fields/file-upload.js +11 -10
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/one-time-code-input.js +1 -1
- package/dist/components/fields/one-time-code-input.js.map +1 -1
- package/dist/components/fields/repeater-table.js +12 -1
- package/dist/components/fields/repeater-table.js.map +1 -1
- package/dist/components/fields/select.js +3 -3
- package/dist/components/fields/select.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +1 -1
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/form/form-block.d.ts.map +1 -1
- package/dist/components/form/form-block.js +13 -1
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/form/form-context.js +6 -2
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/layout/fused-group.js +1 -0
- package/dist/components/layout/fused-group.js.map +1 -1
- package/dist/components/layout/tabs.d.ts.map +1 -1
- package/dist/components/layout/tabs.js +54 -15
- package/dist/components/layout/tabs.js.map +1 -1
- package/dist/components/panel/global-search.js +1 -0
- package/dist/components/panel/global-search.js.map +1 -1
- package/dist/components/panel/shortcut-keys.js +1 -0
- package/dist/components/panel/shortcut-keys.js.map +1 -1
- package/dist/components/panel/tenant-menu.js +2 -2
- package/dist/components/panel/tenant-menu.js.map +1 -1
- package/dist/components/table/cells.js +9 -1
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/grouping.js +11 -1
- package/dist/components/table/grouping.js.map +1 -1
- package/dist/components.css +347 -0
- package/dist/eject-manifest.json +2 -2
- package/dist/standalone/alfakit-toaster.d.ts.map +1 -1
- package/dist/standalone/alfakit-toaster.js +8 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone.css +47 -0
- package/dist/starter-kit/widgets-loaded.js +1 -1
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +1 -0
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/themes.css +2 -334
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +7 -4
- package/src/components/actions/action-modals.tsx +3 -1
- package/src/components/fields/color-picker.tsx +1 -0
- package/src/components/fields/file-upload.tsx +21 -10
- package/src/components/fields/one-time-code-input.tsx +1 -1
- package/src/components/fields/repeater-table.tsx +12 -1
- package/src/components/fields/select.tsx +7 -3
- package/src/components/file-manager/file-manager.tsx +1 -1
- package/src/components/form/form-block.tsx +24 -0
- package/src/components/form/form-context.tsx +2 -5
- package/src/components/layout/fused-group.tsx +5 -1
- package/src/components/layout/tabs.tsx +72 -17
- package/src/components/panel/global-search.tsx +1 -1
- package/src/components/panel/shortcut-keys.tsx +4 -1
- package/src/components/panel/tenant-menu.tsx +2 -2
- package/src/components/table/cells.tsx +18 -3
- package/src/components/table/grouping.tsx +15 -1
- package/src/standalone/alfakit-toaster.tsx +21 -2
- package/src/starter-kit/widgets-loaded.tsx +1 -1
- 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
|
|
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"}
|
package/dist/standalone.css
CHANGED
|
@@ -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;
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
)
|