@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
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Link } from '@inertiajs/react'
|
|
2
|
+
import { usePanel } from '../../context'
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The panel's logo (or initial) and name, linking to the panel home. Its own module, so the
|
|
7
|
+
* auth layout doesn't import the sidebar.
|
|
8
|
+
*/
|
|
9
|
+
export function Brand({ className }: { className?: string }) {
|
|
10
|
+
const panel = usePanel()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<Link
|
|
14
|
+
href={panel.url ?? '/'}
|
|
15
|
+
className={cn('flex items-center gap-2 overflow-hidden', className)}
|
|
16
|
+
>
|
|
17
|
+
{panel.brandLogo ? (
|
|
18
|
+
<>
|
|
19
|
+
<img
|
|
20
|
+
src={panel.brandLogo}
|
|
21
|
+
alt={panel.brandName}
|
|
22
|
+
style={{ height: panel.brandLogoHeight ?? '1.75rem' }}
|
|
23
|
+
className={cn('max-w-full object-contain', panel.darkModeBrandLogo && 'dark:hidden')}
|
|
24
|
+
/>
|
|
25
|
+
{panel.darkModeBrandLogo && (
|
|
26
|
+
<img
|
|
27
|
+
src={panel.darkModeBrandLogo}
|
|
28
|
+
alt={panel.brandName}
|
|
29
|
+
style={{ height: panel.brandLogoHeight ?? '1.75rem' }}
|
|
30
|
+
className="hidden max-w-full object-contain dark:block"
|
|
31
|
+
/>
|
|
32
|
+
)}
|
|
33
|
+
</>
|
|
34
|
+
) : (
|
|
35
|
+
<>
|
|
36
|
+
<span className="bg-primary text-primary-foreground flex size-8 shrink-0 items-center justify-center rounded-lg text-sm font-bold">
|
|
37
|
+
{panel.brandName.charAt(0)}
|
|
38
|
+
</span>
|
|
39
|
+
<span className="truncate text-sm font-semibold group-data-[collapsible=icon]:hidden">
|
|
40
|
+
{panel.brandName}
|
|
41
|
+
</span>
|
|
42
|
+
</>
|
|
43
|
+
)}
|
|
44
|
+
</Link>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -133,7 +133,7 @@ export function GlobalSearch({ variant = 'topbar' }: { variant?: 'topbar' | 'sid
|
|
|
133
133
|
</span>
|
|
134
134
|
)}
|
|
135
135
|
{hydrated && suffix?.type === 'keyBinding' && (
|
|
136
|
-
<kbd className="bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline">
|
|
136
|
+
<kbd dir="ltr" className="bg-muted hidden rounded px-1 font-mono text-[10px] sm:inline">
|
|
137
137
|
{formatKeyBinding(suffix.keyBinding)}
|
|
138
138
|
</kbd>
|
|
139
139
|
)}
|
|
@@ -24,7 +24,10 @@ export function ShortcutKeys({ binding, className }: { binding: string; classNam
|
|
|
24
24
|
{t('shortcuts.then', {}, 'then')}
|
|
25
25
|
</span>
|
|
26
26
|
)}
|
|
27
|
-
<kbd
|
|
27
|
+
<kbd
|
|
28
|
+
dir="ltr"
|
|
29
|
+
className="bg-muted text-muted-foreground rounded border px-1.5 font-mono text-[10px] leading-5"
|
|
30
|
+
>
|
|
28
31
|
{step}
|
|
29
32
|
</kbd>
|
|
30
33
|
</span>
|
|
@@ -151,7 +151,7 @@ export function TenantMenu({ variant = 'sidebar' }: { variant?: 'sidebar' | 'top
|
|
|
151
151
|
<DropdownMenuItem asChild>
|
|
152
152
|
<Link href={tenancy.profileUrl} className="w-full">
|
|
153
153
|
<Icon name="alfakit::panels.tenant-menu.profile-button" fallback={SettingsIcon} />{' '}
|
|
154
|
-
{t('tenancy.profile', {}, 'Settings')}
|
|
154
|
+
{tenancy.profileLabel ?? t('tenancy.profile', {}, 'Settings')}
|
|
155
155
|
</Link>
|
|
156
156
|
</DropdownMenuItem>
|
|
157
157
|
)
|
|
@@ -164,7 +164,7 @@ export function TenantMenu({ variant = 'sidebar' }: { variant?: 'sidebar' | 'top
|
|
|
164
164
|
<DropdownMenuItem asChild>
|
|
165
165
|
<Link href={tenancy.registrationUrl} className="w-full">
|
|
166
166
|
<Icon name="alfakit::panels.tenant-menu.registration-button" fallback={PlusIcon} />{' '}
|
|
167
|
-
{t('tenancy.register', {}, 'New')}
|
|
167
|
+
{tenancy.registrationLabel ?? t('tenancy.register', {}, 'New')}
|
|
168
168
|
</Link>
|
|
169
169
|
</DropdownMenuItem>
|
|
170
170
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LiveUpdates, shownRecordKeys, type LiveUpdatesSchema, type RecordKey } from '@alfakit/core'
|
|
2
2
|
import { ArrowUpIcon } from 'lucide-react'
|
|
3
3
|
import { useEffect, useMemo } from 'react'
|
|
4
|
-
import { useChoice, usePollComponent } from '../../context'
|
|
4
|
+
import { useChoice, usePollComponent, useRefreshComponents } from '../../context'
|
|
5
5
|
import { useStore } from '../../hooks/use-store'
|
|
6
6
|
import { Button } from '../ui/button'
|
|
7
7
|
|
|
@@ -16,15 +16,16 @@ export function useLiveUpdates(
|
|
|
16
16
|
options: { polling?: boolean; records?: ReadonlyArray<{ key?: RecordKey }> | null } = {},
|
|
17
17
|
): LiveUpdates | null {
|
|
18
18
|
const poll = usePollComponent()
|
|
19
|
+
const refresh = useRefreshComponents()
|
|
19
20
|
const key = live ? JSON.stringify(live) : ''
|
|
20
21
|
const polling = options.polling
|
|
21
22
|
const records = options.records
|
|
22
23
|
|
|
23
24
|
const controller = useMemo(
|
|
24
|
-
() => (live ? new LiveUpdates({ live, target, polling, poll }) : null),
|
|
25
|
+
() => (live ? new LiveUpdates({ live, target, polling, poll, refresh }) : null),
|
|
25
26
|
// `live` is compared by value (`key`): a reload brings a new object with the same settings.
|
|
26
27
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
|
-
[key, target, polling, poll],
|
|
28
|
+
[key, target, polling, poll, refresh],
|
|
28
29
|
)
|
|
29
30
|
|
|
30
31
|
// Updates of records on other pages don't refresh the table.
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from '@alfakit/core'
|
|
27
27
|
import { router } from '@inertiajs/react'
|
|
28
28
|
import { CheckIcon, ChevronsUpDownIcon } from 'lucide-react'
|
|
29
|
-
import { createElement, useEffect, useRef, useState } from 'react'
|
|
29
|
+
import { createElement, useEffect, useId, useRef, useState } from 'react'
|
|
30
30
|
import { usePanel, useRegistry, useT } from '../../context'
|
|
31
31
|
import { extraProps } from '../../lib/attributes'
|
|
32
32
|
import {
|
|
@@ -60,15 +60,22 @@ export interface CellProps {
|
|
|
60
60
|
recordKey: RecordKey
|
|
61
61
|
token: string
|
|
62
62
|
component: string
|
|
63
|
+
/**
|
|
64
|
+
* Set when this renderer was picked from the registry (`column:<type>`): it then renders
|
|
65
|
+
* the built-in cell instead of looking itself up again, so an ejected `TableCellContent`
|
|
66
|
+
* registered for a column type doesn't render itself forever.
|
|
67
|
+
*/
|
|
68
|
+
fromRegistry?: boolean
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export function TableCellContent(props: CellProps) {
|
|
66
72
|
const { column } = props
|
|
73
|
+
const registry = useRegistry()
|
|
67
74
|
// A component registered as `column:<type>` replaces the built-in cell renderer.
|
|
68
|
-
const Custom =
|
|
75
|
+
const Custom = props.fromRegistry ? undefined : registry.resolve(columnRegistryKey(column))
|
|
69
76
|
|
|
70
77
|
if (Custom) {
|
|
71
|
-
return createElement(Custom, props)
|
|
78
|
+
return createElement(Custom, { ...props, fromRegistry: true })
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
switch (column.type) {
|
|
@@ -587,6 +594,8 @@ function NativeSelectCell(props: CellProps) {
|
|
|
587
594
|
cell.state === null || cell.state === undefined ? '' : String(cell.state),
|
|
588
595
|
)
|
|
589
596
|
|
|
597
|
+
const errorId = useId()
|
|
598
|
+
|
|
590
599
|
return (
|
|
591
600
|
<span onClick={(event) => event.stopPropagation()}>
|
|
592
601
|
<select
|
|
@@ -596,7 +605,9 @@ function NativeSelectCell(props: CellProps) {
|
|
|
596
605
|
setValue(event.target.value)
|
|
597
606
|
void update(event.target.value === '' ? null : event.target.value)
|
|
598
607
|
}}
|
|
608
|
+
aria-label={column.label || column.name}
|
|
599
609
|
aria-invalid={error ? true : undefined}
|
|
610
|
+
aria-describedby={error ? errorId : undefined}
|
|
600
611
|
className={cn(
|
|
601
612
|
'border-input dark:bg-input/30 focus-visible:ring-ring/50 h-8 min-w-32 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]',
|
|
602
613
|
error && 'border-destructive',
|
|
@@ -611,7 +622,11 @@ function NativeSelectCell(props: CellProps) {
|
|
|
611
622
|
</option>
|
|
612
623
|
))}
|
|
613
624
|
</select>
|
|
614
|
-
{error &&
|
|
625
|
+
{error && (
|
|
626
|
+
<span id={errorId} className="text-destructive block text-xs">
|
|
627
|
+
{error}
|
|
628
|
+
</span>
|
|
629
|
+
)}
|
|
615
630
|
</span>
|
|
616
631
|
)
|
|
617
632
|
}
|
|
@@ -626,6 +641,7 @@ function TextInputCell(props: CellProps) {
|
|
|
626
641
|
const initial = cell.state === null || cell.state === undefined ? '' : String(cell.state)
|
|
627
642
|
// `prefix()` / `suffix()`: text affixes around the input, like the TextInput field.
|
|
628
643
|
const grouped = Boolean(cell.prefix || cell.suffix)
|
|
644
|
+
const errorId = useId()
|
|
629
645
|
|
|
630
646
|
const field = (
|
|
631
647
|
<span className={cn('relative block', grouped && 'min-w-0 flex-1')}>
|
|
@@ -648,7 +664,9 @@ function TextInputCell(props: CellProps) {
|
|
|
648
664
|
step={schema.step}
|
|
649
665
|
inputMode={schema.inputMode as React.HTMLAttributes<HTMLInputElement>['inputMode']}
|
|
650
666
|
disabled={Boolean(cell.disabled) || saving}
|
|
667
|
+
aria-label={column.label || column.name}
|
|
651
668
|
aria-invalid={error ? true : undefined}
|
|
669
|
+
aria-describedby={error ? errorId : undefined}
|
|
652
670
|
className={cn(
|
|
653
671
|
'h-8 min-w-32',
|
|
654
672
|
schema.prefixIcon && 'ps-8',
|
|
@@ -706,7 +724,11 @@ function TextInputCell(props: CellProps) {
|
|
|
706
724
|
) : (
|
|
707
725
|
field
|
|
708
726
|
)}
|
|
709
|
-
{error &&
|
|
727
|
+
{error && (
|
|
728
|
+
<span id={errorId} className="text-destructive block text-xs">
|
|
729
|
+
{error}
|
|
730
|
+
</span>
|
|
731
|
+
)}
|
|
710
732
|
</span>
|
|
711
733
|
)
|
|
712
734
|
}
|
|
@@ -193,24 +193,26 @@ export function ColumnManager({
|
|
|
193
193
|
|
|
194
194
|
const orderChanged = Boolean(orderDraft && columnOrderChanged(appliedNames, orderDraft))
|
|
195
195
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
visitTable(table.identifier, { columns: draft }, ['page'])
|
|
201
|
-
}
|
|
196
|
+
// The order is saved before the visibility visit: both requests share the session, and a
|
|
197
|
+
// visit running alongside the save would write the old order back when it finishes.
|
|
198
|
+
const apply = async () => {
|
|
199
|
+
const columns = draft && columnDraftChanged(table.visibleColumns, draft) ? draft : null
|
|
202
200
|
|
|
203
201
|
setDraft(null)
|
|
204
202
|
setOrderDraft(null)
|
|
205
203
|
setAnnouncement('')
|
|
206
204
|
setOpen(false)
|
|
205
|
+
|
|
206
|
+
if (orderDraft && orderChanged) await save(orderDraft)
|
|
207
|
+
|
|
208
|
+
if (columns) visitTable(table.identifier, { columns }, ['page'])
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
// Reset: the defined column order and, for toggleable columns, the default visibility.
|
|
210
|
-
const reset = () => {
|
|
212
|
+
const reset = async () => {
|
|
211
213
|
if (reorderable) {
|
|
212
214
|
setOrderDraft(null)
|
|
213
|
-
|
|
215
|
+
await save([])
|
|
214
216
|
}
|
|
215
217
|
|
|
216
218
|
if (toggleable) {
|
|
@@ -237,7 +239,7 @@ export function ColumnManager({
|
|
|
237
239
|
disabled={saving}
|
|
238
240
|
data-slot="column-manager-reset"
|
|
239
241
|
className={cn(!applyView && 'w-full')}
|
|
240
|
-
onClick={reset}
|
|
242
|
+
onClick={() => void reset()}
|
|
241
243
|
/>
|
|
242
244
|
) : null
|
|
243
245
|
const inHeader = table.columnManagerResetActionPosition === 'header'
|
|
@@ -394,7 +396,7 @@ export function ColumnManager({
|
|
|
394
396
|
disabled={
|
|
395
397
|
!orderChanged && (!draft || !columnDraftChanged(table.visibleColumns, draft))
|
|
396
398
|
}
|
|
397
|
-
onClick={apply}
|
|
399
|
+
onClick={() => void apply()}
|
|
398
400
|
/>
|
|
399
401
|
)}
|
|
400
402
|
</div>
|
|
@@ -313,13 +313,17 @@ export function ContentLayoutSort({
|
|
|
313
313
|
if (sortable.length === 0) return null
|
|
314
314
|
|
|
315
315
|
const direction = table.sort?.direction ?? 'asc'
|
|
316
|
+
// A default sort on a column the select doesn't list (`created_at`) shows the placeholder.
|
|
317
|
+
const selected = sortable.some((column) => column.name === table.sort?.column)
|
|
318
|
+
? (table.sort?.column ?? '')
|
|
319
|
+
: ''
|
|
316
320
|
|
|
317
321
|
return (
|
|
318
322
|
<div className="flex items-center gap-1">
|
|
319
323
|
<label className="flex items-center gap-2 text-sm">
|
|
320
324
|
<span className="sr-only">{t('table.sort_by', {}, 'Sort by')}</span>
|
|
321
325
|
<select
|
|
322
|
-
value={
|
|
326
|
+
value={selected}
|
|
323
327
|
onChange={(event) =>
|
|
324
328
|
visitTable(
|
|
325
329
|
table.identifier,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { ChevronDownIcon, ChevronRightIcon, LayersIcon } from 'lucide-react'
|
|
15
15
|
import { useEffect, useState } from 'react'
|
|
16
16
|
import { useT } from '../../context'
|
|
17
|
+
import { colorClass, textColorClasses } from '../../lib/colors'
|
|
17
18
|
import { Icon } from '../../lib/icons'
|
|
18
19
|
import { cn } from '../../lib/utils'
|
|
19
20
|
import { Checkbox } from '../ui/checkbox'
|
|
@@ -183,7 +184,7 @@ export function GroupHeaderRow({
|
|
|
183
184
|
selection?: { checked: boolean | 'indeterminate'; onChange: (checked: boolean) => void }
|
|
184
185
|
}) {
|
|
185
186
|
const t = useT()
|
|
186
|
-
const
|
|
187
|
+
const text = (
|
|
187
188
|
<span className="flex flex-col items-start">
|
|
188
189
|
<span className="font-medium">{group.title}</span>
|
|
189
190
|
{group.description && (
|
|
@@ -191,6 +192,19 @@ export function GroupHeaderRow({
|
|
|
191
192
|
)}
|
|
192
193
|
</span>
|
|
193
194
|
)
|
|
195
|
+
// An enum group shows its `HasIcon` icon, in its `HasColor` colour, before the title.
|
|
196
|
+
const content = group.icon ? (
|
|
197
|
+
<span data-slot="table-group-title" className="flex items-center gap-2">
|
|
198
|
+
<Icon
|
|
199
|
+
name={group.icon}
|
|
200
|
+
className={cn('size-4 shrink-0', colorClass(textColorClasses, group.color, 'gray'))}
|
|
201
|
+
aria-hidden="true"
|
|
202
|
+
/>
|
|
203
|
+
{text}
|
|
204
|
+
</span>
|
|
205
|
+
) : (
|
|
206
|
+
text
|
|
207
|
+
)
|
|
194
208
|
|
|
195
209
|
return (
|
|
196
210
|
<TableRow data-slot="table-group" className="bg-muted/50 hover:bg-muted/50">
|
|
@@ -14,12 +14,13 @@ import {
|
|
|
14
14
|
ChevronsRightIcon,
|
|
15
15
|
} from 'lucide-react'
|
|
16
16
|
import { useEffect, useRef, useState } from 'react'
|
|
17
|
-
import { useT } from '../../context'
|
|
17
|
+
import { useFormatter, useT } from '../../context'
|
|
18
18
|
import { Icon } from '../../lib/icons'
|
|
19
19
|
import { Button } from '../ui/button'
|
|
20
20
|
|
|
21
21
|
export function TablePagination({ table }: { table: TableSchema }) {
|
|
22
22
|
const t = useT()
|
|
23
|
+
const format = useFormatter()
|
|
23
24
|
const pagination = table.pagination
|
|
24
25
|
|
|
25
26
|
if (!pagination) {
|
|
@@ -41,13 +42,17 @@ export function TablePagination({ table }: { table: TableSchema }) {
|
|
|
41
42
|
const summary = numbered
|
|
42
43
|
? t(
|
|
43
44
|
'table.showing',
|
|
44
|
-
{
|
|
45
|
-
|
|
45
|
+
{
|
|
46
|
+
first: format.number(pagination.from),
|
|
47
|
+
last: format.number(pagination.to),
|
|
48
|
+
total: format.number(pagination.total),
|
|
49
|
+
},
|
|
50
|
+
'Showing :first–:last of :total',
|
|
46
51
|
)
|
|
47
52
|
: t(
|
|
48
53
|
'table.showing_range',
|
|
49
|
-
{ first: pagination.from, last: pagination.to },
|
|
50
|
-
|
|
54
|
+
{ first: format.number(pagination.from), last: format.number(pagination.to) },
|
|
55
|
+
'Showing :first–:last',
|
|
51
56
|
)
|
|
52
57
|
|
|
53
58
|
return (
|
|
@@ -114,7 +119,7 @@ export function TablePagination({ table }: { table: TableSchema }) {
|
|
|
114
119
|
onClick={() => visitTable(table.identifier, { page }, ['page'])}
|
|
115
120
|
aria-current={page === pagination.currentPage ? 'page' : undefined}
|
|
116
121
|
>
|
|
117
|
-
{page}
|
|
122
|
+
{format.number(page)}
|
|
118
123
|
</Button>
|
|
119
124
|
),
|
|
120
125
|
)}
|
|
@@ -169,6 +174,7 @@ function PerPageSelect({
|
|
|
169
174
|
pagination: AnyPaginationSchema
|
|
170
175
|
}) {
|
|
171
176
|
const t = useT()
|
|
177
|
+
const format = useFormatter()
|
|
172
178
|
|
|
173
179
|
if (pagination.pageOptions.length <= 1) return null
|
|
174
180
|
|
|
@@ -188,7 +194,7 @@ function PerPageSelect({
|
|
|
188
194
|
>
|
|
189
195
|
{pagination.pageOptions.map((option) => (
|
|
190
196
|
<option key={option} value={String(option)}>
|
|
191
|
-
{option === 'all' ? t('table.all', {}, 'All') : option}
|
|
197
|
+
{option === 'all' ? t('table.all', {}, 'All') : format.number(option)}
|
|
192
198
|
</option>
|
|
193
199
|
))}
|
|
194
200
|
</select>
|
|
@@ -210,6 +216,7 @@ function AppendingPagination({
|
|
|
210
216
|
pagination: Extract<AnyPaginationSchema, { mode: 'load-more' | 'infinite' }>
|
|
211
217
|
}) {
|
|
212
218
|
const t = useT()
|
|
219
|
+
const format = useFormatter()
|
|
213
220
|
const sentinel = useRef<HTMLDivElement>(null)
|
|
214
221
|
const [loading, setLoading] = useState(false)
|
|
215
222
|
const [failed, setFailed] = useState(false)
|
|
@@ -290,8 +297,8 @@ function AppendingPagination({
|
|
|
290
297
|
<span>
|
|
291
298
|
{t(
|
|
292
299
|
'table.showing_range',
|
|
293
|
-
{ first: pagination.from, last: pagination.to },
|
|
294
|
-
|
|
300
|
+
{ first: format.number(pagination.from), last: format.number(pagination.to) },
|
|
301
|
+
'Showing :first–:last',
|
|
295
302
|
)}
|
|
296
303
|
</span>
|
|
297
304
|
<PerPageSelect table={table} pagination={pagination} />
|
|
@@ -202,6 +202,20 @@ export function DataTable({
|
|
|
202
202
|
[table.identifier, table.searchDebounce],
|
|
203
203
|
)
|
|
204
204
|
|
|
205
|
+
// Column searches (`searchable(isIndividual: true)`) apply after the same pause as the table
|
|
206
|
+
// search; Enter applies at once. Callers pass every column's search (the others unchanged).
|
|
207
|
+
const pushColumnSearch = useMemo(
|
|
208
|
+
() =>
|
|
209
|
+
debounce(
|
|
210
|
+
(columnSearch: Record<string, string>) =>
|
|
211
|
+
visitTable(table.identifier, { columnSearch }, ['page']),
|
|
212
|
+
table.searchDebounce,
|
|
213
|
+
),
|
|
214
|
+
[table.identifier, table.searchDebounce],
|
|
215
|
+
)
|
|
216
|
+
const searchColumn = (name: string, value: string) =>
|
|
217
|
+
pushColumnSearch({ ...(table.columnSearches ?? {}), [name]: value })
|
|
218
|
+
|
|
205
219
|
// Polls refresh only this table (a partial reload of its own schema), share one request
|
|
206
220
|
// with other components on the same interval and pause while the tab is hidden.
|
|
207
221
|
const pollComponent = usePollComponent()
|
|
@@ -619,19 +633,18 @@ export function DataTable({
|
|
|
619
633
|
placeholder={t('search', {}, 'Search')}
|
|
620
634
|
aria-label={`${t('search', {}, 'Search')} ${column.label}`}
|
|
621
635
|
className="h-7 text-xs font-normal"
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
columnSearch: {
|
|
628
|
-
...(table.columnSearches ?? {}),
|
|
629
|
-
[column.name]: (event.target as HTMLInputElement).value,
|
|
630
|
-
},
|
|
631
|
-
},
|
|
632
|
-
['page'],
|
|
633
|
-
)
|
|
636
|
+
onChange={(event) => {
|
|
637
|
+
if (!table.searchOnBlur) searchColumn(column.name, event.target.value)
|
|
638
|
+
}}
|
|
639
|
+
onBlur={(event) =>
|
|
640
|
+
table.searchOnBlur && searchColumn(column.name, event.target.value)
|
|
634
641
|
}
|
|
642
|
+
onKeyDown={(event) => {
|
|
643
|
+
if (event.key !== 'Enter') return
|
|
644
|
+
|
|
645
|
+
searchColumn(column.name, (event.target as HTMLInputElement).value)
|
|
646
|
+
pushColumnSearch.flush()
|
|
647
|
+
}}
|
|
635
648
|
/>
|
|
636
649
|
)}
|
|
637
650
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { XIcon } from 'lucide-react'
|
|
2
|
-
import { Dialog as DialogPrimitive } from 'radix-ui'
|
|
2
|
+
import { Dialog as DialogPrimitive, Direction } from 'radix-ui'
|
|
3
3
|
import type { ComponentProps } from 'react'
|
|
4
4
|
import { useT } from '../../context'
|
|
5
5
|
import { Icon } from '../../lib/icons'
|
|
@@ -40,11 +40,15 @@ function DialogContent({
|
|
|
40
40
|
showCloseButton = true,
|
|
41
41
|
...props
|
|
42
42
|
}: ComponentProps<typeof DialogPrimitive.Content> & { showCloseButton?: boolean }) {
|
|
43
|
+
// Portaled outside Alfakit's root: carry the reading direction (starter kit pages and
|
|
44
|
+
// standalone components leave <html dir> to the host).
|
|
45
|
+
const dir = Direction.useDirection()
|
|
43
46
|
const t = useT()
|
|
44
47
|
return (
|
|
45
48
|
<DialogPortal data-slot="dialog-portal">
|
|
46
49
|
<DialogOverlay />
|
|
47
50
|
<DialogPrimitive.Content
|
|
51
|
+
dir={dir}
|
|
48
52
|
data-slot="dialog-content"
|
|
49
53
|
className={cn(
|
|
50
54
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Popover as PopoverPrimitive } from 'radix-ui'
|
|
1
|
+
import { Direction, Popover as PopoverPrimitive } from 'radix-ui'
|
|
2
2
|
import type { ComponentProps } from 'react'
|
|
3
3
|
import { cn } from '../../lib/utils'
|
|
4
4
|
|
|
@@ -16,9 +16,13 @@ function PopoverContent({
|
|
|
16
16
|
sideOffset = 4,
|
|
17
17
|
...props
|
|
18
18
|
}: ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
19
|
+
// Portaled outside Alfakit's root: carry the reading direction (starter kit pages and
|
|
20
|
+
// standalone components leave <html dir> to the host).
|
|
21
|
+
const dir = Direction.useDirection()
|
|
19
22
|
return (
|
|
20
23
|
<PopoverPrimitive.Portal>
|
|
21
24
|
<PopoverPrimitive.Content
|
|
25
|
+
dir={dir}
|
|
22
26
|
data-slot="popover-content"
|
|
23
27
|
align={align}
|
|
24
28
|
sideOffset={sideOffset}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { XIcon } from 'lucide-react'
|
|
2
|
-
import { Dialog as SheetPrimitive } from 'radix-ui'
|
|
2
|
+
import { Direction, Dialog as SheetPrimitive } from 'radix-ui'
|
|
3
3
|
import type { ComponentProps } from 'react'
|
|
4
4
|
import { useT } from '../../context'
|
|
5
5
|
import { Icon } from '../../lib/icons'
|
|
@@ -44,11 +44,15 @@ function SheetContent({
|
|
|
44
44
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
45
45
|
showCloseButton?: boolean
|
|
46
46
|
}) {
|
|
47
|
+
// Portaled outside Alfakit's root: carry the reading direction (starter kit pages and
|
|
48
|
+
// standalone components leave <html dir> to the host).
|
|
49
|
+
const dir = Direction.useDirection()
|
|
47
50
|
const t = useT()
|
|
48
51
|
return (
|
|
49
52
|
<SheetPortal>
|
|
50
53
|
<SheetOverlay />
|
|
51
54
|
<SheetPrimitive.Content
|
|
55
|
+
dir={dir}
|
|
52
56
|
data-slot="sheet-content"
|
|
53
57
|
className={cn(
|
|
54
58
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Tooltip as TooltipPrimitive } from 'radix-ui'
|
|
1
|
+
import { Direction, Tooltip as TooltipPrimitive } from 'radix-ui'
|
|
2
2
|
import type { ComponentProps } from 'react'
|
|
3
3
|
import { cn } from '../../lib/utils'
|
|
4
4
|
|
|
@@ -29,9 +29,13 @@ function TooltipContent({
|
|
|
29
29
|
children,
|
|
30
30
|
...props
|
|
31
31
|
}: ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
32
|
+
// Portaled outside Alfakit's root: carry the reading direction (starter kit pages and
|
|
33
|
+
// standalone components leave <html dir> to the host).
|
|
34
|
+
const dir = Direction.useDirection()
|
|
32
35
|
return (
|
|
33
36
|
<TooltipPrimitive.Portal>
|
|
34
37
|
<TooltipPrimitive.Content
|
|
38
|
+
dir={dir}
|
|
35
39
|
data-slot="tooltip-content"
|
|
36
40
|
sideOffset={sideOffset}
|
|
37
41
|
className={cn(
|
package/src/context.tsx
CHANGED
|
@@ -28,6 +28,11 @@ export interface AlfakitContextValue {
|
|
|
28
28
|
* their own schemas by token.
|
|
29
29
|
*/
|
|
30
30
|
poll?: (target: string, interval: string | number | null | undefined) => () => void
|
|
31
|
+
/**
|
|
32
|
+
* Fetches fresh schemas of components now (`live()` tables and widgets after a record
|
|
33
|
+
* change). Panels leave it out (a partial reload); components outside panels fetch theirs.
|
|
34
|
+
*/
|
|
35
|
+
refresh?: (targets: string[]) => Promise<void>
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
const AlfakitContext = createContext<AlfakitContextValue | null>(null)
|
|
@@ -126,6 +131,11 @@ export function usePollComponent(): NonNullable<AlfakitContextValue['poll']> {
|
|
|
126
131
|
return useContext(AlfakitContext)?.poll ?? pollComponent
|
|
127
132
|
}
|
|
128
133
|
|
|
134
|
+
/** How `live()` components refresh: their own fetch outside panels, else a partial reload. */
|
|
135
|
+
export function useRefreshComponents(): AlfakitContextValue['refresh'] {
|
|
136
|
+
return useContext(AlfakitContext)?.refresh
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
export function useAlfakitProps(): AlfakitPageProps {
|
|
130
140
|
return useOptionalPageProps()
|
|
131
141
|
}
|
package/src/create-alfakit.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
configureCharts,
|
|
8
8
|
configureRealtime,
|
|
9
9
|
debugRecorder,
|
|
10
|
+
documentNonce,
|
|
10
11
|
effectiveErrorNotifications,
|
|
11
12
|
initializeAppearance,
|
|
12
13
|
installErrorNotifications,
|
|
@@ -43,6 +44,7 @@ import { SchemaContractGuard } from './components/panel/schema-contract-guard'
|
|
|
43
44
|
import { Toaster } from './components/ui/sonner'
|
|
44
45
|
import { TooltipProvider } from './components/ui/tooltip'
|
|
45
46
|
import { AlfakitProvider, useAlfakit, type AlfakitContextValue, type AnyComponent } from './context'
|
|
47
|
+
import { panelLayouts } from './layouts/panel-layouts'
|
|
46
48
|
import { discoverComponents } from './lib/discovery'
|
|
47
49
|
import { preloadIcons } from './lib/icons'
|
|
48
50
|
import { AlfakitPage } from './pages/page'
|
|
@@ -72,9 +74,13 @@ export function AlfakitRoot({
|
|
|
72
74
|
translator.setLocale(localization?.tag ?? 'en')
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
// Starter kit mode: the host app owns `<html lang dir>` (its own pages and layout are not
|
|
78
|
+
// mirrored); `AlfakitStarterKitRoot` puts the direction on Alfakit's own root element.
|
|
79
|
+
const ownsDocument = panel?.starterKit !== true
|
|
80
|
+
|
|
75
81
|
useLayoutEffect(() => {
|
|
76
|
-
applyDocumentLocale(localization)
|
|
77
|
-
}, [localization])
|
|
82
|
+
if (ownsDocument) applyDocumentLocale(localization)
|
|
83
|
+
}, [localization, ownsDocument])
|
|
78
84
|
|
|
79
85
|
useLayoutEffect(() => {
|
|
80
86
|
applyPanelTheme(panel)
|
|
@@ -133,6 +139,7 @@ export function bootAlfakit(
|
|
|
133
139
|
// Auto-registered files sit between the built-ins and the explicit `components` / `pages`.
|
|
134
140
|
const discovered = discoverComponents(options.discover)
|
|
135
141
|
const registry = createDefaultRegistry()
|
|
142
|
+
.registerMany(panelLayouts)
|
|
136
143
|
.registerMany(discovered.components)
|
|
137
144
|
.registerMany(options.components ?? {})
|
|
138
145
|
const pages: Record<string, AnyComponent> = {
|
|
@@ -237,6 +244,8 @@ export function bootAlfakit(
|
|
|
237
244
|
return createInertiaApp<AlfakitPageProps>({
|
|
238
245
|
title: (title) => title,
|
|
239
246
|
progress: options.progress === undefined ? { color: 'var(--primary)' } : options.progress,
|
|
247
|
+
// The page's CSP nonce, so the progress bar's injected <style> passes a nonce-based policy.
|
|
248
|
+
nonce: documentNonce(),
|
|
240
249
|
resolve: (name, page) => {
|
|
241
250
|
const component = pages[name]
|
|
242
251
|
|
package/src/index.ts
CHANGED
|
@@ -99,6 +99,8 @@ export { discoverComponents, discoveredComponent, type DiscoveredComponents } fr
|
|
|
99
99
|
export { Icon } from './lib/icons'
|
|
100
100
|
export { cn } from './lib/utils'
|
|
101
101
|
export { AlfakitPage, ContentBlockRenderer, PageContent, PageHeader, PageShell } from './pages/page'
|
|
102
|
+
// `createAlfakit()` registers these; a registry built by hand for `AlfakitPage` needs them too.
|
|
103
|
+
export { panelLayouts } from './layouts/panel-layouts'
|
|
102
104
|
export { createDefaultRegistry } from './registry'
|
|
103
105
|
export * from './standalone'
|
|
104
106
|
export * from './starter-kit'
|