@actuate-media/cms-admin 0.9.0 → 0.11.0
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/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +8 -5
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/layout/primitives.test.d.ts +2 -0
- package/dist/__tests__/layout/primitives.test.d.ts.map +1 -0
- package/dist/__tests__/layout/primitives.test.js +34 -0
- package/dist/__tests__/layout/primitives.test.js.map +1 -0
- package/dist/__tests__/lib/cv.test.d.ts +2 -0
- package/dist/__tests__/lib/cv.test.d.ts.map +1 -0
- package/dist/__tests__/lib/cv.test.js +66 -0
- package/dist/__tests__/lib/cv.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/assets/actuate-logo.d.ts +36 -0
- package/dist/assets/actuate-logo.d.ts.map +1 -0
- package/dist/assets/actuate-logo.js +15 -0
- package/dist/assets/actuate-logo.js.map +1 -0
- package/dist/components/Breadcrumbs.js +2 -2
- package/dist/components/CommandPalette.js +10 -10
- package/dist/components/ContentOverviewChart.js +3 -3
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/FocalPointPicker.js +2 -2
- package/dist/components/FolderTree.js +20 -20
- package/dist/components/LivePreview.js +3 -3
- package/dist/components/LocaleSwitcher.js +1 -1
- package/dist/components/MediaPickerModal.js +4 -4
- package/dist/components/PresenceIndicator.js +1 -1
- package/dist/components/SEOConfigPanel.d.ts +2 -0
- package/dist/components/SEOConfigPanel.d.ts.map +1 -0
- package/dist/components/SEOConfigPanel.js +174 -0
- package/dist/components/SEOConfigPanel.js.map +1 -0
- package/dist/components/SEOPanel.js +9 -9
- package/dist/components/SEOPerformance.js +2 -2
- package/dist/components/SchedulePublishDialog.d.ts +18 -0
- package/dist/components/SchedulePublishDialog.d.ts.map +1 -0
- package/dist/components/SchedulePublishDialog.js +106 -0
- package/dist/components/SchedulePublishDialog.js.map +1 -0
- package/dist/components/SharePreviewLinkDialog.d.ts +17 -0
- package/dist/components/SharePreviewLinkDialog.d.ts.map +1 -0
- package/dist/components/SharePreviewLinkDialog.js +83 -0
- package/dist/components/SharePreviewLinkDialog.js.map +1 -0
- package/dist/components/TipTapEditor.js +5 -5
- package/dist/components/VersionHistory.js +2 -2
- package/dist/components/ui/Badge.d.ts +33 -3
- package/dist/components/ui/Badge.d.ts.map +1 -1
- package/dist/components/ui/Badge.js +42 -8
- package/dist/components/ui/Badge.js.map +1 -1
- package/dist/components/ui/Button.d.ts +19 -8
- package/dist/components/ui/Button.d.ts.map +1 -1
- package/dist/components/ui/Button.js +35 -14
- package/dist/components/ui/Button.js.map +1 -1
- package/dist/components/ui/Card.d.ts +26 -0
- package/dist/components/ui/Card.d.ts.map +1 -0
- package/dist/components/ui/Card.js +45 -0
- package/dist/components/ui/Card.js.map +1 -0
- package/dist/components/ui/DataTable.js +1 -1
- package/dist/components/ui/Input.d.ts +15 -0
- package/dist/components/ui/Input.d.ts.map +1 -0
- package/dist/components/ui/Input.js +23 -0
- package/dist/components/ui/Input.js.map +1 -0
- package/dist/components/ui/SearchInput.js +1 -1
- package/dist/components/ui/Select.d.ts +16 -0
- package/dist/components/ui/Select.d.ts.map +1 -0
- package/dist/components/ui/Select.js +25 -0
- package/dist/components/ui/Select.js.map +1 -0
- package/dist/components/ui/Toast.js +1 -1
- package/dist/components/ui/index.d.ts +10 -4
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +5 -2
- package/dist/components/ui/index.js.map +1 -1
- package/dist/fields/BlockBuilderField.js +3 -3
- package/dist/fields/DateField.js +1 -1
- package/dist/fields/RelationshipField.js +3 -3
- package/dist/fields/TextField.js +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/layout/Header.js +1 -1
- package/dist/layout/Layout.d.ts +14 -0
- package/dist/layout/Layout.d.ts.map +1 -1
- package/dist/layout/Layout.js +17 -11
- package/dist/layout/Layout.js.map +1 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +21 -11
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/layout/primitives/AdminShell.d.ts +43 -0
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -0
- package/dist/layout/primitives/AdminShell.js +51 -0
- package/dist/layout/primitives/AdminShell.js.map +1 -0
- package/dist/layout/primitives/Box.d.ts +19 -0
- package/dist/layout/primitives/Box.d.ts.map +1 -0
- package/dist/layout/primitives/Box.js +12 -0
- package/dist/layout/primitives/Box.js.map +1 -0
- package/dist/layout/primitives/Cluster.d.ts +27 -0
- package/dist/layout/primitives/Cluster.d.ts.map +1 -0
- package/dist/layout/primitives/Cluster.js +37 -0
- package/dist/layout/primitives/Cluster.js.map +1 -0
- package/dist/layout/primitives/Grid.d.ts +45 -0
- package/dist/layout/primitives/Grid.d.ts.map +1 -0
- package/dist/layout/primitives/Grid.js +59 -0
- package/dist/layout/primitives/Grid.js.map +1 -0
- package/dist/layout/primitives/PageContainer.d.ts +36 -0
- package/dist/layout/primitives/PageContainer.d.ts.map +1 -0
- package/dist/layout/primitives/PageContainer.js +41 -0
- package/dist/layout/primitives/PageContainer.js.map +1 -0
- package/dist/layout/primitives/Split.d.ts +34 -0
- package/dist/layout/primitives/Split.d.ts.map +1 -0
- package/dist/layout/primitives/Split.js +27 -0
- package/dist/layout/primitives/Split.js.map +1 -0
- package/dist/layout/primitives/Stack.d.ts +23 -0
- package/dist/layout/primitives/Stack.d.ts.map +1 -0
- package/dist/layout/primitives/Stack.js +34 -0
- package/dist/layout/primitives/Stack.js.map +1 -0
- package/dist/layout/primitives/index.d.ts +30 -0
- package/dist/layout/primitives/index.d.ts.map +1 -0
- package/dist/layout/primitives/index.js +22 -0
- package/dist/layout/primitives/index.js.map +1 -0
- package/dist/layout/primitives/tokens.d.ts +48 -0
- package/dist/layout/primitives/tokens.d.ts.map +1 -0
- package/dist/layout/primitives/tokens.js +54 -0
- package/dist/layout/primitives/tokens.js.map +1 -0
- package/dist/lib/cv.d.ts +53 -0
- package/dist/lib/cv.d.ts.map +1 -0
- package/dist/lib/cv.js +39 -0
- package/dist/lib/cv.js.map +1 -0
- package/dist/views/ApiKeys.d.ts.map +1 -1
- package/dist/views/ApiKeys.js +13 -11
- package/dist/views/ApiKeys.js.map +1 -1
- package/dist/views/CollectionList.js +8 -8
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +333 -78
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/DocumentEdit.d.ts.map +1 -1
- package/dist/views/DocumentEdit.js +17 -5
- package/dist/views/DocumentEdit.js.map +1 -1
- package/dist/views/ForgotPassword.js +2 -2
- package/dist/views/FormEditor.js +5 -5
- package/dist/views/FormSubmissions.js +6 -6
- package/dist/views/Forms.js +2 -2
- package/dist/views/Login.d.ts +16 -1
- package/dist/views/Login.d.ts.map +1 -1
- package/dist/views/Login.js +17 -7
- package/dist/views/Login.js.map +1 -1
- package/dist/views/MediaBrowser.js +16 -16
- package/dist/views/PageEditor.js +2 -2
- package/dist/views/Pages.js +10 -10
- package/dist/views/PostEditor.js +2 -2
- package/dist/views/Posts.js +4 -4
- package/dist/views/Redirects.js +4 -4
- package/dist/views/ResetPassword.js +2 -2
- package/dist/views/SEO.js +6 -6
- package/dist/views/ScriptTagEditor.js +4 -4
- package/dist/views/ScriptTags.js +2 -2
- package/dist/views/Settings.d.ts.map +1 -1
- package/dist/views/Settings.js +9 -8
- package/dist/views/Settings.js.map +1 -1
- package/dist/views/SetupWizard.js +2 -2
- package/dist/views/Users.js +4 -4
- package/dist/views/page-builder/AIBlockAssist.js +1 -1
- package/dist/views/page-builder/AIGenerateDialog.js +10 -10
- package/dist/views/page-builder/BlockEditor.js +10 -10
- package/dist/views/page-builder/BlockPicker.js +4 -4
- package/dist/views/page-builder/BottomBar.js +1 -1
- package/dist/views/page-builder/BuilderToolbar.js +2 -2
- package/dist/views/page-builder/ContextPanel.js +2 -2
- package/dist/views/page-builder/DesignScore.js +9 -9
- package/dist/views/page-builder/NodeSettings.js +8 -8
- package/dist/views/page-builder/PageBuilder.js +3 -3
- package/dist/views/page-builder/PageSettings.js +1 -1
- package/dist/views/page-builder/PageTemplates.js +2 -2
- package/dist/views/page-builder/SEOPanel.js +13 -13
- package/dist/views/page-builder/SavedSections.js +5 -5
- package/dist/views/page-builder/TemplatePicker.js +2 -2
- package/dist/views/page-builder/block-renderers/CTAPreview.js +5 -5
- package/dist/views/page-builder/block-renderers/CardsPreview.js +1 -1
- package/dist/views/page-builder/block-renderers/CodePreview.js +1 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +1 -1
- package/dist/views/page-builder/block-renderers/FormPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +5 -5
- package/dist/views/page-builder/block-renderers/HeroPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/ImagePreview.js +3 -3
- package/dist/views/page-builder/block-renderers/TextPreview.js +3 -3
- package/dist/views/page-builder/block-renderers/VideoPreview.js +4 -4
- package/dist/views/page-builder/canvas/BlockRenderer.js +1 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.js +3 -3
- package/dist/views/page-builder/canvas/ColumnRenderer.js +2 -2
- package/dist/views/page-builder/canvas/ContainerRenderer.js +2 -2
- package/dist/views/page-builder/canvas/RowRenderer.js +2 -2
- package/dist/views/page-builder/canvas/SectionRenderer.js +2 -2
- package/package.json +6 -2
- package/src/AdminRoot.tsx +21 -11
- package/src/__tests__/layout/primitives.test.ts +37 -0
- package/src/__tests__/lib/cv.test.ts +74 -0
- package/src/assets/actuate-logo.tsx +72 -0
- package/src/components/Breadcrumbs.tsx +6 -6
- package/src/components/CommandPalette.tsx +34 -34
- package/src/components/ContentOverviewChart.tsx +3 -3
- package/src/components/ErrorBoundary.tsx +3 -3
- package/src/components/FocalPointPicker.tsx +4 -4
- package/src/components/FolderTree.tsx +38 -38
- package/src/components/LivePreview.tsx +16 -16
- package/src/components/LocaleSwitcher.tsx +7 -7
- package/src/components/MediaPickerModal.tsx +21 -21
- package/src/components/PresenceIndicator.tsx +2 -2
- package/src/components/SEOConfigPanel.tsx +582 -0
- package/src/components/SEOPanel.tsx +46 -46
- package/src/components/SEOPerformance.tsx +21 -21
- package/src/components/SchedulePublishDialog.tsx +241 -0
- package/src/components/SharePreviewLinkDialog.tsx +227 -0
- package/src/components/TipTapEditor.tsx +33 -33
- package/src/components/VersionHistory.tsx +16 -16
- package/src/components/ui/Badge.tsx +66 -14
- package/src/components/ui/Button.tsx +70 -33
- package/src/components/ui/Card.tsx +101 -0
- package/src/components/ui/DataTable.tsx +1 -1
- package/src/components/ui/Input.tsx +35 -0
- package/src/components/ui/SearchInput.tsx +4 -4
- package/src/components/ui/Select.tsx +56 -0
- package/src/components/ui/Toast.tsx +1 -1
- package/src/components/ui/index.ts +18 -4
- package/src/fields/BlockBuilderField.tsx +3 -3
- package/src/fields/DateField.tsx +1 -1
- package/src/fields/RelationshipField.tsx +10 -10
- package/src/fields/TextField.tsx +1 -1
- package/src/index.ts +32 -0
- package/src/layout/Header.tsx +28 -28
- package/src/layout/Layout.tsx +39 -46
- package/src/layout/Sidebar.tsx +37 -64
- package/src/layout/primitives/AdminShell.tsx +118 -0
- package/src/layout/primitives/Box.tsx +30 -0
- package/src/layout/primitives/Cluster.tsx +74 -0
- package/src/layout/primitives/Grid.tsx +120 -0
- package/src/layout/primitives/PageContainer.tsx +96 -0
- package/src/layout/primitives/Split.tsx +73 -0
- package/src/layout/primitives/Stack.tsx +67 -0
- package/src/layout/primitives/index.ts +36 -0
- package/src/layout/primitives/tokens.ts +76 -0
- package/src/lib/cv.ts +96 -0
- package/src/styles/build-input.css +1 -1
- package/src/views/ApiKeys.tsx +57 -57
- package/src/views/CollectionList.tsx +30 -30
- package/src/views/Dashboard.tsx +737 -186
- package/src/views/DocumentEdit.tsx +90 -10
- package/src/views/ForgotPassword.tsx +18 -18
- package/src/views/FormEditor.tsx +75 -75
- package/src/views/FormSubmissions.tsx +76 -76
- package/src/views/Forms.tsx +27 -27
- package/src/views/Login.tsx +65 -25
- package/src/views/MediaBrowser.tsx +127 -127
- package/src/views/PageEditor.tsx +25 -25
- package/src/views/Pages.tsx +59 -59
- package/src/views/PostEditor.tsx +37 -37
- package/src/views/Posts.tsx +48 -48
- package/src/views/Redirects.tsx +21 -21
- package/src/views/ResetPassword.tsx +28 -28
- package/src/views/SEO.tsx +144 -144
- package/src/views/ScriptTagEditor.tsx +24 -24
- package/src/views/ScriptTags.tsx +10 -10
- package/src/views/Settings.tsx +88 -80
- package/src/views/SetupWizard.tsx +28 -28
- package/src/views/Users.tsx +20 -20
- package/src/views/page-builder/AIBlockAssist.tsx +1 -1
- package/src/views/page-builder/AIGenerateDialog.tsx +63 -63
- package/src/views/page-builder/BlockEditor.tsx +26 -26
- package/src/views/page-builder/BlockPicker.tsx +22 -22
- package/src/views/page-builder/BottomBar.tsx +8 -8
- package/src/views/page-builder/BuilderToolbar.tsx +17 -17
- package/src/views/page-builder/ContextPanel.tsx +3 -3
- package/src/views/page-builder/DesignScore.tsx +21 -21
- package/src/views/page-builder/NodeSettings.tsx +27 -27
- package/src/views/page-builder/PageBuilder.tsx +11 -11
- package/src/views/page-builder/PageSettings.tsx +4 -4
- package/src/views/page-builder/PageTemplates.tsx +18 -18
- package/src/views/page-builder/SEOPanel.tsx +53 -53
- package/src/views/page-builder/SavedSections.tsx +37 -37
- package/src/views/page-builder/TemplatePicker.tsx +17 -17
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +13 -13
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +5 -5
- package/src/views/page-builder/block-renderers/CodePreview.tsx +6 -6
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +13 -13
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +3 -3
- package/src/views/page-builder/block-renderers/FormPreview.tsx +20 -20
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +8 -8
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +16 -16
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +4 -4
- package/src/views/page-builder/block-renderers/TextPreview.tsx +14 -14
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +12 -12
- package/src/views/page-builder/canvas/BlockRenderer.tsx +4 -4
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +6 -6
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +3 -3
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +2 -2
- package/src/views/page-builder/canvas/RowRenderer.tsx +2 -2
- package/src/views/page-builder/canvas/SectionRenderer.tsx +2 -2
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, ElementType } from 'react'
|
|
2
|
+
import type { SpaceToken } from './tokens.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Responsive column count. Pass a number for static grids, or a map to
|
|
6
|
+
* change column count at each breakpoint.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* columns={3} // 3 cols at every breakpoint
|
|
10
|
+
* columns={{ base: 1, md: 2, lg: 4 }} // responsive
|
|
11
|
+
*/
|
|
12
|
+
export type GridResponsive =
|
|
13
|
+
| number
|
|
14
|
+
| { base?: number; sm?: number; md?: number; lg?: number; xl?: number; '2xl'?: number }
|
|
15
|
+
|
|
16
|
+
export interface GridProps extends HTMLAttributes<HTMLElement> {
|
|
17
|
+
columns: GridResponsive
|
|
18
|
+
/** Gap between cells. */
|
|
19
|
+
space?: SpaceToken
|
|
20
|
+
/** Row gap (defaults to `space`). */
|
|
21
|
+
rowSpace?: SpaceToken
|
|
22
|
+
/** Render as a different element. Defaults to `div`. */
|
|
23
|
+
as?: ElementType
|
|
24
|
+
/**
|
|
25
|
+
* Use auto-fit instead of explicit column counts. When set, `minItemWidth`
|
|
26
|
+
* controls the minimum tile width before the grid reflows. Use this for
|
|
27
|
+
* card grids that should adapt to container width without specific
|
|
28
|
+
* breakpoints.
|
|
29
|
+
*/
|
|
30
|
+
autoFit?: boolean
|
|
31
|
+
/** Min tile width when `autoFit` is true. Defaults to `16rem`. */
|
|
32
|
+
minItemWidth?: string
|
|
33
|
+
children?: ReactNode
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const COL_CLASS: Record<number, string> = {
|
|
37
|
+
1: 'grid-cols-1',
|
|
38
|
+
2: 'grid-cols-2',
|
|
39
|
+
3: 'grid-cols-3',
|
|
40
|
+
4: 'grid-cols-4',
|
|
41
|
+
5: 'grid-cols-5',
|
|
42
|
+
6: 'grid-cols-6',
|
|
43
|
+
7: 'grid-cols-7',
|
|
44
|
+
8: 'grid-cols-8',
|
|
45
|
+
9: 'grid-cols-9',
|
|
46
|
+
10: 'grid-cols-10',
|
|
47
|
+
11: 'grid-cols-11',
|
|
48
|
+
12: 'grid-cols-12',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const BP_PREFIX = ['', 'sm:', 'md:', 'lg:', 'xl:', '2xl:'] as const
|
|
52
|
+
type BpKey = 'base' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
53
|
+
|
|
54
|
+
function columnsToClasses(columns: GridResponsive): string {
|
|
55
|
+
if (typeof columns === 'number') {
|
|
56
|
+
return COL_CLASS[columns] ?? 'grid-cols-1'
|
|
57
|
+
}
|
|
58
|
+
const order: BpKey[] = ['base', 'sm', 'md', 'lg', 'xl', '2xl']
|
|
59
|
+
return order
|
|
60
|
+
.map((bp, i) => {
|
|
61
|
+
const n = columns[bp]
|
|
62
|
+
if (!n) return ''
|
|
63
|
+
const base = COL_CLASS[n] ?? 'grid-cols-1'
|
|
64
|
+
return BP_PREFIX[i] + base
|
|
65
|
+
})
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.join(' ')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Responsive CSS grid primitive. Pass either an explicit `columns` count
|
|
72
|
+
* (per-breakpoint or static) or set `autoFit` for a `repeat(auto-fit, …)`
|
|
73
|
+
* tile grid. Both modes share the same `space` / `rowSpace` knobs so
|
|
74
|
+
* usage is consistent across views.
|
|
75
|
+
*/
|
|
76
|
+
export function Grid({
|
|
77
|
+
columns,
|
|
78
|
+
space = '4',
|
|
79
|
+
rowSpace,
|
|
80
|
+
as: Component = 'div',
|
|
81
|
+
autoFit,
|
|
82
|
+
minItemWidth = '16rem',
|
|
83
|
+
className = '',
|
|
84
|
+
style,
|
|
85
|
+
children,
|
|
86
|
+
...rest
|
|
87
|
+
}: GridProps) {
|
|
88
|
+
if (autoFit) {
|
|
89
|
+
return (
|
|
90
|
+
<Component
|
|
91
|
+
className={['grid', `gap-${space}`, rowSpace ? `gap-y-${rowSpace}` : '', className]
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.join(' ')}
|
|
94
|
+
style={{
|
|
95
|
+
gridTemplateColumns: `repeat(auto-fit, minmax(${minItemWidth}, 1fr))`,
|
|
96
|
+
...style,
|
|
97
|
+
}}
|
|
98
|
+
{...rest}
|
|
99
|
+
>
|
|
100
|
+
{children}
|
|
101
|
+
</Component>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const classes = [
|
|
106
|
+
'grid',
|
|
107
|
+
columnsToClasses(columns),
|
|
108
|
+
`gap-${space}`,
|
|
109
|
+
rowSpace ? `gap-y-${rowSpace}` : '',
|
|
110
|
+
className,
|
|
111
|
+
]
|
|
112
|
+
.filter(Boolean)
|
|
113
|
+
.join(' ')
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<Component className={classes} style={style} {...rest}>
|
|
117
|
+
{children}
|
|
118
|
+
</Component>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import { Stack } from './Stack.js'
|
|
3
|
+
|
|
4
|
+
export interface PageContainerProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
|
|
5
|
+
/** Page title rendered as an h1 inside the page header. */
|
|
6
|
+
title?: ReactNode
|
|
7
|
+
/** Optional sub-headline shown beneath the title. */
|
|
8
|
+
description?: ReactNode
|
|
9
|
+
/** Right-aligned action area (buttons, filters). Rendered in the header. */
|
|
10
|
+
actions?: ReactNode
|
|
11
|
+
/**
|
|
12
|
+
* Maximum width of the content. Defaults to `7xl` (Tailwind's max-w-7xl,
|
|
13
|
+
* ~1280px). Pass `'full'` for edge-to-edge content.
|
|
14
|
+
*/
|
|
15
|
+
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl' | 'full'
|
|
16
|
+
/** Horizontal padding token. Defaults to `6` (24px). */
|
|
17
|
+
paddingX?: '4' | '6' | '8'
|
|
18
|
+
/** Vertical padding token. Defaults to `6`. */
|
|
19
|
+
paddingY?: '4' | '6' | '8' | '10'
|
|
20
|
+
/** Vertical gap between the header and the body. Defaults to `6`. */
|
|
21
|
+
gap?: '4' | '6' | '8'
|
|
22
|
+
children?: ReactNode
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MAX_WIDTH_CLASS: Record<NonNullable<PageContainerProps['maxWidth']>, string> = {
|
|
26
|
+
sm: 'max-w-sm',
|
|
27
|
+
md: 'max-w-md',
|
|
28
|
+
lg: 'max-w-lg',
|
|
29
|
+
xl: 'max-w-xl',
|
|
30
|
+
'2xl': 'max-w-2xl',
|
|
31
|
+
'3xl': 'max-w-3xl',
|
|
32
|
+
'4xl': 'max-w-4xl',
|
|
33
|
+
'5xl': 'max-w-5xl',
|
|
34
|
+
'6xl': 'max-w-6xl',
|
|
35
|
+
'7xl': 'max-w-7xl',
|
|
36
|
+
full: 'max-w-none',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The single canonical wrapper for every admin page. PageContainer owns
|
|
41
|
+
* three responsibilities so individual views can stop redoing them:
|
|
42
|
+
*
|
|
43
|
+
* 1. Consistent max-width + horizontal padding (so dashboards, lists, and
|
|
44
|
+
* forms all line up visually).
|
|
45
|
+
* 2. A standard header slot with title + description + actions.
|
|
46
|
+
* 3. A standard body slot rendered as a `<Stack>` so children stack with
|
|
47
|
+
* consistent vertical rhythm.
|
|
48
|
+
*
|
|
49
|
+
* Use PageContainer at the top of every screen. Compose with `<Stack>` /
|
|
50
|
+
* `<Grid>` / `<Cluster>` inside. Never roll a per-screen wrapper.
|
|
51
|
+
*/
|
|
52
|
+
export function PageContainer({
|
|
53
|
+
title,
|
|
54
|
+
description,
|
|
55
|
+
actions,
|
|
56
|
+
maxWidth = '7xl',
|
|
57
|
+
paddingX = '6',
|
|
58
|
+
paddingY = '6',
|
|
59
|
+
gap = '6',
|
|
60
|
+
className = '',
|
|
61
|
+
children,
|
|
62
|
+
...rest
|
|
63
|
+
}: PageContainerProps) {
|
|
64
|
+
const outerClasses = [
|
|
65
|
+
'mx-auto w-full',
|
|
66
|
+
MAX_WIDTH_CLASS[maxWidth],
|
|
67
|
+
`px-${paddingX}`,
|
|
68
|
+
`py-${paddingY}`,
|
|
69
|
+
className,
|
|
70
|
+
]
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.join(' ')
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<section className={outerClasses} {...rest}>
|
|
76
|
+
<Stack space={gap}>
|
|
77
|
+
{(title || description || actions) && (
|
|
78
|
+
<header className="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
|
79
|
+
{(title || description) && (
|
|
80
|
+
<div className="min-w-0">
|
|
81
|
+
{title && (
|
|
82
|
+
<h1 className="text-foreground truncate text-2xl font-semibold tracking-tight">
|
|
83
|
+
{title}
|
|
84
|
+
</h1>
|
|
85
|
+
)}
|
|
86
|
+
{description && <p className="text-muted-foreground mt-1 text-sm">{description}</p>}
|
|
87
|
+
</div>
|
|
88
|
+
)}
|
|
89
|
+
{actions && <div className="flex shrink-0 items-center gap-2">{actions}</div>}
|
|
90
|
+
</header>
|
|
91
|
+
)}
|
|
92
|
+
{children}
|
|
93
|
+
</Stack>
|
|
94
|
+
</section>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, ElementType } from 'react'
|
|
2
|
+
import type { SpaceToken } from './tokens.js'
|
|
3
|
+
|
|
4
|
+
export interface SplitProps extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/**
|
|
6
|
+
* Fraction of the container the primary pane should take, expressed as
|
|
7
|
+
* a CSS fraction string. Defaults to `'2fr 1fr'` (primary 2x sidebar).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* <Split fraction="3fr 1fr"> // 75% / 25%
|
|
11
|
+
* <Split fraction="minmax(0, 1fr) 320px"> // fluid + fixed sidebar
|
|
12
|
+
*/
|
|
13
|
+
fraction?: string
|
|
14
|
+
/** Gap between the two panes. */
|
|
15
|
+
space?: SpaceToken
|
|
16
|
+
/**
|
|
17
|
+
* Stack vertically at this breakpoint and below. Pass `false` to never
|
|
18
|
+
* stack. Defaults to `md` (stacks on screens narrower than 768px).
|
|
19
|
+
*/
|
|
20
|
+
stackBelow?: 'sm' | 'md' | 'lg' | 'xl' | false
|
|
21
|
+
/** Reverse the order of children (sidebar first instead of second). */
|
|
22
|
+
reverse?: boolean
|
|
23
|
+
/** Render as a different element. */
|
|
24
|
+
as?: ElementType
|
|
25
|
+
children: [ReactNode, ReactNode]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const STACK_BELOW_CLASS = {
|
|
29
|
+
sm: 'sm:grid',
|
|
30
|
+
md: 'md:grid',
|
|
31
|
+
lg: 'lg:grid',
|
|
32
|
+
xl: 'xl:grid',
|
|
33
|
+
} as const
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Two-pane split layout (primary + sidebar/aside) that collapses to a
|
|
37
|
+
* stacked layout below a configurable breakpoint. Use this for the
|
|
38
|
+
* document editor (`<Split fraction="minmax(0, 1fr) 320px">`), settings
|
|
39
|
+
* pages with a navigation rail, etc. — anywhere two panes need to live
|
|
40
|
+
* side-by-side at desktop widths and stack on mobile.
|
|
41
|
+
*/
|
|
42
|
+
export function Split({
|
|
43
|
+
fraction = '2fr 1fr',
|
|
44
|
+
space = '6',
|
|
45
|
+
stackBelow = 'md',
|
|
46
|
+
reverse,
|
|
47
|
+
as: Component = 'div',
|
|
48
|
+
className = '',
|
|
49
|
+
style,
|
|
50
|
+
children,
|
|
51
|
+
...rest
|
|
52
|
+
}: SplitProps) {
|
|
53
|
+
const stackClass =
|
|
54
|
+
stackBelow === false ? 'grid' : `flex flex-col ${STACK_BELOW_CLASS[stackBelow]}`
|
|
55
|
+
const classes = [stackClass, `gap-${space}`, reverse ? 'flex-col-reverse' : '', className]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(' ')
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Component
|
|
61
|
+
className={classes}
|
|
62
|
+
style={{
|
|
63
|
+
...(stackBelow === false
|
|
64
|
+
? { gridTemplateColumns: fraction }
|
|
65
|
+
: { gridTemplateColumns: fraction }),
|
|
66
|
+
...style,
|
|
67
|
+
}}
|
|
68
|
+
{...rest}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</Component>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, ElementType } from 'react'
|
|
2
|
+
import type { SpaceToken } from './tokens.js'
|
|
3
|
+
|
|
4
|
+
export type StackSpace = SpaceToken
|
|
5
|
+
|
|
6
|
+
export interface StackProps extends HTMLAttributes<HTMLElement> {
|
|
7
|
+
/** Vertical gap between children. Maps to Tailwind `gap-{space}`. */
|
|
8
|
+
space?: StackSpace
|
|
9
|
+
/** Render as a different element (e.g. `<section>`, `<article>`). Defaults to `div`. */
|
|
10
|
+
as?: ElementType
|
|
11
|
+
/** Center children horizontally (sets `items-center`). */
|
|
12
|
+
align?: 'start' | 'center' | 'end' | 'stretch'
|
|
13
|
+
/** Distribute vertical space (sets `justify-{value}`). */
|
|
14
|
+
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
|
|
15
|
+
/** Stretch the stack to fill its parent's height. */
|
|
16
|
+
fill?: boolean
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ALIGN_CLASS: Record<NonNullable<StackProps['align']>, string> = {
|
|
21
|
+
start: 'items-start',
|
|
22
|
+
center: 'items-center',
|
|
23
|
+
end: 'items-end',
|
|
24
|
+
stretch: 'items-stretch',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const JUSTIFY_CLASS: Record<NonNullable<StackProps['justify']>, string> = {
|
|
28
|
+
start: 'justify-start',
|
|
29
|
+
center: 'justify-center',
|
|
30
|
+
end: 'justify-end',
|
|
31
|
+
between: 'justify-between',
|
|
32
|
+
around: 'justify-around',
|
|
33
|
+
evenly: 'justify-evenly',
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Vertical stack with a consistent gap between children. This is the
|
|
38
|
+
* single sanctioned way to space block-level elements in the admin —
|
|
39
|
+
* never reach for `space-y-*` directly.
|
|
40
|
+
*/
|
|
41
|
+
export function Stack({
|
|
42
|
+
space = '4',
|
|
43
|
+
as: Component = 'div',
|
|
44
|
+
align,
|
|
45
|
+
justify,
|
|
46
|
+
fill,
|
|
47
|
+
className = '',
|
|
48
|
+
children,
|
|
49
|
+
...rest
|
|
50
|
+
}: StackProps) {
|
|
51
|
+
const classes = [
|
|
52
|
+
'flex flex-col',
|
|
53
|
+
`gap-${space}`,
|
|
54
|
+
align ? ALIGN_CLASS[align] : '',
|
|
55
|
+
justify ? JUSTIFY_CLASS[justify] : '',
|
|
56
|
+
fill ? 'h-full' : '',
|
|
57
|
+
className,
|
|
58
|
+
]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join(' ')
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Component className={classes} {...rest}>
|
|
64
|
+
{children}
|
|
65
|
+
</Component>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout primitives for the Actuate admin.
|
|
3
|
+
*
|
|
4
|
+
* These exist so views never hand-roll their own flex/grid scaffolding.
|
|
5
|
+
* Every admin view should compose these primitives instead of writing raw
|
|
6
|
+
* Tailwind classes for spacing/alignment/structure. That contract is what
|
|
7
|
+
* keeps layout bugs (sidebar overlap, breakpoint mismatches, missing
|
|
8
|
+
* `min-w-0`) from recurring in new views.
|
|
9
|
+
*
|
|
10
|
+
* Naming follows the "Every Layout" canon
|
|
11
|
+
* (https://every-layout.dev/) plus our own `AdminShell` and
|
|
12
|
+
* `PageContainer` for the admin-specific outer chrome.
|
|
13
|
+
*/
|
|
14
|
+
export { AdminShell } from './AdminShell.js'
|
|
15
|
+
export type { AdminShellProps } from './AdminShell.js'
|
|
16
|
+
|
|
17
|
+
export { PageContainer } from './PageContainer.js'
|
|
18
|
+
export type { PageContainerProps } from './PageContainer.js'
|
|
19
|
+
|
|
20
|
+
export { Stack } from './Stack.js'
|
|
21
|
+
export type { StackProps, StackSpace } from './Stack.js'
|
|
22
|
+
|
|
23
|
+
export { Cluster } from './Cluster.js'
|
|
24
|
+
export type { ClusterProps, ClusterAlign, ClusterJustify } from './Cluster.js'
|
|
25
|
+
|
|
26
|
+
export { Grid } from './Grid.js'
|
|
27
|
+
export type { GridProps, GridResponsive } from './Grid.js'
|
|
28
|
+
|
|
29
|
+
export { Split } from './Split.js'
|
|
30
|
+
export type { SplitProps } from './Split.js'
|
|
31
|
+
|
|
32
|
+
export { Box } from './Box.js'
|
|
33
|
+
export type { BoxProps } from './Box.js'
|
|
34
|
+
|
|
35
|
+
export { tokens } from './tokens.js'
|
|
36
|
+
export type { SpaceToken, RadiusToken } from './tokens.js'
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design tokens — single source of truth for the admin design system.
|
|
3
|
+
*
|
|
4
|
+
* Tokens are surfaced both as TypeScript constants (for inline-style escape
|
|
5
|
+
* hatches) and via CSS custom properties in `styles/theme.css`. Always
|
|
6
|
+
* prefer the CSS variables in production code; the TS constants are for
|
|
7
|
+
* computed inline styles only (e.g. dynamic widths, computed grid sizes).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type SpaceToken =
|
|
11
|
+
| '0'
|
|
12
|
+
| 'px'
|
|
13
|
+
| '0.5'
|
|
14
|
+
| '1'
|
|
15
|
+
| '1.5'
|
|
16
|
+
| '2'
|
|
17
|
+
| '2.5'
|
|
18
|
+
| '3'
|
|
19
|
+
| '4'
|
|
20
|
+
| '5'
|
|
21
|
+
| '6'
|
|
22
|
+
| '8'
|
|
23
|
+
| '10'
|
|
24
|
+
| '12'
|
|
25
|
+
| '16'
|
|
26
|
+
|
|
27
|
+
export type RadiusToken = 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
28
|
+
|
|
29
|
+
/** Numeric pixel values for inline style escape hatches. */
|
|
30
|
+
export const tokens = {
|
|
31
|
+
// Spacing scale matches Tailwind's default (--spacing = 0.25rem = 4px).
|
|
32
|
+
space: {
|
|
33
|
+
'0': 0,
|
|
34
|
+
px: 1,
|
|
35
|
+
'0.5': 2,
|
|
36
|
+
'1': 4,
|
|
37
|
+
'1.5': 6,
|
|
38
|
+
'2': 8,
|
|
39
|
+
'2.5': 10,
|
|
40
|
+
'3': 12,
|
|
41
|
+
'4': 16,
|
|
42
|
+
'5': 20,
|
|
43
|
+
'6': 24,
|
|
44
|
+
'8': 32,
|
|
45
|
+
'10': 40,
|
|
46
|
+
'12': 48,
|
|
47
|
+
'16': 64,
|
|
48
|
+
} as const satisfies Record<SpaceToken, number>,
|
|
49
|
+
|
|
50
|
+
radius: {
|
|
51
|
+
none: 0,
|
|
52
|
+
sm: 2,
|
|
53
|
+
md: 6,
|
|
54
|
+
lg: 8,
|
|
55
|
+
xl: 12,
|
|
56
|
+
full: 9999,
|
|
57
|
+
} as const satisfies Record<RadiusToken, number>,
|
|
58
|
+
|
|
59
|
+
// Breakpoints mirror Tailwind defaults so primitives can compose with
|
|
60
|
+
// utility classes without surprises. Use `matchMedia(`(min-width: …`)` to
|
|
61
|
+
// sync JS-driven layout decisions with these values.
|
|
62
|
+
breakpoint: {
|
|
63
|
+
sm: 640,
|
|
64
|
+
md: 768,
|
|
65
|
+
lg: 1024,
|
|
66
|
+
xl: 1280,
|
|
67
|
+
'2xl': 1536,
|
|
68
|
+
} as const,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Map a space token to the corresponding Tailwind `space-y-*` / `gap-*` value. */
|
|
72
|
+
export function spaceToTw(t: SpaceToken): string {
|
|
73
|
+
// Tailwind's spacing utility already accepts the same scale, so we map
|
|
74
|
+
// 1:1. Centralizing here means callers don't repeat the string literal.
|
|
75
|
+
return t
|
|
76
|
+
}
|
package/src/lib/cv.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import clsx from 'clsx'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Tiny in-house implementation of the `class-variance-authority` pattern
|
|
5
|
+
* (a.k.a. `cva` / `tailwind-variants`). We roll our own to avoid a runtime
|
|
6
|
+
* dependency while keeping the API surface compatible with the shadcn /
|
|
7
|
+
* cva ecosystem — anyone familiar with cva can read these calls.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const button = cv('inline-flex items-center', {
|
|
11
|
+
* variants: {
|
|
12
|
+
* intent: {
|
|
13
|
+
* primary: 'bg-primary text-white',
|
|
14
|
+
* secondary: 'bg-secondary text-foreground',
|
|
15
|
+
* },
|
|
16
|
+
* size: {
|
|
17
|
+
* sm: 'px-2 py-1 text-xs',
|
|
18
|
+
* md: 'px-4 py-2 text-sm',
|
|
19
|
+
* },
|
|
20
|
+
* },
|
|
21
|
+
* defaultVariants: { intent: 'primary', size: 'md' },
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* button({ intent: 'secondary' }) // -> "inline-flex … bg-secondary …"
|
|
25
|
+
* button({ intent: 'secondary', class: 'shadow' }) // append extras
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type ClassValue = string | undefined | false | null
|
|
29
|
+
|
|
30
|
+
export type VariantsConfig = Record<string, Record<string, ClassValue>>
|
|
31
|
+
|
|
32
|
+
type VariantSelection<V extends VariantsConfig> = {
|
|
33
|
+
[K in keyof V]?: keyof V[K]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CompoundVariant<V extends VariantsConfig> {
|
|
37
|
+
// Subset of variants that must match for the compound classes to apply.
|
|
38
|
+
// Use string values from each variant key (or arrays for "match any").
|
|
39
|
+
[key: string]: keyof V[keyof V] | Array<keyof V[keyof V]> | ClassValue
|
|
40
|
+
class?: ClassValue
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CvConfig<V extends VariantsConfig> {
|
|
44
|
+
variants: V
|
|
45
|
+
defaultVariants?: VariantSelection<V>
|
|
46
|
+
compoundVariants?: Array<CompoundVariant<V>>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type VariantProps<F extends (...args: any) => any> = Omit<
|
|
50
|
+
NonNullable<Parameters<F>[0]>,
|
|
51
|
+
'class' | 'className'
|
|
52
|
+
>
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Build a variant-aware class generator.
|
|
56
|
+
*
|
|
57
|
+
* The returned function accepts the variant selection plus an optional
|
|
58
|
+
* `class` / `className` escape hatch and returns the combined Tailwind
|
|
59
|
+
* class string. Compound variants run after the base + per-variant
|
|
60
|
+
* classes, so they take precedence when the Tailwind ordering matters.
|
|
61
|
+
*/
|
|
62
|
+
export function cv<V extends VariantsConfig>(base: ClassValue, config: CvConfig<V>) {
|
|
63
|
+
return function variantClass(
|
|
64
|
+
props?: VariantSelection<V> & { class?: ClassValue; className?: ClassValue },
|
|
65
|
+
): string {
|
|
66
|
+
const selected = { ...config.defaultVariants, ...props } as Record<string, unknown>
|
|
67
|
+
|
|
68
|
+
const variantClasses: ClassValue[] = []
|
|
69
|
+
for (const [key, valuesMap] of Object.entries(config.variants)) {
|
|
70
|
+
const value = selected[key]
|
|
71
|
+
if (value === undefined || value === null) continue
|
|
72
|
+
const cls = valuesMap[value as string]
|
|
73
|
+
if (cls) variantClasses.push(cls)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const compoundClasses: ClassValue[] = []
|
|
77
|
+
if (config.compoundVariants) {
|
|
78
|
+
for (const compound of config.compoundVariants) {
|
|
79
|
+
const { class: cClass, className: _cn, ...match } = compound as any
|
|
80
|
+
const matches = Object.entries(match).every(([k, v]) => {
|
|
81
|
+
const current = selected[k]
|
|
82
|
+
if (Array.isArray(v)) return (v as unknown[]).includes(current)
|
|
83
|
+
return current === v
|
|
84
|
+
})
|
|
85
|
+
if (matches && cClass) compoundClasses.push(cClass as ClassValue)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return clsx(
|
|
90
|
+
base,
|
|
91
|
+
...variantClasses,
|
|
92
|
+
...compoundClasses,
|
|
93
|
+
(props?.class ?? props?.className) as ClassValue,
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
}
|