@alfakit/react 0.1.7 → 0.1.9
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 +16 -0
- package/dist/components/actions/action-modals.js +1 -1
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/form/form-block.d.ts.map +1 -1
- package/dist/components/form/form-block.js +6 -3
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/panel/app-sidebar.js +3 -3
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/bottom-nav.js +2 -2
- package/dist/components/panel/bottom-nav.js.map +1 -1
- package/dist/components/panel/keyboard-shortcuts.js +1 -1
- package/dist/components/panel/keyboard-shortcuts.js.map +1 -1
- package/dist/components/panel/locale-switcher.d.ts.map +1 -1
- package/dist/components/panel/locale-switcher.js +2 -1
- package/dist/components/panel/locale-switcher.js.map +1 -1
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/eject-manifest.json +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/layouts/top-nav-layout.js +3 -3
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/standalone/alfakit-component.js +17 -7
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone.css +10 -6
- package/dist/starter-kit/index.d.ts +2 -2
- package/dist/starter-kit/index.js +2 -2
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +7 -2
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets-loaded.js +74 -13
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +34 -3
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/starter-kit/widgets.d.ts +11 -2
- package/dist/starter-kit/widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets.js +20 -2
- package/dist/starter-kit/widgets.js.map +1 -1
- package/package.json +2 -2
- package/src/components/actions/action-modals.tsx +2 -0
- package/src/components/form/form-block.tsx +15 -2
- package/src/components/panel/app-sidebar.tsx +3 -3
- package/src/components/panel/bottom-nav.tsx +2 -2
- package/src/components/panel/keyboard-shortcuts.tsx +1 -1
- package/src/components/panel/locale-switcher.tsx +2 -1
- package/src/components/ui/tooltip.tsx +10 -1
- package/src/layouts/top-nav-layout.tsx +3 -3
- package/src/standalone/alfakit-component.tsx +11 -1
- package/src/starter-kit/index.ts +1 -0
- package/src/starter-kit/starter-kit.tsx +9 -1
- package/src/starter-kit/widgets-loaded.tsx +86 -10
- package/src/starter-kit/widgets-triggers.tsx +41 -1
- package/src/starter-kit/widgets.tsx +33 -2
|
@@ -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 starterKitCopilot,\n starterKitWidgetsPage,\n starterKitWidgetsPanel,\n takeRequestedStarterKitCopilot,\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 { AiCopilot } from '../components/ai/copilot'\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 CopilotTrigger,\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 AI copilot (the panel pages' `topbar.end` slot component). */\nexport function LoadedCopilot({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // A click or the key bindings before the copilot loaded open it at once.\n const [autoOpen] = useState(() => takeRequestedStarterKitCopilot())\n const component = useMemo(() => (panel ? starterKitCopilot(panel) : null), [panel])\n\n if (!panel) return <CopilotTrigger source={source} />\n\n if (!component) return null\n\n return (\n <WidgetShell panel={panel}>\n <AiCopilot component={component} autoOpen={autoOpen || takeRequestedStarterKitCopilot()} />\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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"}
|
|
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 showsLanguageSwitcher,\n starterKitCopilot,\n starterKitPanelLocale,\n starterKitWidgetsPage,\n starterKitWidgetsPanel,\n takeRequestedStarterKitCopilot,\n takeRequestedStarterKitLanguages,\n tenantNameText,\n type PanelSchema,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { router, usePage } from '@inertiajs/react'\nimport { ChevronsUpDownIcon, LanguagesIcon, PlusIcon } from 'lucide-react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { AiCopilot } from '../components/ai/copilot'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { RealtimeNotifications } from '../components/notifications/realtime-notifications'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { LocaleOptions } from '../components/panel/locale-switcher'\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 useLocalization,\n useT,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { Icon } from '../lib/icons'\nimport {\n CopilotTrigger,\n LanguageSwitcherTrigger,\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n languageTriggerClasses,\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, tenant menu and language menu, rendered outside Alfakit pages with\n * the panel data 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/**\n * The panel the components render with, once its data has loaded (null until then). It is\n * loaded per endpoint (a tenant switch) and language (a language switch), so the components\n * follow a switch instead of keeping the old language's strings.\n */\nfunction useWidgetsPanel(source: StarterKitWidgets): PanelSchema | null {\n const endpoint = source.widgets.endpoint\n const locale = starterKitPanelLocale(source)\n const [loaded, setLoaded] = useState(() => loadedStarterKitPanel(endpoint, locale))\n // Until the new language's data arrives, not the old language's.\n const current =\n loaded && (locale === undefined || loaded.localization?.code === locale) ? loaded : null\n const base = loadedStarterKitPanel(endpoint, locale) ?? current\n\n useEffect(() => {\n let active = true\n\n void loadStarterKitPanel(endpoint, locale).then(\n (panel) => active && setLoaded(panel),\n () => {},\n )\n\n return () => {\n active = false\n }\n }, [endpoint, locale])\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 AI copilot (the panel pages' `topbar.end` slot component). */\nexport function LoadedCopilot({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // A click or the key bindings before the copilot loaded open it at once.\n const [autoOpen] = useState(() => takeRequestedStarterKitCopilot())\n const component = useMemo(() => (panel ? starterKitCopilot(panel) : null), [panel])\n\n if (!panel) return <CopilotTrigger source={source} />\n\n if (!component) return null\n\n return (\n <WidgetShell panel={panel}>\n <AiCopilot component={component} autoOpen={autoOpen || takeRequestedStarterKitCopilot()} />\n </WidgetShell>\n )\n}\n\n/**\n * The panel's language menu (its languages in their own names, `LocaleOptions`): picking\n * one posts to the panel's locale endpoint and reloads the page in it. Nothing without a\n * language switcher with two or more languages.\n */\nexport function LoadedLanguageSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <LanguageSwitcherTrigger source={source} />\n\n if (!showsLanguageSwitcher(panel.localization, 'auth')) return null\n\n return (\n <WidgetShell panel={panel}>\n <LanguageMenu />\n </WidgetShell>\n )\n}\n\nfunction LanguageMenu() {\n const t = useT()\n const localization = useLocalization()\n const [open, setOpen] = useState(false)\n // Taken once per mount (effects run twice in StrictMode).\n const requested = useRef<boolean | null>(null)\n\n // A click on the trigger before the menu loaded opens it once mounted: a frame later, as\n // the menu closes again when it opens while focus is still on the replaced button.\n useEffect(() => {\n requested.current ??= takeRequestedStarterKitLanguages()\n\n if (!requested.current) return\n\n const frame = requestAnimationFrame(() => setOpen(true))\n\n return () => cancelAnimationFrame(frame)\n }, [])\n const label = t('change_language', {}, 'Change language')\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"language-switcher-trigger\"\n aria-label={label}\n title={label}\n className={languageTriggerClasses}\n >\n <LanguagesIcon className=\"size-4\" aria-hidden=\"true\" />\n <span lang={localization.tag} dir={localization.direction} className=\"hidden sm:inline\">\n {localization.label}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-40\">\n <LocaleOptions />\n </DropdownMenuContent>\n </DropdownMenu>\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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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useHydrated } from "../hooks/use-hydrated.js";
|
|
2
|
-
import { formatKeyBinding, initials, registerStarterKitCopilotShortcut, registerStarterKitSearchShortcut, requestStarterKitCopilot, requestStarterKitSearch, tenantNameText } from "@alfakit/core";
|
|
2
|
+
import { formatKeyBinding, initials, registerStarterKitCopilotShortcut, registerStarterKitSearchShortcut, requestStarterKitCopilot, requestStarterKitLanguages, requestStarterKitSearch, tenantNameText } from "@alfakit/core";
|
|
3
3
|
import { useEffect } from "react";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { BellIcon, ChevronsUpDownIcon, SearchIcon, SparklesIcon } from "lucide-react";
|
|
5
|
+
import { BellIcon, ChevronsUpDownIcon, LanguagesIcon, SearchIcon, SparklesIcon } from "lucide-react";
|
|
6
6
|
//#region src/starter-kit/widgets-triggers.tsx
|
|
7
7
|
const triggerClasses = "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";
|
|
8
8
|
/** The search field until the palette has loaded: the same look, and it remembers a click. */
|
|
@@ -116,7 +116,38 @@ function CopilotTrigger({ source, onLoad }) {
|
|
|
116
116
|
})
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
/** The language button's look, before and after its menu has loaded. */
|
|
120
|
+
const languageTriggerClasses = "hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1.5 rounded-md px-2 text-sm font-medium outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50";
|
|
121
|
+
/**
|
|
122
|
+
* The language button until its menu has loaded: the languages icon and the current
|
|
123
|
+
* language in its own name (from `sm`). A click is remembered and opens the menu once loaded.
|
|
124
|
+
*/
|
|
125
|
+
function LanguageSwitcherTrigger({ source, onLoad }) {
|
|
126
|
+
const language = source.widgets.language;
|
|
127
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
128
|
+
type: "button",
|
|
129
|
+
"data-slot": "language-switcher-trigger",
|
|
130
|
+
"aria-label": language?.label,
|
|
131
|
+
"aria-haspopup": "menu",
|
|
132
|
+
title: language?.label,
|
|
133
|
+
className: languageTriggerClasses,
|
|
134
|
+
onPointerEnter: () => onLoad?.(),
|
|
135
|
+
onClick: () => {
|
|
136
|
+
requestStarterKitLanguages();
|
|
137
|
+
onLoad?.();
|
|
138
|
+
},
|
|
139
|
+
children: [/* @__PURE__ */ jsx(LanguagesIcon, {
|
|
140
|
+
className: "size-4",
|
|
141
|
+
"aria-hidden": "true"
|
|
142
|
+
}), language && /* @__PURE__ */ jsx("span", {
|
|
143
|
+
lang: language.current.tag,
|
|
144
|
+
dir: language.current.direction,
|
|
145
|
+
className: "hidden sm:inline",
|
|
146
|
+
children: language.current.label
|
|
147
|
+
})]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
119
150
|
//#endregion
|
|
120
|
-
export { CopilotTrigger, NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger };
|
|
151
|
+
export { CopilotTrigger, LanguageSwitcherTrigger, NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger, languageTriggerClasses };
|
|
121
152
|
|
|
122
153
|
//# sourceMappingURL=widgets-triggers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgets-triggers.js","names":[],"sources":["../../src/starter-kit/widgets-triggers.tsx"],"sourcesContent":["import {\n formatKeyBinding,\n initials,\n registerStarterKitCopilotShortcut,\n registerStarterKitSearchShortcut,\n requestStarterKitCopilot,\n requestStarterKitSearch,\n tenantNameText,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { BellIcon, ChevronsUpDownIcon, SearchIcon, SparklesIcon } 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\n/**\n * The AI copilot's button until its code has loaded: the same look, and it remembers a\n * click or its key bindings (`mod+j`), so the copilot opens once loaded.\n */\nexport function CopilotTrigger({ source, onLoad }: WidgetProps & { onLoad?: () => void }) {\n const copilot = source.widgets.copilot\n const keys = JSON.stringify(copilot?.keyBindings ?? [])\n const label = copilot?.label ?? ''\n // `onLoad` is a stable function (it starts loading the copilot's code).\n useEffect(\n () => registerStarterKitCopilotShortcut(JSON.parse(keys) as string[], label, () => onLoad?.()),\n [keys, label, onLoad],\n )\n\n return (\n <button\n type=\"button\"\n data-slot=\"ai-copilot-trigger\"\n aria-label={label}\n aria-haspopup=\"dialog\"\n title={label}\n className=\"hover:bg-accent hover:text-accent-foreground inline-flex size-9 items-center justify-center rounded-md\"\n onPointerEnter={() => onLoad?.()}\n onClick={() => {\n requestStarterKitCopilot()\n onLoad?.()\n }}\n >\n <SparklesIcon className=\"size-4\" aria-hidden=\"true\" />\n </button>\n )\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"widgets-triggers.js","names":[],"sources":["../../src/starter-kit/widgets-triggers.tsx"],"sourcesContent":["import {\n formatKeyBinding,\n initials,\n registerStarterKitCopilotShortcut,\n registerStarterKitSearchShortcut,\n requestStarterKitCopilot,\n requestStarterKitLanguages,\n requestStarterKitSearch,\n tenantNameText,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { BellIcon, ChevronsUpDownIcon, LanguagesIcon, SearchIcon, SparklesIcon } 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\n/**\n * The AI copilot's button until its code has loaded: the same look, and it remembers a\n * click or its key bindings (`mod+j`), so the copilot opens once loaded.\n */\nexport function CopilotTrigger({ source, onLoad }: WidgetProps & { onLoad?: () => void }) {\n const copilot = source.widgets.copilot\n const keys = JSON.stringify(copilot?.keyBindings ?? [])\n const label = copilot?.label ?? ''\n // `onLoad` is a stable function (it starts loading the copilot's code).\n useEffect(\n () => registerStarterKitCopilotShortcut(JSON.parse(keys) as string[], label, () => onLoad?.()),\n [keys, label, onLoad],\n )\n\n return (\n <button\n type=\"button\"\n data-slot=\"ai-copilot-trigger\"\n aria-label={label}\n aria-haspopup=\"dialog\"\n title={label}\n className=\"hover:bg-accent hover:text-accent-foreground inline-flex size-9 items-center justify-center rounded-md\"\n onPointerEnter={() => onLoad?.()}\n onClick={() => {\n requestStarterKitCopilot()\n onLoad?.()\n }}\n >\n <SparklesIcon className=\"size-4\" aria-hidden=\"true\" />\n </button>\n )\n}\n\n/** The language button's look, before and after its menu has loaded. */\nexport const languageTriggerClasses =\n 'hover:bg-accent hover:text-accent-foreground data-[state=open]:bg-accent inline-flex h-9 min-w-9 shrink-0 items-center justify-center gap-1.5 rounded-md px-2 text-sm font-medium outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50'\n\n/**\n * The language button until its menu has loaded: the languages icon and the current\n * language in its own name (from `sm`). A click is remembered and opens the menu once loaded.\n */\nexport function LanguageSwitcherTrigger({ source, onLoad }: WidgetProps & { onLoad?: () => void }) {\n const language = source.widgets.language\n\n return (\n <button\n type=\"button\"\n data-slot=\"language-switcher-trigger\"\n aria-label={language?.label}\n aria-haspopup=\"menu\"\n title={language?.label}\n className={languageTriggerClasses}\n onPointerEnter={() => onLoad?.()}\n onClick={() => {\n requestStarterKitLanguages()\n onLoad?.()\n }}\n >\n <LanguagesIcon className=\"size-4\" aria-hidden=\"true\" />\n {language && (\n <span\n lang={language.current.tag}\n dir={language.current.direction}\n className=\"hidden sm:inline\"\n >\n {language.current.label}\n </span>\n )}\n </button>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
//#region src/starter-kit/widgets.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Drop-in header components of starter kit mode (`Panel::starterKit()`): AlfaKit's search,
|
|
4
|
-
* notifications bell and
|
|
4
|
+
* notifications bell, tenant switcher and language switcher in the host's own header, on every page of the
|
|
5
5
|
* application. They read the shared `alfakitNavigation` prop, render a lightweight trigger
|
|
6
6
|
* straight away, and load their code and the panel data the first time they render
|
|
7
7
|
* (in the browser, never during SSR).
|
|
8
8
|
*
|
|
9
9
|
* ```tsx
|
|
10
|
-
* import { AlfakitCopilot, AlfakitNotifications, AlfakitSearch } from '@alfakit/react'
|
|
10
|
+
* import { AlfakitCopilot, AlfakitLanguageSwitcher, AlfakitNotifications, AlfakitSearch } from '@alfakit/react'
|
|
11
11
|
*
|
|
12
12
|
* <AlfakitSearch />
|
|
13
13
|
* <AlfakitNotifications />
|
|
14
14
|
* <AlfakitCopilot />
|
|
15
|
+
* <AlfakitLanguageSwitcher />
|
|
15
16
|
* ```
|
|
16
17
|
*/
|
|
17
18
|
export interface AlfakitHeaderWidgetProps {
|
|
@@ -42,5 +43,13 @@ export declare function AlfakitTenantSwitcher({ panel }?: AlfakitHeaderWidgetPro
|
|
|
42
43
|
* outside a tenant.
|
|
43
44
|
*/
|
|
44
45
|
export declare function AlfakitCopilot({ panel }?: AlfakitHeaderWidgetProps): import("react").JSX.Element;
|
|
46
|
+
/**
|
|
47
|
+
* The panel's language menu (`locales()`): the current language straight away, and each
|
|
48
|
+
* language in its own name once loaded. Picking one switches the panel's language (the
|
|
49
|
+
* session, the cookie and `persistLocaleUsing()`) and reloads the page, so AlfaKit pages and
|
|
50
|
+
* the header components show it; the host's own pages and `<html lang dir>` are the app's.
|
|
51
|
+
* Renders nothing for guests or with fewer than two languages.
|
|
52
|
+
*/
|
|
53
|
+
export declare function AlfakitLanguageSwitcher({ panel }?: AlfakitHeaderWidgetProps): import("react").JSX.Element;
|
|
45
54
|
//#endregion
|
|
46
55
|
//# sourceMappingURL=widgets.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgets.d.ts","names":[],"sources":["../../src/starter-kit/widgets.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"widgets.d.ts","names":[],"sources":["../../src/starter-kit/widgets.tsx"],"mappings":";;;;;;;;;;;;;;;;;iBAkCiB;;EAEf;;;;;;;wBAmEc,gBAAgB,UAAS,2CAA6B,IAAA;;;;;;wBAsBtD,uBAAuB,UAAS,2CAA6B,IAAA;;;;;wBAqB7D,wBAAwB,UAAS,2CAA6B,IAAA;;;;;;;wBAuB9D,iBAAiB,UAAS,2CAA6B,IAAA;;;;;;;;wBAwBvD,0BAA0B,UAAS,2CAA6B,IAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { lazyComponent } from "../lib/lazy.js";
|
|
2
|
-
import { CopilotTrigger, NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger } from "./widgets-triggers.js";
|
|
2
|
+
import { CopilotTrigger, LanguageSwitcherTrigger, NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger } from "./widgets-triggers.js";
|
|
3
3
|
import { isAlfakitPage, starterKitWidgets } from "@alfakit/core";
|
|
4
4
|
import { usePage } from "@inertiajs/react";
|
|
5
5
|
import { Suspense, lazy } from "react";
|
|
@@ -22,6 +22,7 @@ const LoadedCopilot = lazyComponent(() => load().then((module) => module.LoadedC
|
|
|
22
22
|
function preloadCopilot() {
|
|
23
23
|
LoadedCopilot.preload();
|
|
24
24
|
}
|
|
25
|
+
const LoadedLanguageSwitcher = lazyComponent(() => load().then((module) => module.LoadedLanguageSwitcher), (props) => /* @__PURE__ */ jsx(LanguageSwitcherTrigger, { ...props }));
|
|
25
26
|
const LazyImpersonationBanner = lazy(() => import("./impersonation-banner.js"));
|
|
26
27
|
/** While impersonating, the first header component shows the banner on host pages. */
|
|
27
28
|
function StarterKitImpersonationBanner({ source }) {
|
|
@@ -96,7 +97,24 @@ function AlfakitCopilot({ panel } = {}) {
|
|
|
96
97
|
children: /* @__PURE__ */ jsx(LoadedCopilot, { source })
|
|
97
98
|
})] });
|
|
98
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* The panel's language menu (`locales()`): the current language straight away, and each
|
|
102
|
+
* language in its own name once loaded. Picking one switches the panel's language (the
|
|
103
|
+
* session, the cookie and `persistLocaleUsing()`) and reloads the page, so AlfaKit pages and
|
|
104
|
+
* the header components show it; the host's own pages and `<html lang dir>` are the app's.
|
|
105
|
+
* Renders nothing for guests or with fewer than two languages.
|
|
106
|
+
*/
|
|
107
|
+
function AlfakitLanguageSwitcher({ panel } = {}) {
|
|
108
|
+
const source = useWidgets(panel, "language");
|
|
109
|
+
const banner = /* @__PURE__ */ jsx(StarterKitImpersonationBanner, { source });
|
|
110
|
+
if (!source?.widgets.language) return banner;
|
|
111
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [banner, /* @__PURE__ */ jsx("span", {
|
|
112
|
+
"data-slot": "starter-kit-language-switcher",
|
|
113
|
+
className: "inline-flex",
|
|
114
|
+
children: /* @__PURE__ */ jsx(LoadedLanguageSwitcher, { source })
|
|
115
|
+
})] });
|
|
116
|
+
}
|
|
99
117
|
//#endregion
|
|
100
|
-
export { AlfakitCopilot, AlfakitNotifications, AlfakitSearch, AlfakitTenantSwitcher };
|
|
118
|
+
export { AlfakitCopilot, AlfakitLanguageSwitcher, AlfakitNotifications, AlfakitSearch, AlfakitTenantSwitcher };
|
|
101
119
|
|
|
102
120
|
//# sourceMappingURL=widgets.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgets.js","names":[],"sources":["../../src/starter-kit/widgets.tsx"],"sourcesContent":["import {\n isAlfakitPage,\n starterKitWidgets,\n type StarterKitWidgets,\n type StarterKitWidgetsSchema,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { lazy, Suspense } from 'react'\nimport { lazyComponent } from '../lib/lazy'\nimport {\n CopilotTrigger,\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * Drop-in header components of starter kit mode (`Panel::starterKit()`): AlfaKit's search,\n * notifications bell and
|
|
1
|
+
{"version":3,"file":"widgets.js","names":[],"sources":["../../src/starter-kit/widgets.tsx"],"sourcesContent":["import {\n isAlfakitPage,\n starterKitWidgets,\n type StarterKitWidgets,\n type StarterKitWidgetsSchema,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { lazy, Suspense } from 'react'\nimport { lazyComponent } from '../lib/lazy'\nimport {\n CopilotTrigger,\n LanguageSwitcherTrigger,\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * Drop-in header components of starter kit mode (`Panel::starterKit()`): AlfaKit's search,\n * notifications bell, tenant switcher and language switcher in the host's own header, on every page of the\n * application. They read the shared `alfakitNavigation` prop, render a lightweight trigger\n * straight away, and load their code and the panel data the first time they render\n * (in the browser, never during SSR).\n *\n * ```tsx\n * import { AlfakitCopilot, AlfakitLanguageSwitcher, AlfakitNotifications, AlfakitSearch } from '@alfakit/react'\n *\n * <AlfakitSearch />\n * <AlfakitNotifications />\n * <AlfakitCopilot />\n * <AlfakitLanguageSwitcher />\n * ```\n */\nexport interface AlfakitHeaderWidgetProps {\n /** The `starterKit()` panel to use (by default the open AlfaKit page's panel, else the first one that has this component). */\n panel?: string\n}\n\nconst load = () => import('./widgets-loaded')\n\nconst LoadedSearch = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedSearch),\n (props) => <SearchTrigger {...props} onLoad={preloadSearch} />,\n)\n\nfunction preloadSearch() {\n void LoadedSearch.preload()\n}\n\nconst LoadedNotifications = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedNotifications),\n (props) => <NotificationsTrigger {...props} />,\n)\n\nconst LoadedTenantSwitcher = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedTenantSwitcher),\n (props) => <TenantSwitcherTrigger {...props} />,\n)\n\nconst LoadedCopilot = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedCopilot),\n (props) => <CopilotTrigger {...props} onLoad={preloadCopilot} />,\n)\n\nfunction preloadCopilot() {\n void LoadedCopilot.preload()\n}\n\nconst LoadedLanguageSwitcher = lazyComponent<WidgetProps>(\n () => load().then((module) => module.LoadedLanguageSwitcher),\n (props) => <LanguageSwitcherTrigger {...props} />,\n)\n\n// Only needed while impersonating: its code loads then (and never on AlfaKit pages).\nconst LazyImpersonationBanner = lazy(() => import('./impersonation-banner'))\n\n/** While impersonating, the first header component shows the banner on host pages. */\nfunction StarterKitImpersonationBanner({ source }: { source: StarterKitWidgets | null }) {\n const props = usePage().props\n const impersonation = source?.widgets.impersonation\n\n if (!impersonation || isAlfakitPage(props)) return null\n\n return (\n <Suspense fallback={null}>\n <LazyImpersonationBanner impersonation={impersonation} />\n </Suspense>\n )\n}\n\nfunction useWidgets(\n panelId: string | undefined,\n part: keyof StarterKitWidgetsSchema,\n): StarterKitWidgets | null {\n return starterKitWidgets(usePage().props, panelId, part)\n}\n\n/**\n * AlfaKit's command palette and global search: the search field opens it, and so do the\n * panel's key bindings (⌘K / Ctrl+K by default), registered once for the page. Renders\n * nothing for guests or without global search and a command palette.\n */\nexport function AlfakitSearch({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'search')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.search) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-search\" className=\"inline-flex\">\n <LoadedSearch source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The bell with the user's database notifications (`databaseNotifications()`), polling and\n * realtime updates as in the panel. Renders nothing for guests or without database\n * notifications.\n */\nexport function AlfakitNotifications({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'notifications')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.notifications) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-notifications\" className=\"inline-flex\">\n <LoadedNotifications source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The tenant switcher of a tenancy panel: the current tenant inside the panel, or a menu\n * to choose one on the host's own pages. Renders nothing for guests or other panels.\n */\nexport function AlfakitTenantSwitcher({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'tenancy')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.tenancy) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-tenant-switcher\" className=\"inline-flex\">\n <LoadedTenantSwitcher source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The AI copilot of alfasix/alfakit-ai (`AiPlugin::copilot()`): its button and chat\n * slide-over, opened with its key bindings (`mod+j`) from any page. Renders nothing for\n * guests, without the plugin's copilot, for users who may not use it and (tenancy panels)\n * outside a tenant.\n */\nexport function AlfakitCopilot({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'copilot')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.copilot) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-copilot\" className=\"inline-flex\">\n <LoadedCopilot source={source} />\n </span>\n </>\n )\n}\n\n/**\n * The panel's language menu (`locales()`): the current language straight away, and each\n * language in its own name once loaded. Picking one switches the panel's language (the\n * session, the cookie and `persistLocaleUsing()`) and reloads the page, so AlfaKit pages and\n * the header components show it; the host's own pages and `<html lang dir>` are the app's.\n * Renders nothing for guests or with fewer than two languages.\n */\nexport function AlfakitLanguageSwitcher({ panel }: AlfakitHeaderWidgetProps = {}) {\n const source = useWidgets(panel, 'language')\n\n const banner = <StarterKitImpersonationBanner source={source} />\n\n if (!source?.widgets.language) return banner\n\n return (\n <>\n {banner}\n <span data-slot=\"starter-kit-language-switcher\" className=\"inline-flex\">\n <LoadedLanguageSwitcher source={source} />\n </span>\n </>\n )\n}\n"],"mappings":";;;;;;;AAuCA,MAAM,aAAa,OAAO;AAE1B,MAAM,eAAe,oBACb,KAAK,CAAC,CAAC,MAAM,WAAW,OAAO,YAAY,IAChD,UAAU,oBAAC,eAAD;CAAe,GAAI;CAAO,QAAQ;AAAgB,CAAA,CAC/D;AAEA,SAAS,gBAAgB;CACvB,aAAkB,QAAQ;AAC5B;AAEA,MAAM,sBAAsB,oBACpB,KAAK,CAAC,CAAC,MAAM,WAAW,OAAO,mBAAmB,IACvD,UAAU,oBAAC,sBAAD,EAAsB,GAAI,MAAQ,CAAA,CAC/C;AAEA,MAAM,uBAAuB,oBACrB,KAAK,CAAC,CAAC,MAAM,WAAW,OAAO,oBAAoB,IACxD,UAAU,oBAAC,uBAAD,EAAuB,GAAI,MAAQ,CAAA,CAChD;AAEA,MAAM,gBAAgB,oBACd,KAAK,CAAC,CAAC,MAAM,WAAW,OAAO,aAAa,IACjD,UAAU,oBAAC,gBAAD;CAAgB,GAAI;CAAO,QAAQ;AAAiB,CAAA,CACjE;AAEA,SAAS,iBAAiB;CACxB,cAAmB,QAAQ;AAC7B;AAEA,MAAM,yBAAyB,oBACvB,KAAK,CAAC,CAAC,MAAM,WAAW,OAAO,sBAAsB,IAC1D,UAAU,oBAAC,yBAAD,EAAyB,GAAI,MAAQ,CAAA,CAClD;AAGA,MAAM,0BAA0B,WAAW,OAAO,4BAAyB;;AAG3E,SAAS,8BAA8B,EAAE,UAAgD;CACvF,MAAM,QAAQ,QAAQ,CAAC,CAAC;CACxB,MAAM,gBAAgB,QAAQ,QAAQ;CAEtC,IAAI,CAAC,iBAAiB,cAAc,KAAK,GAAG,OAAO;CAEnD,OACE,oBAAC,UAAD;EAAU,UAAU;EAClB,UAAA,oBAAC,yBAAD,EAAwC,cAAgB,CAAA;CAChD,CAAA;AAEd;AAEA,SAAS,WACP,SACA,MAC0B;CAC1B,OAAO,kBAAkB,QAAQ,CAAC,CAAC,OAAO,SAAS,IAAI;AACzD;;;;;;AAOA,SAAgB,cAAc,EAAE,UAAoC,CAAC,GAAG;CACtE,MAAM,SAAS,WAAW,OAAO,QAAQ;CAEzC,MAAM,SAAS,oBAAC,+BAAD,EAAuC,OAAS,CAAA;CAE/D,IAAI,CAAC,QAAQ,QAAQ,QAAQ,OAAO;CAEpC,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,QACD,oBAAC,QAAD;EAAM,aAAU;EAAqB,WAAU;EAC7C,UAAA,oBAAC,cAAD,EAAsB,OAAS,CAAA;CAC3B,CAAA,CACN,EAAA,CAAA;AAEN;;;;;;AAOA,SAAgB,qBAAqB,EAAE,UAAoC,CAAC,GAAG;CAC7E,MAAM,SAAS,WAAW,OAAO,eAAe;CAEhD,MAAM,SAAS,oBAAC,+BAAD,EAAuC,OAAS,CAAA;CAE/D,IAAI,CAAC,QAAQ,QAAQ,eAAe,OAAO;CAE3C,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,QACD,oBAAC,QAAD;EAAM,aAAU;EAA4B,WAAU;EACpD,UAAA,oBAAC,qBAAD,EAA6B,OAAS,CAAA;CAClC,CAAA,CACN,EAAA,CAAA;AAEN;;;;;AAMA,SAAgB,sBAAsB,EAAE,UAAoC,CAAC,GAAG;CAC9E,MAAM,SAAS,WAAW,OAAO,SAAS;CAE1C,MAAM,SAAS,oBAAC,+BAAD,EAAuC,OAAS,CAAA;CAE/D,IAAI,CAAC,QAAQ,QAAQ,SAAS,OAAO;CAErC,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,QACD,oBAAC,QAAD;EAAM,aAAU;EAA8B,WAAU;EACtD,UAAA,oBAAC,sBAAD,EAA8B,OAAS,CAAA;CACnC,CAAA,CACN,EAAA,CAAA;AAEN;;;;;;;AAQA,SAAgB,eAAe,EAAE,UAAoC,CAAC,GAAG;CACvE,MAAM,SAAS,WAAW,OAAO,SAAS;CAE1C,MAAM,SAAS,oBAAC,+BAAD,EAAuC,OAAS,CAAA;CAE/D,IAAI,CAAC,QAAQ,QAAQ,SAAS,OAAO;CAErC,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,QACD,oBAAC,QAAD;EAAM,aAAU;EAAsB,WAAU;EAC9C,UAAA,oBAAC,eAAD,EAAuB,OAAS,CAAA;CAC5B,CAAA,CACN,EAAA,CAAA;AAEN;;;;;;;;AASA,SAAgB,wBAAwB,EAAE,UAAoC,CAAC,GAAG;CAChF,MAAM,SAAS,WAAW,OAAO,UAAU;CAE3C,MAAM,SAAS,oBAAC,+BAAD,EAAuC,OAAS,CAAA;CAE/D,IAAI,CAAC,QAAQ,QAAQ,UAAU,OAAO;CAEtC,OACE,qBAAA,YAAA,EAAA,UAAA,CACG,QACD,oBAAC,QAAD;EAAM,aAAU;EAAgC,WAAU;EACxD,UAAA,oBAAC,wBAAD,EAAgC,OAAS,CAAA;CACrC,CAAA,CACN,EAAA,CAAA;AAEN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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.9"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@inertiajs/react": "^3.0.0",
|
|
@@ -469,6 +469,8 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
|
|
|
469
469
|
// Pinned to the top of the scrolling dialog, edge to edge.
|
|
470
470
|
schema.stickyHeader &&
|
|
471
471
|
'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',
|
|
472
|
+
// Room for the close button (X): a long heading would run under it.
|
|
473
|
+
schema.closeButton !== false && (schema.stickyHeader ? 'pe-12' : 'pe-6'),
|
|
472
474
|
)}
|
|
473
475
|
>
|
|
474
476
|
{header}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
FormStore,
|
|
4
4
|
applyFormRefresh,
|
|
5
5
|
formActionsVisible,
|
|
6
|
+
formIdPrefix,
|
|
6
7
|
formIdentity,
|
|
7
8
|
formRefreshFor,
|
|
8
9
|
isImplicitSubmit,
|
|
@@ -17,8 +18,15 @@ import {
|
|
|
17
18
|
wizardProgressStore,
|
|
18
19
|
} from '@alfakit/core'
|
|
19
20
|
import { router } from '@inertiajs/react'
|
|
20
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
21
|
-
import {
|
|
21
|
+
import { useCallback, useEffect, useId, useMemo, useRef } from 'react'
|
|
22
|
+
import {
|
|
23
|
+
useActionRunner,
|
|
24
|
+
useAlfakitProps,
|
|
25
|
+
usePageSchema,
|
|
26
|
+
usePanel,
|
|
27
|
+
useStandalone,
|
|
28
|
+
useT,
|
|
29
|
+
} from '../../context'
|
|
22
30
|
import { useStore } from '../../hooks/use-store'
|
|
23
31
|
import { ActionList } from '../actions/action-list'
|
|
24
32
|
import { showToast } from '../notifications/flash-toasts'
|
|
@@ -42,12 +50,17 @@ export function FormBlock({ block }: { block: FormBlockSchema }) {
|
|
|
42
50
|
// identity starts from server state again. The token is re-issued per response, so it
|
|
43
51
|
// is swapped in place instead of being part of the identity.
|
|
44
52
|
const identity = formIdentity(page, block)
|
|
53
|
+
// Several standalone components can share a page (and field names): each form gets its
|
|
54
|
+
// own field id prefix, so labels and errors point at their own inputs.
|
|
55
|
+
const standalone = useStandalone()
|
|
56
|
+
const instanceId = useId()
|
|
45
57
|
|
|
46
58
|
const store = useMemo(
|
|
47
59
|
() =>
|
|
48
60
|
new FormStore(block, {
|
|
49
61
|
token,
|
|
50
62
|
component: block.component,
|
|
63
|
+
idPrefix: standalone ? formIdPrefix(`form-${instanceId}`) : undefined,
|
|
51
64
|
refreshUrl: panel.endpoints.schemaRefresh,
|
|
52
65
|
optionsUrl: panel.endpoints.schemaOptions,
|
|
53
66
|
validateUrl: panel.endpoints.schemaValidate,
|
|
@@ -133,7 +133,7 @@ function NavSubItem({ item }: { item: NavigationItemSchema }) {
|
|
|
133
133
|
<CollapsibleContent>
|
|
134
134
|
<SidebarMenuSub>
|
|
135
135
|
{item.children.map((child) => (
|
|
136
|
-
<NavSubItem key={child.label} item={child} />
|
|
136
|
+
<NavSubItem key={child.url ?? child.label} item={child} />
|
|
137
137
|
))}
|
|
138
138
|
</SidebarMenuSub>
|
|
139
139
|
</CollapsibleContent>
|
|
@@ -175,7 +175,7 @@ function NavItem({ item }: { item: NavigationItemSchema }) {
|
|
|
175
175
|
<CollapsibleContent>
|
|
176
176
|
<SidebarMenuSub>
|
|
177
177
|
{item.children.map((child) => (
|
|
178
|
-
<NavSubItem key={child.label} item={child} />
|
|
178
|
+
<NavSubItem key={child.url ?? child.label} item={child} />
|
|
179
179
|
))}
|
|
180
180
|
</SidebarMenuSub>
|
|
181
181
|
</CollapsibleContent>
|
|
@@ -263,7 +263,7 @@ function NavGroup({ group }: { group: NavigationGroupSchema }) {
|
|
|
263
263
|
<SidebarGroupContent className={cn(flyout && 'group-data-[collapsible=icon]:hidden')}>
|
|
264
264
|
<SidebarMenu>
|
|
265
265
|
{group.items.map((item) => (
|
|
266
|
-
<NavItem key={item.label} item={item} />
|
|
266
|
+
<NavItem key={item.url ?? item.label} item={item} />
|
|
267
267
|
))}
|
|
268
268
|
</SidebarMenu>
|
|
269
269
|
</SidebarGroupContent>
|
|
@@ -81,7 +81,7 @@ export function BottomNav() {
|
|
|
81
81
|
className="md:hidden"
|
|
82
82
|
>
|
|
83
83
|
{primary.map((item) => (
|
|
84
|
-
<BottomNavLink key={item.label} item={item} />
|
|
84
|
+
<BottomNavLink key={item.url ?? item.label} item={item} />
|
|
85
85
|
))}
|
|
86
86
|
{overflow.length > 0 && (
|
|
87
87
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
@@ -109,7 +109,7 @@ export function BottomNav() {
|
|
|
109
109
|
.flatMap((item) => [item, ...(item.children ?? [])])
|
|
110
110
|
.map((item) => (
|
|
111
111
|
<Link
|
|
112
|
-
key={item.label}
|
|
112
|
+
key={item.url ?? item.label}
|
|
113
113
|
href={navigationItemUrl(item) ?? '#'}
|
|
114
114
|
onClick={() => setOpen(false)}
|
|
115
115
|
data-active={isNavigationItemActive(item) ? 'true' : undefined}
|
|
@@ -47,7 +47,7 @@ export function KeyboardShortcuts() {
|
|
|
47
47
|
const overrides = JSON.stringify(config ? (config.overrides ?? {}) : {})
|
|
48
48
|
const token = page?.token ?? ''
|
|
49
49
|
const commands = panel.commandPalette?.commands
|
|
50
|
-
const home = panel.url
|
|
50
|
+
const home = panel.dashboardUrl ?? panel.url
|
|
51
51
|
// Strings, so the effect below doesn't re-run on every render (`t` is a new function each time).
|
|
52
52
|
const overlayLabel = t('shortcuts.show_overlay', {}, 'Show keyboard shortcuts')
|
|
53
53
|
const searchLabel = t('shortcuts.focus_search', {}, 'Search the table')
|
|
@@ -24,7 +24,8 @@ function useSwitch() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/** The panel's languages as radio items, each in its own name: picking one switches to it. */
|
|
28
|
+
export function LocaleOptions() {
|
|
28
29
|
const localization = useLocalization()
|
|
29
30
|
const change = useSwitch()
|
|
30
31
|
|
|
@@ -20,7 +20,16 @@ function Tooltip({ ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
23
|
-
|
|
23
|
+
// With asChild the child keeps its own data-slot (an action's `button`, which the
|
|
24
|
+
// button-shape and touch-target CSS select).
|
|
25
|
+
// (The key must be left out, not set to undefined: Slot passes it on and the child's own
|
|
26
|
+
// `{...props}` would erase its data-slot.)
|
|
27
|
+
return (
|
|
28
|
+
<TooltipPrimitive.Trigger
|
|
29
|
+
{...(props.asChild ? {} : { 'data-slot': 'tooltip-trigger' })}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
function TooltipContent({
|
|
@@ -118,7 +118,7 @@ function TopNavMenu({
|
|
|
118
118
|
</DropdownMenuTrigger>
|
|
119
119
|
<DropdownMenuContent align="start" className="min-w-48">
|
|
120
120
|
{items.map((item) => (
|
|
121
|
-
<DropdownMenuItem key={item.label} asChild>
|
|
121
|
+
<DropdownMenuItem key={item.url ?? item.label} asChild>
|
|
122
122
|
<Link
|
|
123
123
|
{...extraProps(item.extraAttributes, cn('w-full', item.active && 'font-medium'))}
|
|
124
124
|
href={item.url ?? '#'}
|
|
@@ -150,7 +150,7 @@ function TopNavGroup({ group }: { group: NavigationGroupSchema }) {
|
|
|
150
150
|
return (
|
|
151
151
|
<>
|
|
152
152
|
{group.items.map((item) => (
|
|
153
|
-
<TopNavLink key={item.label} item={item} />
|
|
153
|
+
<TopNavLink key={item.url ?? item.label} item={item} />
|
|
154
154
|
))}
|
|
155
155
|
</>
|
|
156
156
|
)
|
|
@@ -181,7 +181,7 @@ function MobileNav({ onNavigate }: { onNavigate: () => void }) {
|
|
|
181
181
|
.flatMap((item) => [item, ...(item.children ?? [])])
|
|
182
182
|
.map((item) => (
|
|
183
183
|
<Link
|
|
184
|
-
key={item.label}
|
|
184
|
+
key={item.url ?? item.label}
|
|
185
185
|
href={item.url ?? '#'}
|
|
186
186
|
onClick={onNavigate}
|
|
187
187
|
data-active={item.active ? 'true' : undefined}
|
|
@@ -83,8 +83,18 @@ export function AlfakitComponent({
|
|
|
83
83
|
const refresh = (targets: string[]) => state.refresh(targets)
|
|
84
84
|
|
|
85
85
|
if (parent) {
|
|
86
|
+
// Inside a panel page: the panel's registry and translator, but this component's own
|
|
87
|
+
// action runner, so its actions post to its own endpoints with its own token (sharing
|
|
88
|
+
// the panel's runner sent them to the panel's endpoint: 403).
|
|
89
|
+
const own = createStandaloneContext(
|
|
90
|
+
{ action: component.endpoints.action, modal: component.endpoints.modal },
|
|
91
|
+
components,
|
|
92
|
+
poll,
|
|
93
|
+
)
|
|
94
|
+
|
|
86
95
|
return {
|
|
87
96
|
...parent,
|
|
97
|
+
actions: own.actions,
|
|
88
98
|
registry: createStandaloneRegistry(components, parent.registry),
|
|
89
99
|
poll,
|
|
90
100
|
refresh,
|
|
@@ -201,7 +211,7 @@ export function AlfakitComponent({
|
|
|
201
211
|
{footer}
|
|
202
212
|
</div>
|
|
203
213
|
<ActionModals />
|
|
204
|
-
{ownsToaster && <AlfakitToaster claimed />}
|
|
214
|
+
{ownsToaster && <AlfakitToaster claimed dir={direction} />}
|
|
205
215
|
</PageActionProvider>
|
|
206
216
|
)
|
|
207
217
|
|
package/src/starter-kit/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { usePage } from '@inertiajs/react'
|
|
17
17
|
import { useEffect, useState, type ComponentType, type ReactNode } from 'react'
|
|
18
18
|
import { setToasterId, showToast } from '../components/notifications/flash-toasts'
|
|
19
|
+
import { DebugPanelSlot } from '../components/panel/debug-panel-slot'
|
|
19
20
|
import { Slot } from '../components/panel/slot'
|
|
20
21
|
import {
|
|
21
22
|
AlfakitProvider,
|
|
@@ -181,7 +182,14 @@ export function AlfakitStarterKitRoot({
|
|
|
181
182
|
lang={panel?.localization?.tag}
|
|
182
183
|
className="contents"
|
|
183
184
|
>
|
|
184
|
-
<AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>
|
|
185
|
+
<AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>
|
|
186
|
+
{children}
|
|
187
|
+
{/* The debug panel's button lives in AlfaKit's top bar, which a starter kit doesn't
|
|
188
|
+
render: float it in the corner instead (nothing without `alfakitDebug`). */}
|
|
189
|
+
<div data-slot="starter-kit-debug-panel" className="fixed end-16 bottom-4 z-40">
|
|
190
|
+
<DebugPanelSlot />
|
|
191
|
+
</div>
|
|
192
|
+
</AlfakitRoot>
|
|
185
193
|
</div>
|
|
186
194
|
</AlfakitProvider>
|
|
187
195
|
)
|