@alfakit/react 0.1.1 → 0.1.6
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-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 +6 -5
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/ai/copilot.d.ts +3 -1
- package/dist/components/ai/copilot.d.ts.map +1 -1
- package/dist/components/ai/copilot.js +4 -1
- package/dist/components/ai/copilot.js.map +1 -1
- package/dist/components/cms/live-preview.js +3 -2
- package/dist/components/cms/live-preview.js.map +1 -1
- package/dist/components/cms/page.js +1 -1
- package/dist/components/fields/builder.js +15 -6
- package/dist/components/fields/builder.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/image-editor-dialog.js +4 -2
- package/dist/components/fields/image-editor-dialog.js.map +1 -1
- package/dist/components/fields/item-actions.js +10 -10
- package/dist/components/fields/item-actions.js.map +1 -1
- package/dist/components/fields/key-value.d.ts.map +1 -1
- package/dist/components/fields/key-value.js +3 -3
- package/dist/components/fields/key-value.js.map +1 -1
- package/dist/components/fields/modal-table-select.js +6 -5
- package/dist/components/fields/modal-table-select.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/repeater.js +2 -2
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor.js +2 -1
- package/dist/components/fields/rich-editor.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 +14 -2
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/form/form-context.js +7 -3
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/kanban/kanban.js +4 -3
- package/dist/components/kanban/kanban.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/section.js +2 -2
- package/dist/components/layout/section.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/app-sidebar.d.ts +2 -3
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +1 -24
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/brand.d.ts +10 -0
- package/dist/components/panel/brand.d.ts.map +1 -0
- package/dist/components/panel/brand.js +37 -0
- package/dist/components/panel/brand.js.map +1 -0
- package/dist/components/panel/global-search.js +1 -0
- package/dist/components/panel/global-search.js.map +1 -1
- package/dist/components/panel/index.d.ts +2 -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/panel/topbar.js +1 -1
- package/dist/components/realtime/live-records-pill.d.ts.map +1 -1
- package/dist/components/realtime/live-records-pill.js +6 -3
- package/dist/components/realtime/live-records-pill.js.map +1 -1
- package/dist/components/table/cells.d.ts +6 -0
- package/dist/components/table/cells.d.ts.map +1 -1
- package/dist/components/table/cells.js +15 -3
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +8 -7
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/content-layout.js +2 -1
- package/dist/components/table/content-layout.js.map +1 -1
- package/dist/components/table/grouping.js +11 -1
- package/dist/components/table/grouping.js.map +1 -1
- package/dist/components/table/pagination.js +16 -13
- package/dist/components/table/pagination.js.map +1 -1
- package/dist/components/table/table-block.js +14 -4
- package/dist/components/table/table-block.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +3 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +3 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +3 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/tooltip.js +3 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components.css +347 -0
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +5 -1
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +8 -5
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +16 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +24 -21
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +2 -29
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/layouts/auth-layout.js +2 -2
- package/dist/layouts/auth-layout.js.map +1 -1
- package/dist/layouts/fullscreen-layout.js +1 -1
- package/dist/layouts/fullscreen-layout.js.map +1 -1
- package/dist/layouts/lazy-layouts.js +15 -0
- package/dist/layouts/lazy-layouts.js.map +1 -0
- package/dist/layouts/max-widths.js +34 -0
- package/dist/layouts/max-widths.js.map +1 -0
- package/dist/layouts/panel-layouts.d.ts +10 -0
- package/dist/layouts/panel-layouts.d.ts.map +1 -0
- package/dist/layouts/panel-layouts.js +20 -0
- package/dist/layouts/panel-layouts.js.map +1 -0
- package/dist/layouts/top-nav-layout.js +2 -2
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +12 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +11 -6
- package/dist/standalone/alfakit-component.js.map +1 -1
- 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 +54 -0
- package/dist/starter-kit/dashboard-widgets.d.ts +33 -0
- package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
- package/dist/starter-kit/dashboard-widgets.js +53 -0
- package/dist/starter-kit/dashboard-widgets.js.map +1 -0
- package/dist/starter-kit/impersonation-banner.js +34 -0
- package/dist/starter-kit/impersonation-banner.js.map +1 -0
- package/dist/starter-kit/index.d.ts +3 -2
- package/dist/starter-kit/index.js +3 -2
- package/dist/starter-kit/starter-kit.d.ts +6 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +18 -7
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets-loaded.js +21 -5
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +35 -3
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/starter-kit/widgets.d.ts +13 -5
- package/dist/starter-kit/widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets.js +56 -18
- package/dist/starter-kit/widgets.js.map +1 -1
- package/dist/styles.css +15 -2
- package/dist/themes.css +8 -335
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +7 -4
- package/src/components/actions/action-modals.tsx +13 -1
- package/src/components/ai/copilot.tsx +14 -1
- package/src/components/cms/live-preview.tsx +5 -2
- package/src/components/fields/builder.tsx +14 -5
- package/src/components/fields/color-picker.tsx +1 -0
- package/src/components/fields/file-upload.tsx +21 -10
- package/src/components/fields/image-editor-dialog.tsx +7 -2
- package/src/components/fields/item-actions.tsx +32 -9
- package/src/components/fields/key-value.tsx +3 -2
- package/src/components/fields/modal-table-select.tsx +8 -3
- package/src/components/fields/one-time-code-input.tsx +1 -1
- package/src/components/fields/repeater-table.tsx +12 -1
- package/src/components/fields/repeater.tsx +2 -3
- package/src/components/fields/rich-editor.tsx +3 -0
- 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 +26 -1
- package/src/components/form/form-context.tsx +3 -6
- package/src/components/kanban/kanban.tsx +16 -3
- package/src/components/layout/fused-group.tsx +5 -1
- package/src/components/layout/section.tsx +2 -2
- package/src/components/layout/tabs.tsx +72 -17
- package/src/components/panel/app-sidebar.tsx +2 -38
- package/src/components/panel/brand.tsx +46 -0
- 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/realtime/live-records-pill.tsx +4 -3
- package/src/components/table/cells.tsx +27 -5
- package/src/components/table/column-manager.tsx +12 -10
- package/src/components/table/content-layout.tsx +5 -1
- package/src/components/table/grouping.tsx +15 -1
- package/src/components/table/pagination.tsx +16 -9
- package/src/components/table/table-block.tsx +25 -12
- package/src/components/ui/dialog.tsx +5 -1
- package/src/components/ui/popover.tsx +5 -1
- package/src/components/ui/sheet.tsx +5 -1
- package/src/components/ui/tooltip.tsx +5 -1
- package/src/context.tsx +10 -0
- package/src/create-alfakit.tsx +11 -2
- package/src/index.ts +2 -0
- package/src/layouts/app-layout.tsx +4 -28
- package/src/layouts/auth-layout.tsx +2 -2
- package/src/layouts/fullscreen-layout.tsx +1 -1
- package/src/layouts/lazy-layouts.ts +14 -0
- package/src/layouts/max-widths.ts +29 -0
- package/src/layouts/panel-layouts.ts +17 -0
- package/src/layouts/top-nav-layout.tsx +2 -2
- package/src/pages/page.tsx +21 -12
- package/src/standalone/alfakit-component.tsx +18 -7
- package/src/standalone/alfakit-toaster.tsx +21 -2
- package/src/starter-kit/dashboard-widgets.tsx +78 -0
- package/src/starter-kit/impersonation-banner.tsx +48 -0
- package/src/starter-kit/index.ts +2 -0
- package/src/starter-kit/starter-kit.tsx +31 -4
- package/src/starter-kit/widgets-loaded.tsx +23 -1
- package/src/starter-kit/widgets-triggers.tsx +37 -2
- package/src/starter-kit/widgets.tsx +95 -20
- package/src/styles.css +15 -2
|
@@ -15,36 +15,12 @@ import { Topbar } from '../components/panel/topbar'
|
|
|
15
15
|
import { SidebarInset, SidebarProvider, SidebarTrigger } from '../components/ui/sidebar'
|
|
16
16
|
import { useAlfakitProps, useRegistry } from '../context'
|
|
17
17
|
import { cn } from '../lib/utils'
|
|
18
|
+
import { MAX_WIDTHS } from './max-widths'
|
|
18
19
|
import { TopNavLayout } from './top-nav-layout'
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
xs: 'max-w-xs',
|
|
24
|
-
sm: 'max-w-sm',
|
|
25
|
-
md: 'max-w-md',
|
|
26
|
-
lg: 'max-w-lg',
|
|
27
|
-
xl: 'max-w-xl',
|
|
28
|
-
'2xl': 'max-w-2xl',
|
|
29
|
-
'3xl': 'max-w-3xl',
|
|
30
|
-
'4xl': 'max-w-4xl',
|
|
31
|
-
'5xl': 'max-w-5xl',
|
|
32
|
-
'6xl': 'max-w-6xl',
|
|
33
|
-
'7xl': 'max-w-7xl',
|
|
34
|
-
none: 'max-w-none',
|
|
35
|
-
min: 'max-w-min',
|
|
36
|
-
max: 'max-w-max',
|
|
37
|
-
fit: 'max-w-fit',
|
|
38
|
-
prose: 'max-w-prose',
|
|
39
|
-
container: 'container',
|
|
40
|
-
'screen-sm': 'max-w-(--breakpoint-sm)',
|
|
41
|
-
'screen-md': 'max-w-(--breakpoint-md)',
|
|
42
|
-
'screen-lg': 'max-w-(--breakpoint-lg)',
|
|
43
|
-
'screen-xl': 'max-w-(--breakpoint-xl)',
|
|
44
|
-
'screen-2xl': 'max-w-(--breakpoint-2xl)',
|
|
45
|
-
full: 'max-w-full',
|
|
46
|
-
screen: 'max-w-screen',
|
|
47
|
-
}
|
|
21
|
+
// Re-exported for code that imported it from here; the map lives in `./max-widths` so the
|
|
22
|
+
// starter kit's content layout doesn't pull the panel chrome in.
|
|
23
|
+
export { MAX_WIDTHS } from './max-widths'
|
|
48
24
|
|
|
49
25
|
export interface AppLayoutProps {
|
|
50
26
|
children: ReactNode
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
|
-
import { Brand } from '../components/panel/
|
|
2
|
+
import { Brand } from '../components/panel/brand'
|
|
3
3
|
import { LocaleSwitcher } from '../components/panel/locale-switcher'
|
|
4
4
|
import { Slot } from '../components/panel/slot'
|
|
5
5
|
import { ThemeToggle } from '../components/panel/theme-toggle'
|
|
6
6
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/card'
|
|
7
7
|
import { usePanel } from '../context'
|
|
8
8
|
import { cn } from '../lib/utils'
|
|
9
|
-
import { MAX_WIDTHS } from './
|
|
9
|
+
import { MAX_WIDTHS } from './max-widths'
|
|
10
10
|
|
|
11
11
|
export interface AuthLayoutProps {
|
|
12
12
|
children: ReactNode
|
|
@@ -3,7 +3,7 @@ import { Link } from '@inertiajs/react'
|
|
|
3
3
|
import { LogOutIcon } from 'lucide-react'
|
|
4
4
|
import type { ReactNode } from 'react'
|
|
5
5
|
import { NotificationsMenu } from '../components/notifications/notifications-menu'
|
|
6
|
-
import { Brand } from '../components/panel/
|
|
6
|
+
import { Brand } from '../components/panel/brand'
|
|
7
7
|
import { DebugPanelSlot } from '../components/panel/debug-panel-slot'
|
|
8
8
|
import { TopbarLocaleSwitcher } from '../components/panel/locale-switcher'
|
|
9
9
|
import { Slot } from '../components/panel/slot'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { lazyComponents } from '@alfakit/core'
|
|
2
|
+
import type { AnyComponent } from '../context'
|
|
3
|
+
import { discoveredComponent } from '../lib/discovery'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The fullscreen layout (`$layout = 'fullscreen'`), loaded on demand by starter kit pages:
|
|
7
|
+
* they render in the host's layout, so the page chunk never imports the panel's header,
|
|
8
|
+
* menus and notifications for a layout few pages use. Only the starter kit registers it;
|
|
9
|
+
* `createAlfakit()` registers the layouts themselves up front (`panelLayouts`), so a panel's
|
|
10
|
+
* bundle has no extra lazy entry points.
|
|
11
|
+
*/
|
|
12
|
+
export const lazyPanelLayouts: Record<string, AnyComponent> = lazyComponents<AnyComponent>({
|
|
13
|
+
'layout:fullscreen': () => import('./fullscreen-layout').then((m) => m.FullscreenLayout),
|
|
14
|
+
}).map(discoveredComponent).components
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Tailwind classes for the `maxContentWidth` setting of the panel and its pages. */
|
|
2
|
+
export const MAX_WIDTHS: Record<string, string> = {
|
|
3
|
+
'3xs': 'max-w-3xs',
|
|
4
|
+
'2xs': 'max-w-2xs',
|
|
5
|
+
xs: 'max-w-xs',
|
|
6
|
+
sm: 'max-w-sm',
|
|
7
|
+
md: 'max-w-md',
|
|
8
|
+
lg: 'max-w-lg',
|
|
9
|
+
xl: 'max-w-xl',
|
|
10
|
+
'2xl': 'max-w-2xl',
|
|
11
|
+
'3xl': 'max-w-3xl',
|
|
12
|
+
'4xl': 'max-w-4xl',
|
|
13
|
+
'5xl': 'max-w-5xl',
|
|
14
|
+
'6xl': 'max-w-6xl',
|
|
15
|
+
'7xl': 'max-w-7xl',
|
|
16
|
+
none: 'max-w-none',
|
|
17
|
+
min: 'max-w-min',
|
|
18
|
+
max: 'max-w-max',
|
|
19
|
+
fit: 'max-w-fit',
|
|
20
|
+
prose: 'max-w-prose',
|
|
21
|
+
container: 'container',
|
|
22
|
+
'screen-sm': 'max-w-(--breakpoint-sm)',
|
|
23
|
+
'screen-md': 'max-w-(--breakpoint-md)',
|
|
24
|
+
'screen-lg': 'max-w-(--breakpoint-lg)',
|
|
25
|
+
'screen-xl': 'max-w-(--breakpoint-xl)',
|
|
26
|
+
'screen-2xl': 'max-w-(--breakpoint-2xl)',
|
|
27
|
+
full: 'max-w-full',
|
|
28
|
+
screen: 'max-w-screen',
|
|
29
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AnyComponent } from '../context'
|
|
2
|
+
import { AppLayout } from './app-layout'
|
|
3
|
+
import { AuthLayout } from './auth-layout'
|
|
4
|
+
import { BlankLayout } from './blank-layout'
|
|
5
|
+
import { FullscreenLayout } from './fullscreen-layout'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The panel's own layouts (sidebar or top navigation, auth card, blank, fullscreen), which
|
|
9
|
+
* `createAlfakit()` registers up front. Starter kit pages render in the host's layout and
|
|
10
|
+
* never import these: `PageShell` loads one on demand if such a page asks for it.
|
|
11
|
+
*/
|
|
12
|
+
export const panelLayouts: Record<string, AnyComponent> = {
|
|
13
|
+
'layout:app': AppLayout,
|
|
14
|
+
'layout:auth': AuthLayout,
|
|
15
|
+
'layout:blank': BlankLayout,
|
|
16
|
+
'layout:fullscreen': FullscreenLayout,
|
|
17
|
+
}
|
|
@@ -10,8 +10,8 @@ import { Link } from '@inertiajs/react'
|
|
|
10
10
|
import { ChevronDownIcon, MenuIcon } from 'lucide-react'
|
|
11
11
|
import { createElement, useState, type ReactNode } from 'react'
|
|
12
12
|
import { NotificationsMenu } from '../components/notifications/notifications-menu'
|
|
13
|
-
import { Brand } from '../components/panel/app-sidebar'
|
|
14
13
|
import { BottomNav } from '../components/panel/bottom-nav'
|
|
14
|
+
import { Brand } from '../components/panel/brand'
|
|
15
15
|
import { DebugPanelSlot } from '../components/panel/debug-panel-slot'
|
|
16
16
|
import { GlobalSearch } from '../components/panel/global-search'
|
|
17
17
|
import { TopbarLocaleSwitcher } from '../components/panel/locale-switcher'
|
|
@@ -34,7 +34,7 @@ import { extraProps } from '../lib/attributes'
|
|
|
34
34
|
import { Icon } from '../lib/icons'
|
|
35
35
|
import { NewTabHint } from '../lib/new-tab-hint'
|
|
36
36
|
import { cn } from '../lib/utils'
|
|
37
|
-
import { MAX_WIDTHS } from './
|
|
37
|
+
import { MAX_WIDTHS } from './max-widths'
|
|
38
38
|
|
|
39
39
|
export interface TopNavLayoutProps {
|
|
40
40
|
children: ReactNode
|
package/src/pages/page.tsx
CHANGED
|
@@ -47,11 +47,15 @@ import { DataTable } from '../components/table/table-block'
|
|
|
47
47
|
import { Badge } from '../components/ui/badge'
|
|
48
48
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs'
|
|
49
49
|
import { WidgetGrid } from '../components/widgets/widgets-block'
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
import {
|
|
51
|
+
useActionRunner,
|
|
52
|
+
useAlfakit,
|
|
53
|
+
useAlfakitProps,
|
|
54
|
+
usePanel,
|
|
55
|
+
useT,
|
|
56
|
+
type AnyComponent,
|
|
57
|
+
} from '../context'
|
|
53
58
|
import { BlankLayout } from '../layouts/blank-layout'
|
|
54
|
-
import { FullscreenLayout } from '../layouts/fullscreen-layout'
|
|
55
59
|
import { badgeColorClasses, colorClass } from '../lib/colors'
|
|
56
60
|
import { Icon } from '../lib/icons'
|
|
57
61
|
import { cn } from '../lib/utils'
|
|
@@ -138,6 +142,15 @@ export function useBodyAttributes(attributes: Record<string, string> | undefined
|
|
|
138
142
|
)
|
|
139
143
|
}
|
|
140
144
|
|
|
145
|
+
/**
|
|
146
|
+
* The registered layout: `createAlfakit()` registers the panel's (`panelLayouts`), the starter
|
|
147
|
+
* kit its content layout. Without one the page renders bare: importing the panel layouts here
|
|
148
|
+
* would put the sidebar and top bar in every starter kit page.
|
|
149
|
+
*/
|
|
150
|
+
function layout(registry: { resolve(key: string): AnyComponent | undefined }, key: string) {
|
|
151
|
+
return registry.resolve(key) ?? BlankLayout
|
|
152
|
+
}
|
|
153
|
+
|
|
141
154
|
export function PageShell({ page, children }: { page: PageSchema; children: ReactNode }) {
|
|
142
155
|
const { registry } = useAlfakit()
|
|
143
156
|
|
|
@@ -146,26 +159,22 @@ export function PageShell({ page, children }: { page: PageSchema; children: Reac
|
|
|
146
159
|
// Each layout can be replaced: createAlfakit({ components: { 'layout:app': MyLayout } }).
|
|
147
160
|
if (page.layout === 'auth') {
|
|
148
161
|
return createElement(
|
|
149
|
-
registry
|
|
162
|
+
layout(registry, 'layout:auth'),
|
|
150
163
|
{ heading: page.heading ?? page.title, subheading: page.subheading, page },
|
|
151
164
|
children,
|
|
152
165
|
)
|
|
153
166
|
}
|
|
154
167
|
|
|
155
168
|
if (page.layout === 'blank') {
|
|
156
|
-
return createElement(registry
|
|
169
|
+
return createElement(layout(registry, 'layout:blank'), { page }, children)
|
|
157
170
|
}
|
|
158
171
|
|
|
159
172
|
if (page.layout === 'fullscreen') {
|
|
160
|
-
return createElement(
|
|
161
|
-
registry.resolve('layout:fullscreen') ?? FullscreenLayout,
|
|
162
|
-
{ page },
|
|
163
|
-
children,
|
|
164
|
-
)
|
|
173
|
+
return createElement(layout(registry, 'layout:fullscreen'), { page }, children)
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
return createElement(
|
|
168
|
-
registry
|
|
177
|
+
layout(registry, 'layout:app'),
|
|
169
178
|
{ breadcrumbs: page.breadcrumbs, maxContentWidth: page.maxContentWidth, page },
|
|
170
179
|
<>
|
|
171
180
|
{/* `?` overlay, `/`, `g d` and panel command shortcuts (`keyboardShortcuts()`). */}
|
|
@@ -80,16 +80,25 @@ export function AlfakitComponent({
|
|
|
80
80
|
const context = useMemo<AlfakitContextValue>(() => {
|
|
81
81
|
const poll = (target: string, interval: string | number | null | undefined) =>
|
|
82
82
|
state.poll(target, interval)
|
|
83
|
+
const refresh = (targets: string[]) => state.refresh(targets)
|
|
83
84
|
|
|
84
85
|
if (parent) {
|
|
85
|
-
return {
|
|
86
|
+
return {
|
|
87
|
+
...parent,
|
|
88
|
+
registry: createStandaloneRegistry(components, parent.registry),
|
|
89
|
+
poll,
|
|
90
|
+
refresh,
|
|
91
|
+
}
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
return {
|
|
95
|
+
...createStandaloneContext(
|
|
96
|
+
{ action: component.endpoints.action, modal: component.endpoints.modal },
|
|
97
|
+
components,
|
|
98
|
+
poll,
|
|
99
|
+
),
|
|
100
|
+
refresh,
|
|
101
|
+
}
|
|
93
102
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
94
103
|
}, [parent, state, overrideKeys])
|
|
95
104
|
|
|
@@ -122,7 +131,9 @@ export function AlfakitComponent({
|
|
|
122
131
|
useEffect(() => registerStandaloneProp(propName ?? null), [propName])
|
|
123
132
|
|
|
124
133
|
useEffect(() => {
|
|
125
|
-
void state.loadLazy()
|
|
134
|
+
void state.loadLazy(component)
|
|
135
|
+
// The token identifies a new payload.
|
|
136
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
126
137
|
}, [state, component.token])
|
|
127
138
|
|
|
128
139
|
// Toasts: use the layout's <AlfakitToaster /> when there is one, else the first component
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { resolveToastPosition, type ToastPosition } from '@alfakit/core'
|
|
1
|
+
import { resolveToastPosition, STARTER_KIT_TOASTER_ID, type ToastPosition } from '@alfakit/core'
|
|
2
2
|
import { useEffect, useMemo } from 'react'
|
|
3
|
+
import { setToasterId } from '../components/notifications/flash-toasts'
|
|
3
4
|
import { Toaster } from '../components/ui/sonner'
|
|
4
5
|
import { useOptionalAlfakit, type AlfakitContextValue } from '../context'
|
|
5
6
|
import { claimToaster, createStandaloneContext, ensureFlashToasts } from './runtime'
|
|
@@ -46,7 +47,25 @@ export function AlfakitToaster({
|
|
|
46
47
|
ensureFlashToasts(context.registry)
|
|
47
48
|
}, [context])
|
|
48
49
|
|
|
50
|
+
// Our own toaster gets an id, so a `<Toaster />` the app's layout already mounts (the
|
|
51
|
+
// starter kits have one) doesn't show every Alfakit toast a second time.
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!toaster) return undefined
|
|
54
|
+
|
|
55
|
+
setToasterId(STARTER_KIT_TOASTER_ID)
|
|
56
|
+
|
|
57
|
+
return () => setToasterId(undefined)
|
|
58
|
+
}, [toaster])
|
|
59
|
+
|
|
49
60
|
if (!toaster) return null
|
|
50
61
|
|
|
51
|
-
return
|
|
62
|
+
return (
|
|
63
|
+
<Toaster
|
|
64
|
+
id={STARTER_KIT_TOASTER_ID}
|
|
65
|
+
position={resolveToastPosition(position, dir)}
|
|
66
|
+
dir={dir}
|
|
67
|
+
richColors
|
|
68
|
+
closeButton
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
52
71
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
standaloneWidgetCount,
|
|
3
|
+
starterKitDashboardWidgets,
|
|
4
|
+
type StandaloneComponentSchema,
|
|
5
|
+
} from '@alfakit/core'
|
|
6
|
+
import { usePage } from '@inertiajs/react'
|
|
7
|
+
import type { ReactNode } from 'react'
|
|
8
|
+
import type { AnyComponent } from '../context'
|
|
9
|
+
import { lazyComponent } from '../lib/lazy'
|
|
10
|
+
import { cn } from '../lib/utils'
|
|
11
|
+
|
|
12
|
+
/** Props of `<AlfakitWidgets>`. */
|
|
13
|
+
export interface AlfakitWidgetsProps {
|
|
14
|
+
/** The `starterKit()` panel whose widgets to show (the first one by default). */
|
|
15
|
+
panel?: string
|
|
16
|
+
/** Frontend components of custom widgets (`{ RevenueWidget }`), as for `<AlfakitComponent>`. */
|
|
17
|
+
components?: Record<string, AnyComponent>
|
|
18
|
+
className?: string
|
|
19
|
+
/** Rendered instead when there are no widgets to show. */
|
|
20
|
+
children?: ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface LoadedProps {
|
|
24
|
+
component: StandaloneComponentSchema
|
|
25
|
+
components?: Record<string, AnyComponent>
|
|
26
|
+
className?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const LoadedWidgets = lazyComponent<LoadedProps>(
|
|
30
|
+
() => import('../standalone/alfakit-component').then((module) => module.AlfakitComponent),
|
|
31
|
+
(props) => <WidgetsPlaceholder {...props} />,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
/** Widget-sized boxes until the widgets' code has loaded (also during SSR). */
|
|
35
|
+
function WidgetsPlaceholder({ component, className }: LoadedProps) {
|
|
36
|
+
const count = standaloneWidgetCount(component)
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="starter-kit-widgets-placeholder"
|
|
41
|
+
aria-busy="true"
|
|
42
|
+
className={cn('grid auto-rows-min gap-4 md:grid-cols-2', className)}
|
|
43
|
+
>
|
|
44
|
+
{Array.from({ length: Math.max(count, 1) }, (_, index) => (
|
|
45
|
+
<div key={index} aria-hidden="true" className="bg-muted h-28 animate-pulse rounded-xl" />
|
|
46
|
+
))}
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The dashboard widgets of a `starterKit()` panel on the host's own dashboard. Reads the
|
|
53
|
+
* `alfakitWidgets` prop (shared on the panel's `starterKitDashboard()` pages only, the
|
|
54
|
+
* starter kit's `/dashboard` by default) and renders the widgets with the host's theme, as
|
|
55
|
+
* `<AlfakitComponent>` renders standalone widgets: lazy widgets, polling and table widget
|
|
56
|
+
* actions go through the panel's endpoints. Its code loads in the browser; the server
|
|
57
|
+
* render and hydration show placeholders. Renders `children` (the kit's placeholder boxes)
|
|
58
|
+
* when there are no widgets to show: other pages, guests, users who may not open the panel.
|
|
59
|
+
*
|
|
60
|
+
* ```tsx
|
|
61
|
+
* import { AlfakitWidgets } from '@alfakit/react'
|
|
62
|
+
*
|
|
63
|
+
* <AlfakitWidgets>
|
|
64
|
+
* <MyEmptyState />
|
|
65
|
+
* </AlfakitWidgets>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function AlfakitWidgets({ panel, components, className, children }: AlfakitWidgetsProps) {
|
|
69
|
+
const component = starterKitDashboardWidgets(usePage().props, panel)
|
|
70
|
+
|
|
71
|
+
if (!component) return <>{children}</>
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div data-slot="starter-kit-widgets">
|
|
75
|
+
<LoadedWidgets component={component} components={components} className={className} />
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ImpersonationSchema } from '@alfakit/core'
|
|
2
|
+
import { useEffect, useState, useSyncExternalStore } from 'react'
|
|
3
|
+
import { ImpersonationBanner } from '../components/panel/impersonation-banner'
|
|
4
|
+
|
|
5
|
+
// The header components appear in both header layouts and next to each other: the first
|
|
6
|
+
// one mounted shows the banner, the others render nothing.
|
|
7
|
+
let owner: symbol | null = null
|
|
8
|
+
const listeners = new Set<() => void>()
|
|
9
|
+
|
|
10
|
+
function setOwner(next: symbol | null): void {
|
|
11
|
+
owner = next
|
|
12
|
+
listeners.forEach((listener) => listener())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function subscribe(listener: () => void): () => void {
|
|
16
|
+
listeners.add(listener)
|
|
17
|
+
|
|
18
|
+
return () => listeners.delete(listener)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The impersonation banner (who you are signed in as, **Leave**) on the host's own pages,
|
|
23
|
+
* rendered by the header components. Loaded only while impersonating; AlfaKit's pages
|
|
24
|
+
* render their own (`AlfakitRoot`).
|
|
25
|
+
*/
|
|
26
|
+
export default function StarterKitImpersonationBanner({
|
|
27
|
+
impersonation,
|
|
28
|
+
}: {
|
|
29
|
+
impersonation: ImpersonationSchema
|
|
30
|
+
}) {
|
|
31
|
+
const [id] = useState(() => Symbol('impersonation-banner'))
|
|
32
|
+
// Claimed once mounted (effects never run on the server), so SSR and hydration skip it.
|
|
33
|
+
const owns = useSyncExternalStore(
|
|
34
|
+
subscribe,
|
|
35
|
+
() => owner === id,
|
|
36
|
+
() => false,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (owner === null) setOwner(id)
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
if (owner === id) setOwner(null)
|
|
44
|
+
}
|
|
45
|
+
}, [id])
|
|
46
|
+
|
|
47
|
+
return owns ? <ImpersonationBanner impersonation={impersonation} /> : null
|
|
48
|
+
}
|
package/src/starter-kit/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { AlfakitWidgets, type AlfakitWidgetsProps } from './dashboard-widgets'
|
|
1
2
|
export { useAlfakitNavigation, type AlfakitNavigation } from './navigation'
|
|
2
3
|
export {
|
|
3
4
|
AlfakitStarterKitRoot,
|
|
@@ -7,6 +8,7 @@ export {
|
|
|
7
8
|
type StarterKitPageOptions,
|
|
8
9
|
} from './starter-kit'
|
|
9
10
|
export {
|
|
11
|
+
AlfakitCopilot,
|
|
10
12
|
AlfakitNotifications,
|
|
11
13
|
AlfakitSearch,
|
|
12
14
|
AlfakitTenantSwitcher,
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveRequestErrorNotification,
|
|
9
9
|
starterKitBreadcrumbsFromProps,
|
|
10
10
|
syncStarterKitPage,
|
|
11
|
+
usesStarterKitHostLayout,
|
|
11
12
|
type AlfakitOptions,
|
|
12
13
|
type AlfakitPageProps,
|
|
13
14
|
type StarterKitBreadcrumb,
|
|
@@ -23,7 +24,11 @@ import {
|
|
|
23
24
|
type AnyComponent,
|
|
24
25
|
} from '../context'
|
|
25
26
|
import { AlfakitRoot } from '../create-alfakit'
|
|
26
|
-
import {
|
|
27
|
+
import type { AppLayoutProps } from '../layouts/app-layout'
|
|
28
|
+
import { AuthLayout } from '../layouts/auth-layout'
|
|
29
|
+
import { lazyPanelLayouts } from '../layouts/lazy-layouts'
|
|
30
|
+
import { MAX_WIDTHS } from '../layouts/max-widths'
|
|
31
|
+
import { discoverComponents } from '../lib/discovery'
|
|
27
32
|
import { preloadIcons } from '../lib/icons'
|
|
28
33
|
import { cn } from '../lib/utils'
|
|
29
34
|
import { AlfakitPage } from '../pages/page'
|
|
@@ -45,6 +50,12 @@ export interface StarterKitPageOptions extends Pick<
|
|
|
45
50
|
* `null` renders the pages without a host layout.
|
|
46
51
|
*/
|
|
47
52
|
layout?: StarterKitLayout | null
|
|
53
|
+
/**
|
|
54
|
+
* Auto-registered custom components, as for `createAlfakit({ discover })`: pass
|
|
55
|
+
* `import.meta.glob('../../alfakit/{fields,columns,entries,widgets,pages,components,blocks}/**\/*.tsx')`
|
|
56
|
+
* from `resources/js/pages/Alfakit/Page.tsx` and every file there registers under its name.
|
|
57
|
+
*/
|
|
58
|
+
discover?: AlfakitOptions<AnyComponent>['discover']
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
/**
|
|
@@ -78,14 +89,21 @@ export function StarterKitContentLayout({ children, maxContentWidth }: AppLayout
|
|
|
78
89
|
let browserContext: AlfakitContextValue | null = null
|
|
79
90
|
|
|
80
91
|
function createStarterKitContext(options: StarterKitPageOptions): AlfakitContextValue {
|
|
92
|
+
// The host's layout replaces the panel's. Auth pages (invitations, tenant registration)
|
|
93
|
+
// keep the panel's card; the fullscreen layout loads on demand.
|
|
94
|
+
// Auto-registered files sit between the built-ins and the explicit `components` / `pages`.
|
|
95
|
+
const discovered = discoverComponents(options.discover)
|
|
81
96
|
const registry = createDefaultRegistry()
|
|
97
|
+
.registerMany(lazyPanelLayouts)
|
|
98
|
+
.register('layout:auth', AuthLayout)
|
|
82
99
|
.register('layout:app', StarterKitContentLayout)
|
|
100
|
+
.registerMany(discovered.components)
|
|
83
101
|
.registerMany(options.components ?? {})
|
|
84
102
|
const translator = new Translator()
|
|
85
103
|
|
|
86
104
|
return {
|
|
87
105
|
registry,
|
|
88
|
-
pages: { 'Alfakit/Page': AlfakitPage, ...options.pages },
|
|
106
|
+
pages: { 'Alfakit/Page': AlfakitPage, ...discovered.pages, ...options.pages },
|
|
89
107
|
translator,
|
|
90
108
|
actions: new ActionRunner({
|
|
91
109
|
endpoints: { action: '', modal: '' },
|
|
@@ -155,7 +173,16 @@ export function AlfakitStarterKitRoot({
|
|
|
155
173
|
|
|
156
174
|
return (
|
|
157
175
|
<AlfakitProvider value={context}>
|
|
158
|
-
|
|
176
|
+
{/* The reading direction and language of Alfakit's content only: the host layout around
|
|
177
|
+
it (sidebar, header) and the app's own pages keep the document's. */}
|
|
178
|
+
<div
|
|
179
|
+
data-slot="starter-kit-root"
|
|
180
|
+
dir={panel?.localization?.direction ?? 'ltr'}
|
|
181
|
+
lang={panel?.localization?.tag}
|
|
182
|
+
className="contents"
|
|
183
|
+
>
|
|
184
|
+
<AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>{children}</AlfakitRoot>
|
|
185
|
+
</div>
|
|
159
186
|
</AlfakitProvider>
|
|
160
187
|
)
|
|
161
188
|
}
|
|
@@ -186,7 +213,7 @@ export function createStarterKitPage(options: StarterKitPageOptions = {}) {
|
|
|
186
213
|
|
|
187
214
|
// A layout callback: the host layout (with the page's breadcrumbs), then Alfakit's root.
|
|
188
215
|
AlfakitStarterKitPage.layout = (props: Record<string, unknown>) => [
|
|
189
|
-
...(options.layout
|
|
216
|
+
...(options.layout && usesStarterKitHostLayout(props)
|
|
190
217
|
? [[options.layout, { breadcrumbs: starterKitBreadcrumbsFromProps(props) }] as const]
|
|
191
218
|
: []),
|
|
192
219
|
[AlfakitStarterKitRoot, { options }] as const,
|
|
@@ -7,8 +7,10 @@ import {
|
|
|
7
7
|
loadStarterKitPanel,
|
|
8
8
|
loadedStarterKitPanel,
|
|
9
9
|
openRequestedStarterKitSearch,
|
|
10
|
+
starterKitCopilot,
|
|
10
11
|
starterKitWidgetsPage,
|
|
11
12
|
starterKitWidgetsPanel,
|
|
13
|
+
takeRequestedStarterKitCopilot,
|
|
12
14
|
tenantNameText,
|
|
13
15
|
type PanelSchema,
|
|
14
16
|
type StarterKitWidgets,
|
|
@@ -17,6 +19,7 @@ import { router, usePage } from '@inertiajs/react'
|
|
|
17
19
|
import { ChevronsUpDownIcon, PlusIcon } from 'lucide-react'
|
|
18
20
|
import { Direction } from 'radix-ui'
|
|
19
21
|
import { useEffect, useMemo, useState, type ReactNode } from 'react'
|
|
22
|
+
import { AiCopilot } from '../components/ai/copilot'
|
|
20
23
|
import { NotificationsMenu } from '../components/notifications/notifications-menu'
|
|
21
24
|
import { RealtimeNotifications } from '../components/notifications/realtime-notifications'
|
|
22
25
|
import { GlobalSearch } from '../components/panel/global-search'
|
|
@@ -41,6 +44,7 @@ import {
|
|
|
41
44
|
} from '../context'
|
|
42
45
|
import { Icon } from '../lib/icons'
|
|
43
46
|
import {
|
|
47
|
+
CopilotTrigger,
|
|
44
48
|
NotificationsTrigger,
|
|
45
49
|
SearchTrigger,
|
|
46
50
|
TenantSwitcherTrigger,
|
|
@@ -162,6 +166,24 @@ export function LoadedTenantSwitcher({ source }: WidgetProps) {
|
|
|
162
166
|
)
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
/** The AI copilot (the panel pages' `topbar.end` slot component). */
|
|
170
|
+
export function LoadedCopilot({ source }: WidgetProps) {
|
|
171
|
+
const panel = useWidgetsPanel(source)
|
|
172
|
+
// A click or the key bindings before the copilot loaded open it at once.
|
|
173
|
+
const [autoOpen] = useState(() => takeRequestedStarterKitCopilot())
|
|
174
|
+
const component = useMemo(() => (panel ? starterKitCopilot(panel) : null), [panel])
|
|
175
|
+
|
|
176
|
+
if (!panel) return <CopilotTrigger source={source} />
|
|
177
|
+
|
|
178
|
+
if (!component) return null
|
|
179
|
+
|
|
180
|
+
return (
|
|
181
|
+
<WidgetShell panel={panel}>
|
|
182
|
+
<AiCopilot component={component} autoOpen={autoOpen || takeRequestedStarterKitCopilot()} />
|
|
183
|
+
</WidgetShell>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
165
187
|
/** The user's tenants on a page outside the panel (no current tenant). */
|
|
166
188
|
function TenantChooser({ panel }: { panel: PanelSchema }) {
|
|
167
189
|
const t = useT()
|
|
@@ -207,7 +229,7 @@ function TenantChooser({ panel }: { panel: PanelSchema }) {
|
|
|
207
229
|
{tenancy.tenants.length > 0 && <DropdownMenuSeparator />}
|
|
208
230
|
<DropdownMenuItem onSelect={() => router.visit(tenancy.registrationUrl!)}>
|
|
209
231
|
<Icon name="alfakit::panels.tenant-menu.registration-button" fallback={PlusIcon} />
|
|
210
|
-
{t('tenancy.register', {}, 'New')}
|
|
232
|
+
{tenancy.registrationLabel ?? t('tenancy.register', {}, 'New')}
|
|
211
233
|
</DropdownMenuItem>
|
|
212
234
|
</>
|
|
213
235
|
)}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatKeyBinding,
|
|
3
3
|
initials,
|
|
4
|
+
registerStarterKitCopilotShortcut,
|
|
4
5
|
registerStarterKitSearchShortcut,
|
|
6
|
+
requestStarterKitCopilot,
|
|
5
7
|
requestStarterKitSearch,
|
|
6
8
|
tenantNameText,
|
|
7
9
|
type StarterKitWidgets,
|
|
8
10
|
} from '@alfakit/core'
|
|
9
|
-
import { BellIcon, ChevronsUpDownIcon, SearchIcon } from 'lucide-react'
|
|
11
|
+
import { BellIcon, ChevronsUpDownIcon, SearchIcon, SparklesIcon } from 'lucide-react'
|
|
10
12
|
import { useEffect } from 'react'
|
|
11
13
|
import { useHydrated } from '../hooks/use-hydrated'
|
|
12
14
|
|
|
@@ -49,7 +51,7 @@ export function SearchTrigger({ source, onLoad }: WidgetProps & { onLoad?: () =>
|
|
|
49
51
|
<SearchIcon className="size-4" aria-hidden="true" />
|
|
50
52
|
<span className="sr-only sm:not-sr-only sm:flex-1 sm:text-start">{placeholder}</span>
|
|
51
53
|
{hydrated && binding && (
|
|
52
|
-
<kbd className="bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline">
|
|
54
|
+
<kbd dir="ltr" className="bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline">
|
|
53
55
|
{formatKeyBinding(binding)}
|
|
54
56
|
</kbd>
|
|
55
57
|
)}
|
|
@@ -101,3 +103,36 @@ export function TenantSwitcherTrigger({ source }: WidgetProps) {
|
|
|
101
103
|
</button>
|
|
102
104
|
)
|
|
103
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The AI copilot's button until its code has loaded: the same look, and it remembers a
|
|
109
|
+
* click or its key bindings (`mod+j`), so the copilot opens once loaded.
|
|
110
|
+
*/
|
|
111
|
+
export function CopilotTrigger({ source, onLoad }: WidgetProps & { onLoad?: () => void }) {
|
|
112
|
+
const copilot = source.widgets.copilot
|
|
113
|
+
const keys = JSON.stringify(copilot?.keyBindings ?? [])
|
|
114
|
+
const label = copilot?.label ?? ''
|
|
115
|
+
// `onLoad` is a stable function (it starts loading the copilot's code).
|
|
116
|
+
useEffect(
|
|
117
|
+
() => registerStarterKitCopilotShortcut(JSON.parse(keys) as string[], label, () => onLoad?.()),
|
|
118
|
+
[keys, label, onLoad],
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<button
|
|
123
|
+
type="button"
|
|
124
|
+
data-slot="ai-copilot-trigger"
|
|
125
|
+
aria-label={label}
|
|
126
|
+
aria-haspopup="dialog"
|
|
127
|
+
title={label}
|
|
128
|
+
className="hover:bg-accent hover:text-accent-foreground inline-flex size-9 items-center justify-center rounded-md"
|
|
129
|
+
onPointerEnter={() => onLoad?.()}
|
|
130
|
+
onClick={() => {
|
|
131
|
+
requestStarterKitCopilot()
|
|
132
|
+
onLoad?.()
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
<SparklesIcon className="size-4" aria-hidden="true" />
|
|
136
|
+
</button>
|
|
137
|
+
)
|
|
138
|
+
}
|