@santi020k/lumen 3.0.1 → 4.0.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/README.md +207 -3
- package/dist/cli.js +49 -5
- package/dist/consumer-upgrade-audit.d.ts +10 -0
- package/dist/consumer-upgrade-audit.d.ts.map +1 -0
- package/dist/consumer-upgrade-audit.js +52 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +16 -0
- package/dist/integration-diagnostics.d.ts.map +1 -1
- package/dist/integration-diagnostics.js +199 -3
- package/dist/registry-component-name.d.ts +2 -0
- package/dist/registry-component-name.d.ts.map +1 -0
- package/dist/registry-component-name.js +53 -0
- package/dist/registry-data.d.ts +339 -18
- package/dist/registry-data.d.ts.map +1 -1
- package/dist/registry-data.js +789 -3
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +11 -9
- package/dist/source-script-ranges.d.ts +3 -0
- package/dist/source-script-ranges.d.ts.map +1 -0
- package/dist/source-script-ranges.js +41 -0
- package/dist/v2-migration.d.ts +38 -0
- package/dist/v2-migration.d.ts.map +1 -1
- package/dist/v2-migration.js +26 -14
- package/dist/v4-migration.d.ts +26 -0
- package/dist/v4-migration.d.ts.map +1 -0
- package/dist/v4-migration.js +272 -0
- package/dist/version-migration-dependencies.d.ts +8 -0
- package/dist/version-migration-dependencies.d.ts.map +1 -0
- package/dist/version-migration-dependencies.js +10 -0
- package/dist/version-migration-source.d.ts +4 -0
- package/dist/version-migration-source.d.ts.map +1 -0
- package/dist/version-migration-source.js +281 -0
- package/dist/version-migration.d.ts +34 -0
- package/dist/version-migration.d.ts.map +1 -0
- package/dist/version-migration.js +158 -0
- package/native-components.json +563 -16
- package/package.json +26 -11
- package/release-manifest.json +22 -17
- package/styles/critical.css +634 -61
- package/styles/motion.css +61 -0
- package/styles/world-map.css +30 -0
- package/styles.css +1736 -455
- package/templates/astro/advanced-fields/src/lumen/advanced-fields.astro +35 -0
- package/templates/astro/ai-docs/llms.txt +3 -0
- package/templates/astro/analytics-dashboard/src/lumen/analytics-dashboard.astro +110 -0
- package/templates/astro/auth-onboarding/src/lumen/auth-onboarding.astro +107 -0
- package/templates/astro/command-center/src/lumen/command-center.astro +34 -0
- package/templates/astro/commerce-dashboard/src/lumen/commerce-dashboard.astro +108 -0
- package/templates/astro/content-flow-actions/src/lumen/content-flow-actions.astro +10 -0
- package/templates/astro/content-flow-header/src/lumen/content-flow-header.astro +14 -0
- package/templates/astro/content-flow-list/src/lumen/content-flow-list.astro +13 -0
- package/templates/astro/content-flow-settings/src/lumen/content-flow-settings.astro +17 -0
- package/templates/astro/data-collections/src/lumen/data-collections.astro +52 -0
- package/templates/astro/feature-preview/src/lumen/feature-preview.astro +32 -0
- package/templates/astro/guided-onboarding/src/lumen/guided-onboarding.astro +35 -0
- package/templates/astro/interactive-pricing/src/lumen/interactive-pricing.astro +32 -0
- package/templates/astro/media-workspace/src/lumen/adjustment-controls.astro +89 -0
- package/templates/astro/media-workspace/src/lumen/media-processing.astro +47 -0
- package/templates/astro/media-workspace/src/lumen/media-workspace.astro +77 -0
- package/templates/astro/page-header/src/lumen/page-header.astro +41 -0
- package/templates/astro/project-workspace/src/lumen/project-workspace.astro +103 -0
- package/templates/astro/rich-text-editor/src/lumen/rich-text-editor.astro +21 -0
- package/templates/astro/saas-admin/src/lumen/saas-admin.astro +110 -0
- package/templates/astro/scheduler/src/lumen/scheduler.astro +36 -0
- package/templates/astro/section-header/src/lumen/section-header.astro +35 -0
- package/templates/astro/theme-builder/src/lumen/theme-builder.astro +28 -0
- package/templates/elements/advanced-fields/src/lumen/advanced-fields.html +25 -0
- package/templates/elements/analytics-dashboard/src/lumen/analytics-dashboard.html +28 -0
- package/templates/elements/auth-onboarding/src/lumen/auth-onboarding.html +79 -0
- package/templates/elements/command-center/src/lumen/command-center.html +15 -0
- package/templates/elements/commerce-dashboard/src/lumen/commerce-dashboard.html +24 -0
- package/templates/elements/content-flow-actions/src/lumen/content-flow-actions.html +11 -0
- package/templates/elements/content-flow-header/src/lumen/content-flow-header.html +15 -0
- package/templates/elements/content-flow-list/src/lumen/content-flow-list.html +14 -0
- package/templates/elements/content-flow-settings/src/lumen/content-flow-settings.html +18 -0
- package/templates/elements/data-collections/src/lumen/data-collections.html +31 -0
- package/templates/elements/feature-preview/src/lumen/feature-preview.html +13 -0
- package/templates/elements/guided-onboarding/src/lumen/guided-onboarding.html +16 -0
- package/templates/elements/interactive-pricing/src/lumen/interactive-pricing.html +13 -0
- package/templates/elements/media-workspace/src/lumen/media-workspace.html +57 -0
- package/templates/elements/media-workspace/src/lumen/media-workspace.ts +163 -0
- package/templates/elements/page-header/src/lumen/page-header.html +20 -0
- package/templates/elements/project-workspace/src/lumen/project-workspace.html +17 -0
- package/templates/elements/rich-text-editor/src/lumen/rich-text-editor.html +17 -0
- package/templates/elements/saas-admin/src/lumen/saas-admin.html +18 -0
- package/templates/elements/scheduler/src/lumen/scheduler.html +27 -0
- package/templates/elements/section-header/src/lumen/section-header.html +21 -0
- package/templates/elements/theme-builder/src/lumen/theme-builder.html +59 -0
- package/templates/react/advanced-fields/src/lumen/advanced-fields.tsx +37 -0
- package/templates/react/analytics-dashboard/src/lumen/analytics-dashboard.tsx +143 -0
- package/templates/react/auth-onboarding/src/lumen/auth-onboarding.tsx +129 -0
- package/templates/react/command-center/src/lumen/command-center.tsx +85 -0
- package/templates/react/commerce-dashboard/src/lumen/commerce-dashboard.tsx +148 -0
- package/templates/react/content-flow-actions/src/lumen/content-flow-actions.tsx +11 -0
- package/templates/react/content-flow-header/src/lumen/content-flow-header.tsx +14 -0
- package/templates/react/content-flow-list/src/lumen/content-flow-list.tsx +19 -0
- package/templates/react/content-flow-settings/src/lumen/content-flow-settings.tsx +26 -0
- package/templates/react/data-collections/src/lumen/data-collections.tsx +60 -0
- package/templates/react/feature-preview/src/lumen/feature-preview.tsx +42 -0
- package/templates/react/guided-onboarding/src/lumen/guided-onboarding.tsx +100 -0
- package/templates/react/import-review/src/lumen/import-review.tsx +150 -0
- package/templates/react/interactive-pricing/src/lumen/interactive-pricing.tsx +60 -0
- package/templates/react/media-workspace/src/lumen/adjustment-controls.tsx +61 -0
- package/templates/react/media-workspace/src/lumen/media-processing.tsx +43 -0
- package/templates/react/media-workspace/src/lumen/media-workspace.tsx +100 -0
- package/templates/react/operational-records/src/lumen/operational-records.tsx +92 -0
- package/templates/react/page-header/src/lumen/page-header.tsx +41 -0
- package/templates/react/project-workspace/src/lumen/project-workspace.tsx +112 -0
- package/templates/react/record-workspace/src/lumen/record-workspace.tsx +67 -0
- package/templates/react/review-workflow/src/lumen/review-workflow.tsx +202 -0
- package/templates/react/rich-text-editor/src/lumen/rich-text-editor.tsx +33 -0
- package/templates/react/saas-admin/src/lumen/saas-admin.tsx +119 -0
- package/templates/react/scheduler/src/lumen/scheduler.tsx +45 -0
- package/templates/react/section-header/src/lumen/section-header.tsx +33 -0
- package/templates/react/styles.d.ts +1 -0
- package/templates/react/theme-builder/src/lumen/theme-builder.tsx +57 -0
- package/templates/react/tsconfig.json +14 -0
- package/templates/react/validated-form/src/lumen/validated-form.tsx +166 -0
- package/templates/shared/common/src/lumen/lumen-template.css +247 -0
- package/templates/shared/media-workspace/src/lumen/media-workspace.css +14 -0
- package/templates/shared/visual-blocks/src/lumen/product-blocks.ts +224 -0
- package/v4-migration.json +447 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Agenda,
|
|
3
|
+
Calendar,
|
|
4
|
+
DatePicker,
|
|
5
|
+
Schedule,
|
|
6
|
+
useSchedule
|
|
7
|
+
} from '@santi020k/lumen-react'
|
|
8
|
+
|
|
9
|
+
export const SchedulerRecipe = () => {
|
|
10
|
+
const schedule = useSchedule()
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<section className="lumen-recipe lumen-recipe--scheduler">
|
|
14
|
+
<Calendar aria-label="Choose schedule date" />
|
|
15
|
+
<DatePicker aria-label="Jump to date" />
|
|
16
|
+
<Schedule>
|
|
17
|
+
<header>
|
|
18
|
+
<h2>Launch week</h2>
|
|
19
|
+
</header>
|
|
20
|
+
<div data-ui-schedule-grid>
|
|
21
|
+
<section {...schedule.getSlotProps('monday')}>
|
|
22
|
+
<article {...schedule.getEventProps('schedule-planning')}>
|
|
23
|
+
Planning
|
|
24
|
+
</article>
|
|
25
|
+
</section>
|
|
26
|
+
<section {...schedule.getSlotProps('friday')}>
|
|
27
|
+
<article {...schedule.getEventProps('schedule-ship')}>Ship</article>
|
|
28
|
+
</section>
|
|
29
|
+
</div>
|
|
30
|
+
</Schedule>
|
|
31
|
+
<Agenda>
|
|
32
|
+
<ol>
|
|
33
|
+
<li>
|
|
34
|
+
<strong>Design review</strong>
|
|
35
|
+
<span>10:00</span>
|
|
36
|
+
</li>
|
|
37
|
+
<li>
|
|
38
|
+
<strong>Release notes</strong>
|
|
39
|
+
<span>14:30</span>
|
|
40
|
+
</li>
|
|
41
|
+
</ol>
|
|
42
|
+
</Agenda>
|
|
43
|
+
</section>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { Badge, Stack, Typography } from '@santi020k/lumen-react'
|
|
4
|
+
|
|
5
|
+
export interface SectionHeaderRecipeProps {
|
|
6
|
+
actions?: ReactNode
|
|
7
|
+
actionsLabel?: string
|
|
8
|
+
count?: string
|
|
9
|
+
description?: string
|
|
10
|
+
headingId: string
|
|
11
|
+
level?: 2 | 3 | 4 | 5 | 6
|
|
12
|
+
title: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SectionHeaderRecipe = ({ actions, actionsLabel = 'Section actions', count, description, headingId, level = 2, title }: SectionHeaderRecipeProps) => {
|
|
16
|
+
const headings = { 2: 'h2', 3: 'h3', 4: 'h4', 5: 'h5', 6: 'h6' } as const
|
|
17
|
+
const Heading = headings[level]
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<header aria-labelledby={headingId}>
|
|
21
|
+
<Stack direction="horizontal" align="start" justify="between" gap="group" wrap>
|
|
22
|
+
<Stack gap="related" style={{ flex: '1 1 20rem', minWidth: 0, overflowWrap: 'anywhere' }}>
|
|
23
|
+
<Stack direction="horizontal" align="center" gap="related" wrap>
|
|
24
|
+
<Typography><Heading id={headingId}>{title}</Heading></Typography>
|
|
25
|
+
{count !== undefined && <Badge variant="secondary">{count}</Badge>}
|
|
26
|
+
</Stack>
|
|
27
|
+
{description && <Typography><p>{description}</p></Typography>}
|
|
28
|
+
</Stack>
|
|
29
|
+
{actions && <Stack direction="horizontal" align="center" gap="related" wrap role="group" aria-label={actionsLabel}>{actions}</Stack>}
|
|
30
|
+
</Stack>
|
|
31
|
+
</header>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '*.css' {}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react'
|
|
2
|
+
import { useMemo, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { createThemeFromHue, exportThemeCss } from '@santi020k/lumen-core'
|
|
5
|
+
import { Button, Card, Input, ThemeBuilder } from '@santi020k/lumen-react'
|
|
6
|
+
|
|
7
|
+
export const ThemeBuilderRecipe = () => {
|
|
8
|
+
const [hue, setHue] = useState(264)
|
|
9
|
+
const tokens = useMemo(() => createThemeFromHue(hue), [hue])
|
|
10
|
+
|
|
11
|
+
const previewStyle = useMemo(
|
|
12
|
+
() => Object.fromEntries(
|
|
13
|
+
Object.entries(tokens).map(([token, value]) => [`--${token}`, value])
|
|
14
|
+
) as CSSProperties, [tokens]
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const css = useMemo(() => exportThemeCss(tokens), [tokens])
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<ThemeBuilder className="lumen-recipe lumen-recipe--theme-builder">
|
|
21
|
+
<header>
|
|
22
|
+
<h2>Brand theme</h2>
|
|
23
|
+
<Button
|
|
24
|
+
type="button"
|
|
25
|
+
onClick={() => navigator.clipboard.writeText(css)}
|
|
26
|
+
>
|
|
27
|
+
Copy CSS
|
|
28
|
+
</Button>
|
|
29
|
+
</header>
|
|
30
|
+
<Card id="lumen-theme-preview" style={previewStyle}>
|
|
31
|
+
<span
|
|
32
|
+
data-ui-swatch
|
|
33
|
+
style={{ '--ui-swatch': 'hsl(var(--brand))' } as CSSProperties}
|
|
34
|
+
>
|
|
35
|
+
</span>
|
|
36
|
+
<span
|
|
37
|
+
data-ui-swatch
|
|
38
|
+
style={{ '--ui-swatch': 'hsl(var(--accent))' } as CSSProperties}
|
|
39
|
+
>
|
|
40
|
+
</span>
|
|
41
|
+
</Card>
|
|
42
|
+
<Card>
|
|
43
|
+
<Input
|
|
44
|
+
aria-label="Brand hue"
|
|
45
|
+
max="359"
|
|
46
|
+
min="0"
|
|
47
|
+
type="range"
|
|
48
|
+
value={hue}
|
|
49
|
+
onChange={event => {
|
|
50
|
+
setHue(Number(event.currentTarget.value))
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
</Card>
|
|
54
|
+
<textarea aria-label="Theme CSS" readOnly value={css}></textarea>
|
|
55
|
+
</ThemeBuilder>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"rootDir": "../../../../",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@santi020k/lumen-react": ["../../../react/src/index.ts"]
|
|
10
|
+
},
|
|
11
|
+
"noEmit": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { ReactNode, SyntheticEvent } from 'react'
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { type LumenFormErrorInput, type LumenFormErrors, normalizeLumenFormErrors } from '@santi020k/lumen-core'
|
|
5
|
+
import { Alert, ErrorSummary } from '@santi020k/lumen-react'
|
|
6
|
+
|
|
7
|
+
export interface ValidatedFormRecipeProps {
|
|
8
|
+
children: (state: { errors: LumenFormErrors, pending: boolean }) => ReactNode
|
|
9
|
+
id: string
|
|
10
|
+
label: string
|
|
11
|
+
summaryHeading: string
|
|
12
|
+
failureMessage: string
|
|
13
|
+
successMessage: string
|
|
14
|
+
validate?: (form: HTMLFormElement) => LumenFormErrorInput
|
|
15
|
+
submit: (values: FormData) => Promise<LumenFormErrorInput | undefined>
|
|
16
|
+
onFailure?: (error: unknown) => void
|
|
17
|
+
onReset?: (event: SyntheticEvent<HTMLFormElement>) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const emptyErrors: LumenFormErrors = { fields: [], form: [] }
|
|
21
|
+
|
|
22
|
+
const nativeErrors = (form: HTMLFormElement): LumenFormErrors => {
|
|
23
|
+
const view = form.ownerDocument.defaultView
|
|
24
|
+
const fields: LumenFormErrors['fields'] = []
|
|
25
|
+
|
|
26
|
+
if (!view) return emptyErrors
|
|
27
|
+
|
|
28
|
+
for (const control of form.elements) {
|
|
29
|
+
const isControl = control instanceof view.HTMLInputElement ||
|
|
30
|
+
control instanceof view.HTMLSelectElement || control instanceof view.HTMLTextAreaElement
|
|
31
|
+
|
|
32
|
+
if (!isControl) continue
|
|
33
|
+
|
|
34
|
+
if (control.willValidate && !control.validity.valid) {
|
|
35
|
+
fields.push({ controlId: control.id, name: control.name, message: control.validationMessage })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { fields, form: [] }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Product validation, network policy and idempotency remain in the supplied callbacks. */
|
|
43
|
+
export const ValidatedFormRecipe = ({
|
|
44
|
+
children, id, label, summaryHeading, failureMessage, successMessage, validate, submit, onFailure, onReset
|
|
45
|
+
}: ValidatedFormRecipeProps) => {
|
|
46
|
+
const formRef = useRef<HTMLFormElement>(null)
|
|
47
|
+
const activeRequestRef = useRef(false)
|
|
48
|
+
const attemptedRef = useRef(false)
|
|
49
|
+
const [errors, setErrors] = useState<LumenFormErrors>(emptyErrors)
|
|
50
|
+
const [pending, setPending] = useState(false)
|
|
51
|
+
const [succeeded, setSucceeded] = useState(false)
|
|
52
|
+
const [focusErrors, setFocusErrors] = useState(false)
|
|
53
|
+
|
|
54
|
+
const readErrors = (form: HTMLFormElement) => {
|
|
55
|
+
const native = nativeErrors(form)
|
|
56
|
+
const product = normalizeLumenFormErrors(validate?.(form))
|
|
57
|
+
|
|
58
|
+
return { fields: [...native.fields, ...product.fields], form: [...native.form, ...product.form] }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
useLayoutEffect(() => {
|
|
62
|
+
const form = formRef.current
|
|
63
|
+
|
|
64
|
+
if (!form || !focusErrors) return
|
|
65
|
+
|
|
66
|
+
const targetId = errors.fields.find(item => item.controlId)?.controlId
|
|
67
|
+
const target = targetId ? form.ownerDocument.getElementById(targetId) : undefined
|
|
68
|
+
|
|
69
|
+
if (target instanceof HTMLElement && (form.contains(target) || ('form' in target && target.form === form))) target.focus()
|
|
70
|
+
else form.querySelector<HTMLElement>('[data-ui-error-summary]')?.focus()
|
|
71
|
+
|
|
72
|
+
setFocusErrors(false)
|
|
73
|
+
}, [errors, focusErrors])
|
|
74
|
+
|
|
75
|
+
const handleSubmit = async (event: SyntheticEvent<HTMLFormElement>) => {
|
|
76
|
+
event.preventDefault()
|
|
77
|
+
|
|
78
|
+
if (activeRequestRef.current) return
|
|
79
|
+
|
|
80
|
+
const form = event.currentTarget
|
|
81
|
+
const next = readErrors(form)
|
|
82
|
+
|
|
83
|
+
attemptedRef.current = true
|
|
84
|
+
|
|
85
|
+
setErrors(next)
|
|
86
|
+
|
|
87
|
+
setSucceeded(false)
|
|
88
|
+
|
|
89
|
+
if (next.fields.length || next.form.length) {
|
|
90
|
+
setFocusErrors(true)
|
|
91
|
+
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Capture values before pending UI disables the controls.
|
|
96
|
+
const values = new FormData(form)
|
|
97
|
+
|
|
98
|
+
activeRequestRef.current = true
|
|
99
|
+
|
|
100
|
+
setPending(true)
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const result = normalizeLumenFormErrors(await submit(values))
|
|
104
|
+
|
|
105
|
+
setErrors(result)
|
|
106
|
+
|
|
107
|
+
if (result.fields.length || result.form.length) setFocusErrors(true)
|
|
108
|
+
else setSucceeded(true)
|
|
109
|
+
} catch (error) {
|
|
110
|
+
setErrors({ fields: [], form: [failureMessage] })
|
|
111
|
+
|
|
112
|
+
setFocusErrors(true)
|
|
113
|
+
|
|
114
|
+
onFailure?.(error)
|
|
115
|
+
} finally {
|
|
116
|
+
activeRequestRef.current = false
|
|
117
|
+
|
|
118
|
+
setPending(false)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<form
|
|
124
|
+
ref={formRef}
|
|
125
|
+
id={id}
|
|
126
|
+
aria-label={label}
|
|
127
|
+
aria-busy={pending}
|
|
128
|
+
noValidate
|
|
129
|
+
onSubmit={event => {
|
|
130
|
+
void handleSubmit(event)
|
|
131
|
+
}}
|
|
132
|
+
onBlur={event => {
|
|
133
|
+
const next = event.relatedTarget
|
|
134
|
+
|
|
135
|
+
// Keep a focused action stationary between pointer down and click.
|
|
136
|
+
if (next instanceof HTMLElement && next.closest('button') && event.currentTarget.contains(next)) return
|
|
137
|
+
|
|
138
|
+
if (attemptedRef.current && !activeRequestRef.current) setErrors(readErrors(event.currentTarget))
|
|
139
|
+
}}
|
|
140
|
+
onChange={event => {
|
|
141
|
+
if (attemptedRef.current && !activeRequestRef.current) setErrors(readErrors(event.currentTarget))
|
|
142
|
+
}}
|
|
143
|
+
onReset={event => {
|
|
144
|
+
if (activeRequestRef.current) {
|
|
145
|
+
event.preventDefault()
|
|
146
|
+
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
onReset?.(event)
|
|
151
|
+
|
|
152
|
+
if (event.defaultPrevented) return
|
|
153
|
+
|
|
154
|
+
attemptedRef.current = false
|
|
155
|
+
|
|
156
|
+
setErrors(emptyErrors)
|
|
157
|
+
|
|
158
|
+
setSucceeded(false)
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<ErrorSummary id={`${id}-errors`} heading={summaryHeading} errors={errors} />
|
|
162
|
+
{children({ errors, pending })}
|
|
163
|
+
{succeeded && <Alert role="status">{successMessage}</Alert>}
|
|
164
|
+
</form>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
.lumen-template {
|
|
2
|
+
min-height: 100vh;
|
|
3
|
+
background: hsl(var(--canvas));
|
|
4
|
+
color: hsl(var(--ink));
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.lumen-template__shell {
|
|
8
|
+
display: grid;
|
|
9
|
+
grid-template-columns: 15rem minmax(0, 1fr);
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.lumen-template__sidebar {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
gap: 1.5rem;
|
|
17
|
+
padding: 1.25rem;
|
|
18
|
+
border: 0;
|
|
19
|
+
border-right: 1px solid hsl(var(--line));
|
|
20
|
+
border-radius: 0;
|
|
21
|
+
background: hsl(var(--surface));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.lumen-template__sidebar nav {
|
|
25
|
+
display: grid;
|
|
26
|
+
gap: 0.35rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.lumen-template__sidebar a {
|
|
30
|
+
padding: 0.7rem 0.8rem;
|
|
31
|
+
border-radius: 0.65rem;
|
|
32
|
+
color: hsl(var(--ink-soft));
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.lumen-template__sidebar a[aria-current="page"] {
|
|
37
|
+
background: hsl(var(--brand-soft));
|
|
38
|
+
color: hsl(var(--brand));
|
|
39
|
+
font-weight: 700;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.lumen-template__main {
|
|
43
|
+
display: grid;
|
|
44
|
+
align-content: start;
|
|
45
|
+
gap: 1rem;
|
|
46
|
+
min-width: 0;
|
|
47
|
+
padding: clamp(1rem, 4vw, 3rem);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.lumen-template__header,
|
|
51
|
+
.lumen-template__section-header,
|
|
52
|
+
.lumen-template__actions {
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 1rem;
|
|
55
|
+
align-items: center;
|
|
56
|
+
justify-content: space-between;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.lumen-template__header h1 {
|
|
60
|
+
margin: 0.4rem 0;
|
|
61
|
+
font-size: clamp(2rem, 5vw, 3.25rem);
|
|
62
|
+
letter-spacing: -0.045em;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.lumen-template__header p,
|
|
66
|
+
.lumen-template__section-header p,
|
|
67
|
+
.lumen-template__muted {
|
|
68
|
+
margin: 0;
|
|
69
|
+
color: hsl(var(--ink-soft));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.lumen-template__metrics,
|
|
73
|
+
.lumen-template__grid,
|
|
74
|
+
.lumen-template__choices {
|
|
75
|
+
display: grid;
|
|
76
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
77
|
+
gap: 1rem;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.lumen-template__metric,
|
|
81
|
+
.lumen-template__panel,
|
|
82
|
+
.lumen-template__form {
|
|
83
|
+
display: grid;
|
|
84
|
+
gap: 1rem;
|
|
85
|
+
padding: 1.2rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.lumen-template__metric .ui-sparkline {
|
|
89
|
+
width: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.lumen-template__grid {
|
|
93
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.lumen-template__table {
|
|
97
|
+
overflow-x: auto;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.lumen-template__table table {
|
|
101
|
+
width: 100%;
|
|
102
|
+
border-collapse: collapse;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.lumen-template__table th,
|
|
106
|
+
.lumen-template__table td {
|
|
107
|
+
padding: 0.8rem;
|
|
108
|
+
border-bottom: 1px solid hsl(var(--line));
|
|
109
|
+
text-align: left;
|
|
110
|
+
white-space: nowrap;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.lumen-template__list {
|
|
114
|
+
display: grid;
|
|
115
|
+
gap: 0.75rem;
|
|
116
|
+
padding: 0;
|
|
117
|
+
margin: 0;
|
|
118
|
+
list-style: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.lumen-template__list li {
|
|
122
|
+
padding: 0.75rem;
|
|
123
|
+
border-radius: 0.65rem;
|
|
124
|
+
background: hsl(var(--surface-muted));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.lumen-template__form-shell {
|
|
128
|
+
display: grid;
|
|
129
|
+
min-height: 100vh;
|
|
130
|
+
place-items: center;
|
|
131
|
+
padding: 1rem;
|
|
132
|
+
background:
|
|
133
|
+
radial-gradient(circle at 15% 15%, hsl(var(--brand) / 0.2), transparent 28rem),
|
|
134
|
+
hsl(var(--canvas));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.lumen-template__form {
|
|
138
|
+
width: min(100%, 38rem);
|
|
139
|
+
padding: clamp(1.4rem, 4vw, 2.5rem);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.lumen-template__field {
|
|
143
|
+
display: grid;
|
|
144
|
+
gap: 0.45rem;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.lumen-template__choices {
|
|
148
|
+
grid-template-columns: 1fr 1fr;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.lumen-template__choice {
|
|
152
|
+
padding: 1rem;
|
|
153
|
+
border: 1px solid hsl(var(--line));
|
|
154
|
+
border-radius: 0.75rem;
|
|
155
|
+
background: hsl(var(--surface-muted));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@media (max-width: 64rem) {
|
|
159
|
+
.lumen-template__metrics {
|
|
160
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.lumen-template__grid {
|
|
164
|
+
grid-template-columns: 1fr;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
@media (max-width: 48rem) {
|
|
169
|
+
.lumen-template__shell {
|
|
170
|
+
grid-template-columns: 1fr;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.lumen-template__sidebar {
|
|
174
|
+
position: static;
|
|
175
|
+
border-right: 0;
|
|
176
|
+
border-bottom: 1px solid hsl(var(--line));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.lumen-template__sidebar nav {
|
|
180
|
+
grid-template-columns: repeat(3, max-content);
|
|
181
|
+
overflow-x: auto;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
@media (max-width: 34rem) {
|
|
186
|
+
.lumen-template__header,
|
|
187
|
+
.lumen-template__section-header {
|
|
188
|
+
align-items: start;
|
|
189
|
+
flex-direction: column;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.lumen-template__metrics,
|
|
193
|
+
.lumen-template__choices {
|
|
194
|
+
grid-template-columns: 1fr;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.lumen-login-examples {
|
|
199
|
+
box-sizing: border-box;
|
|
200
|
+
grid-column: 1 / -1;
|
|
201
|
+
width: 100%;
|
|
202
|
+
min-width: 0;
|
|
203
|
+
padding: clamp(1.25rem, 4vw, 3rem);
|
|
204
|
+
color: hsl(var(--ink));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.lumen-login-examples header {
|
|
208
|
+
max-width: 44rem;
|
|
209
|
+
margin-bottom: 1.5rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.lumen-login-examples p {
|
|
213
|
+
margin: 0;
|
|
214
|
+
color: hsl(var(--ink-soft));
|
|
215
|
+
line-height: 1.6;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.lumen-login-examples h2,
|
|
219
|
+
.lumen-login-examples h3 {
|
|
220
|
+
margin: 0;
|
|
221
|
+
color: hsl(var(--ink));
|
|
222
|
+
line-height: 1.2;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.lumen-login-examples h2 {
|
|
226
|
+
margin-block: 0.5rem;
|
|
227
|
+
font-size: clamp(1.75rem, 3vw, 2.5rem);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.lumen-login-examples__grid {
|
|
231
|
+
display: grid;
|
|
232
|
+
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
|
|
233
|
+
gap: 1.25rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.lumen-login-examples__card {
|
|
237
|
+
display: flex;
|
|
238
|
+
min-width: 0;
|
|
239
|
+
flex-direction: column;
|
|
240
|
+
gap: 1rem;
|
|
241
|
+
padding: 1.5rem;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.lumen-login-examples__card .ui-field {
|
|
245
|
+
display: grid;
|
|
246
|
+
gap: 0.5rem;
|
|
247
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.lumen-media-workspace { display: grid; gap: var(--ui-space-lg); min-width: 0; container-type: inline-size; }
|
|
2
|
+
.lumen-media-workspace__layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(12rem, 20rem); gap: var(--ui-space-lg); align-items: start; }
|
|
3
|
+
.lumen-media-workspace__preview, .lumen-media-workspace__inspector, .lumen-media-workspace__bottom { min-width: 0; }
|
|
4
|
+
.lumen-media-workspace__preview { color: hsl(var(--ink)); background: hsl(var(--surface)); }
|
|
5
|
+
.lumen-media-workspace__tools { min-width: 0; }
|
|
6
|
+
.lumen-media-workspace__tools .ui-toolbar { flex-wrap: wrap; }
|
|
7
|
+
.lumen-media-workspace__inspector > * + * { margin-block-start: var(--ui-space-lg); }
|
|
8
|
+
@container (max-width: 45rem) {
|
|
9
|
+
.lumen-media-workspace__layout { grid-template-columns: minmax(0, 1fr); }
|
|
10
|
+
.lumen-media-workspace__tools { position: sticky; bottom: var(--ui-space-sm); z-index: 2; }
|
|
11
|
+
}
|
|
12
|
+
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
|
|
13
|
+
.lumen-media-workspace__tools { background: hsl(var(--surface)); backdrop-filter: none; }
|
|
14
|
+
}
|