aeon-ui-engine 1.0.0 → 1.0.2
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/.changeset/config.json +2 -1
- package/CHANGELOG.md +38 -0
- package/COMPONENTS.md +7 -2
- package/INTEGRATION.md +14 -0
- package/PUBLISHING.md +20 -0
- package/README.md +8 -4
- package/apps/demo/og.config.mjs +8 -3
- package/apps/demo/public/og-aeon-ui-engine.png +0 -0
- package/apps/demo/public/og.svg +2 -2
- package/apps/demo/scripts/generate-og-png.mjs +4 -2
- package/apps/demo/src/App.tsx +2 -2
- package/apps/demo/src/aeonDiagrams.ts +17 -75
- package/apps/demo/src/components/DiagramsSection.tsx +8 -9
- package/apps/demo/src/components/HeroInstall.tsx +7 -4
- package/apps/demo/src/components/LandingFaq.tsx +1 -1
- package/apps/demo/src/components/LandingFooter.tsx +8 -0
- package/apps/demo/src/components/LandingHeader.tsx +4 -2
- package/apps/demo/src/components/LayersSection.tsx +6 -10
- package/apps/demo/src/components/MermaidDiagram.tsx +5 -3
- package/apps/demo/src/components/PhilosophySection.tsx +0 -2
- package/apps/demo/src/components/PlaygroundBlock.tsx +3 -8
- package/apps/demo/src/components/PlaygroundControlsDemo.tsx +63 -0
- package/apps/demo/src/components/PlaygroundSection.tsx +7 -91
- package/apps/demo/src/components/ThemeSwitcher.tsx +13 -10
- package/apps/demo/src/demo-layout.css +29 -0
- package/apps/demo/src/landing.css +138 -89
- package/apps/demo/src/nav.ts +3 -3
- package/apps/demo/src/site.ts +99 -228
- package/docs/MACHINES.md +6 -1
- package/package.json +1 -1
- package/packages/core/src/anatomy.ts +23 -0
- package/packages/panda/panda.config.ts +19 -1
- package/packages/panda/src/recipes/button.ts +4 -4
- package/packages/panda/src/recipes/combobox.ts +1 -1
- package/packages/panda/src/recipes/index.ts +5 -0
- package/packages/panda/src/recipes/pin-input.ts +29 -0
- package/packages/panda/src/recipes/progress.ts +44 -0
- package/packages/panda/src/recipes/radio-group.ts +51 -0
- package/packages/panda/src/recipes/select.ts +3 -1
- package/packages/panda/src/recipes/separator.ts +35 -0
- package/packages/panda/src/recipes/slider.ts +57 -0
- package/packages/panda/src/theme/tokens.ts +5 -0
- package/packages/panda/styled-system/recipes/aeon-pin-input.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-pin-input.mjs +36 -0
- package/packages/panda/styled-system/recipes/aeon-progress.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-progress.mjs +44 -0
- package/packages/panda/styled-system/recipes/aeon-radio-group.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-radio-group.mjs +48 -0
- package/packages/panda/styled-system/recipes/aeon-separator.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-separator.mjs +41 -0
- package/packages/panda/styled-system/recipes/aeon-slider.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-slider.mjs +48 -0
- package/packages/panda/styled-system/recipes/index.d.ts +5 -0
- package/packages/panda/styled-system/recipes/index.mjs +5 -0
- package/packages/panda/styled-system/styles.css +233 -52
- package/packages/panda/styled-system/tokens/index.mjs +32 -0
- package/packages/panda/styled-system/tokens/tokens.d.ts +2 -2
- package/packages/panda/styled-system/types/conditions.d.ts +1 -1
- package/packages/primitives/src/index.ts +1 -0
- package/packages/primitives/src/machines/slider.ts +93 -0
- package/packages/react/src/checkbox/index.tsx +14 -0
- package/packages/react/src/floating/FloatingPositioner.tsx +59 -0
- package/packages/react/src/hooks/use-anchor-position.ts +33 -5
- package/packages/react/src/hooks/use-prefers-hover.ts +16 -0
- package/packages/react/src/index.ts +5 -0
- package/packages/react/src/menu/index.tsx +38 -11
- package/packages/react/src/pin-input/index.tsx +223 -0
- package/packages/react/src/popover/index.tsx +38 -11
- package/packages/react/src/progress/index.tsx +110 -0
- package/packages/react/src/radio-group/index.tsx +199 -0
- package/packages/react/src/select/index.tsx +51 -28
- package/packages/react/src/select/select.test.tsx +21 -0
- package/packages/react/src/separator/index.tsx +23 -0
- package/packages/react/src/slider/index.tsx +264 -0
- package/packages/react/src/toast/primitives.tsx +8 -1
- package/packages/react/src/toast/toast.test.tsx +40 -0
- package/packages/react/src/tooltip/index.tsx +124 -20
- package/packages/react/src/tooltip/tooltip.test.tsx +114 -0
- package/packages/site/README.md +1 -1
- package/packages/ui/src/index.ts +5 -0
- package/packages/ui/src/pin-input.tsx +16 -0
- package/packages/ui/src/progress.tsx +21 -0
- package/packages/ui/src/radio-group.tsx +24 -0
- package/packages/ui/src/select.tsx +3 -4
- package/packages/ui/src/separator.tsx +15 -0
- package/packages/ui/src/slider.tsx +24 -0
- package/apps/demo/public/og.png +0 -0
- package/apps/demo/src/components/SceneSection.tsx +0 -157
package/apps/demo/src/site.ts
CHANGED
|
@@ -3,12 +3,23 @@ export const siteOrigin =
|
|
|
3
3
|
(import.meta.env.VITE_SITE_ORIGIN as string | undefined)?.replace(/\/$/, '') ??
|
|
4
4
|
'https://aeon-ui.com'
|
|
5
5
|
|
|
6
|
+
/** Published npm package (monorepo root). */
|
|
7
|
+
export const npmPackage = 'aeon-ui-engine' as const
|
|
8
|
+
|
|
9
|
+
export const npmInstallCommand = `npm install ${npmPackage}` as const
|
|
10
|
+
|
|
11
|
+
/** User-facing code sample — install line plus usage (no internal workspace import paths). */
|
|
12
|
+
export const withInstall = (usage: string) => `${npmInstallCommand}\n\n${usage.trim()}`
|
|
13
|
+
|
|
6
14
|
export const site = {
|
|
7
15
|
brand: 'Aeon UI',
|
|
8
16
|
tagline: 'Future-proof UI infrastructure for the web',
|
|
9
17
|
url: 'https://github.com/GenericCPU/aeon-ui',
|
|
10
18
|
siteOrigin,
|
|
11
19
|
repoCloneUrl: 'https://github.com/GenericCPU/aeon-ui.git',
|
|
20
|
+
npmPackage,
|
|
21
|
+
npmRegistryUrl: `https://www.npmjs.com/package/${npmPackage}`,
|
|
22
|
+
npmInstallCommand,
|
|
12
23
|
studio: 'SpiderCorp',
|
|
13
24
|
studioUrl: 'https://spidercorp.xyz',
|
|
14
25
|
|
|
@@ -19,10 +30,12 @@ export const site = {
|
|
|
19
30
|
'Statechart-driven primitives and a styled layer your team controls — dependable behavior today, room to grow tomorrow.',
|
|
20
31
|
heroSub:
|
|
21
32
|
'Grounded in proven state theory and production-ready patterns for compound components, anatomy, and styling.',
|
|
22
|
-
heroCtaPrimary: '
|
|
33
|
+
heroCtaPrimary: 'Try components',
|
|
23
34
|
heroCtaSecondary: 'View architecture',
|
|
24
|
-
|
|
25
|
-
|
|
35
|
+
headerCtaShort: 'Try',
|
|
36
|
+
heroInstallLabel: 'npm',
|
|
37
|
+
heroInstallCommand: npmInstallCommand,
|
|
38
|
+
heroInstallHint: `${npmPackage} on npm — core, React bindings, styled components, and themes in one install.`,
|
|
26
39
|
stateCardLabel: 'Example: fetch region',
|
|
27
40
|
stateCardHint: 'Five states on the rail. One active. Click Run.',
|
|
28
41
|
|
|
@@ -40,115 +53,101 @@ export const site = {
|
|
|
40
53
|
{
|
|
41
54
|
label: 'Styled',
|
|
42
55
|
detail: 'Panda CSS slot recipes with thoughtful defaults you can extend',
|
|
43
|
-
example:
|
|
56
|
+
example: `${npmInstallCommand}\nimport { Button } from '@aeon-ui/ui'`,
|
|
44
57
|
},
|
|
45
58
|
],
|
|
46
59
|
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
philosophyLabel: 'Foundation',
|
|
61
|
+
philosophyTitle: 'What Aeon is',
|
|
62
|
+
philosophyLead:
|
|
63
|
+
'Not a grab bag of widgets — a discipline for interfaces that stay legible as requirements accumulate. Grounded in statechart theory and the conviction that the UI must represent every condition the model allows, and imply none that it does not.',
|
|
64
|
+
thesisUrl:
|
|
65
|
+
'https://www.theseus.fi/bitstream/handle/10024/334118/Thanh_Nguyen.pdf?sequence=2&isAllowed=y',
|
|
66
|
+
thesisCta: 'Read the thesis (PDF)',
|
|
67
|
+
|
|
68
|
+
layersLabel: 'Shape',
|
|
69
|
+
layersTitle: 'Separation on purpose',
|
|
49
70
|
layersLead:
|
|
50
|
-
'
|
|
71
|
+
'Three layers so behavior, contract, and brand can evolve independently — without forking the mental model.',
|
|
51
72
|
|
|
52
73
|
diagramsLabel: 'Statecharts',
|
|
53
|
-
diagramsTitle: '
|
|
74
|
+
diagramsTitle: 'A map the whole team can read',
|
|
54
75
|
diagramsLead:
|
|
55
|
-
'Executable
|
|
76
|
+
'Executable models you can review with design and engineering before complexity lands in production — the same chart the playground runs.',
|
|
56
77
|
statesDocUrl: 'https://github.com/GenericCPU/aeon-ui/blob/main/STATES.md',
|
|
57
78
|
|
|
58
|
-
playgroundLabel: '
|
|
59
|
-
playgroundTitle: '
|
|
79
|
+
playgroundLabel: 'Proof',
|
|
80
|
+
playgroundTitle: 'See the discipline in motion',
|
|
60
81
|
playgroundLead:
|
|
61
|
-
'
|
|
62
|
-
|
|
63
|
-
philosophyLabel: 'Foundation',
|
|
64
|
-
philosophyTitle: 'Built on statecharts',
|
|
65
|
-
philosophyLead:
|
|
66
|
-
'Thanh Nguyen’s research on statecharts for web applications informs how Aeon models interaction — clear states, clear transitions, lasting maintainability.',
|
|
67
|
-
thesisUrl:
|
|
68
|
-
'https://www.theseus.fi/bitstream/handle/10024/334118/Thanh_Nguyen.pdf?sequence=2&isAllowed=y',
|
|
69
|
-
thesisCta: 'Read the thesis (PDF)',
|
|
82
|
+
'Each block is a real machine. Interact to feel what it means when the interface and the chart stay in lockstep.',
|
|
70
83
|
|
|
71
84
|
faqLabel: 'FAQ',
|
|
72
|
-
faqTitle: '
|
|
73
|
-
faqLead: '
|
|
85
|
+
faqTitle: 'Questions teams ask',
|
|
86
|
+
faqLead: 'Identity and adoption — not a feature matrix.',
|
|
74
87
|
} as const
|
|
75
88
|
|
|
76
|
-
|
|
77
|
-
{
|
|
78
|
-
id: 'core',
|
|
79
|
-
title: '@aeon-ui/core',
|
|
80
|
-
description: 'Philosophy, anatomy, and attributes your theme can target with confidence.',
|
|
81
|
-
highlights: ['asyncAnatomy, buttonAnatomy, …', 'stateToAttr(snapshot)', 'PHILOSOPHY.md + STATES.md'],
|
|
82
|
-
example: `import { partAttrs, asyncAnatomy } from '@aeon-ui/core'
|
|
83
|
-
|
|
84
|
-
partAttrs(asyncAnatomy.scope, asyncAnatomy.readout, {
|
|
85
|
-
state: 'loading',
|
|
86
|
-
})`,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
id: 'react',
|
|
90
|
-
title: '@aeon-ui/react',
|
|
91
|
-
description: 'Headless React bindings where the interface reflects the machine snapshot.',
|
|
92
|
-
highlights: ['Async, Field, Dialog, Tabs', 'useAeonMachine(machine)', 'Compound parts'],
|
|
93
|
-
example: `import { Async, useAsyncContext } from '@aeon-ui/react'
|
|
94
|
-
|
|
95
|
-
<Async.Root>
|
|
96
|
-
<Async.Track />
|
|
97
|
-
<Async.Readout>{status}</Async.Readout>
|
|
98
|
-
</Async.Root>`,
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
id: 'ui',
|
|
102
|
-
title: '@aeon-ui/ui',
|
|
103
|
-
description: 'A polished styled layer — ship as-is or make it yours.',
|
|
104
|
-
highlights: ['Buttons, badges, panels', 'Panda slot recipes', 'Themes: light/dark × preset'],
|
|
105
|
-
example: `import { Async, Button } from '@aeon-ui/ui'
|
|
106
|
-
|
|
107
|
-
<Button.Root status="pending">Saving…</Button.Root>`,
|
|
108
|
-
},
|
|
109
|
-
] as const
|
|
110
|
-
|
|
89
|
+
/** Why we built the stack this way — sits above the layer cards. */
|
|
111
90
|
export const commitments = [
|
|
112
91
|
{
|
|
113
|
-
id: '
|
|
114
|
-
title: '
|
|
92
|
+
id: 'coherent',
|
|
93
|
+
title: 'One condition at a time',
|
|
115
94
|
description:
|
|
116
|
-
'
|
|
95
|
+
'Regions hold a single stable state the team can name, test, and extend — so interfaces stay coherent when flows multiply.',
|
|
117
96
|
},
|
|
118
97
|
{
|
|
119
|
-
id: '
|
|
120
|
-
title: '
|
|
98
|
+
id: 'shared',
|
|
99
|
+
title: 'A shared map',
|
|
121
100
|
description:
|
|
122
|
-
'
|
|
101
|
+
'Statecharts give designers and engineers the same picture of behavior before edge cases become tribal knowledge.',
|
|
123
102
|
},
|
|
124
103
|
{
|
|
125
|
-
id: '
|
|
126
|
-
title: '
|
|
104
|
+
id: 'sovereign',
|
|
105
|
+
title: 'Sovereign surfaces',
|
|
127
106
|
description:
|
|
128
|
-
'Unstyled primitives
|
|
107
|
+
'Unstyled primitives with a clear anatomy contract. Defaults when you want speed — full control when you do not.',
|
|
129
108
|
},
|
|
130
109
|
] as const
|
|
131
110
|
|
|
132
111
|
export const philosophyTenets = [
|
|
133
112
|
{
|
|
134
113
|
title: 'Write the chart first',
|
|
135
|
-
body: '
|
|
136
|
-
example: `states: { idle: {}, loading: {}, success: {}, failure: {} }`,
|
|
114
|
+
body: 'List every stable state and transition before you choose type scales. The model is the brief; the UI is its faithful projection.',
|
|
137
115
|
},
|
|
138
116
|
{
|
|
139
117
|
title: 'UI = f(state)',
|
|
140
|
-
body: '
|
|
141
|
-
example: `data-aeon-state="loading"`,
|
|
118
|
+
body: 'The snapshot decides what renders — labels, disabled affordances, and data-aeon-state together. Not parallel React flags that drift out of sync.',
|
|
142
119
|
},
|
|
143
120
|
{
|
|
144
121
|
title: 'Show the full map',
|
|
145
|
-
body: 'Users
|
|
146
|
-
example: `<Async.Track /> {/* idle · loading · success · … */}`,
|
|
122
|
+
body: 'Users and developers see the same complete lifecycle. Async empty, button failure, field validation — nothing left implicit off-screen.',
|
|
147
123
|
},
|
|
148
124
|
{
|
|
149
125
|
title: 'Scale with regions',
|
|
150
|
-
body: '
|
|
151
|
-
|
|
126
|
+
body: 'When complexity arrives, split orthogonal concerns — interaction, validation, submission — instead of one overloaded “error” boolean.',
|
|
127
|
+
},
|
|
128
|
+
] as const
|
|
129
|
+
|
|
130
|
+
export const layers = [
|
|
131
|
+
{
|
|
132
|
+
id: 'core',
|
|
133
|
+
title: 'Core',
|
|
134
|
+
description:
|
|
135
|
+
'The contract between machine and markup — scope, part, and state attributes your theme can target for years.',
|
|
136
|
+
highlights: ['North star & anatomy', 'PHILOSOPHY.md · STATES.md'],
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'react',
|
|
140
|
+
title: 'React',
|
|
141
|
+
description:
|
|
142
|
+
'Headless bindings where the DOM reflects the snapshot — compound parts, accessibility, and predictable structure.',
|
|
143
|
+
highlights: ['Snapshot-driven parts', 'useAeonMachine for app logic'],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'ui',
|
|
147
|
+
title: 'Styled UI',
|
|
148
|
+
description:
|
|
149
|
+
'An optional face with intentional tokens — ship quickly or treat it as a reference implementation for your system.',
|
|
150
|
+
highlights: ['Panda recipes on anatomy', 'Themes: your palette, your modes'],
|
|
152
151
|
},
|
|
153
152
|
] as const
|
|
154
153
|
|
|
@@ -156,192 +155,64 @@ export const playgroundBlocks = {
|
|
|
156
155
|
async: {
|
|
157
156
|
title: 'Async region',
|
|
158
157
|
description:
|
|
159
|
-
'
|
|
160
|
-
example: `import { Async, Button } from '@aeon-ui/ui'
|
|
161
|
-
|
|
162
|
-
<Async.Root>
|
|
163
|
-
<Async.Track />
|
|
164
|
-
<Async.Readout>Ready</Async.Readout>
|
|
165
|
-
<Async.Actions>
|
|
166
|
-
<Button.Root onClick={() => send({ type: 'FETCH' })}>
|
|
167
|
-
Fetch
|
|
168
|
-
</Button.Root>
|
|
169
|
-
</Async.Actions>
|
|
170
|
-
</Async.Root>`,
|
|
158
|
+
'Fetch flows as a named lifecycle — idle through empty — so “loading” is never the only story the UI tells.',
|
|
171
159
|
},
|
|
172
160
|
buttonLifecycle: {
|
|
173
161
|
title: 'Button lifecycle',
|
|
174
162
|
description:
|
|
175
|
-
'
|
|
176
|
-
example: `<Button.Root status="pending" aria-busy>
|
|
177
|
-
Submitting…
|
|
178
|
-
</Button.Root>`,
|
|
179
|
-
},
|
|
180
|
-
scroll: {
|
|
181
|
-
title: 'Scroll regions',
|
|
182
|
-
description:
|
|
183
|
-
'Theme-aware scrollbars on vertical, horizontal, and 2D overflow. Scroll position and axis show up on data-aeon-state (e.g. between-y, at-start-x).',
|
|
184
|
-
example: `import { Scroll } from '@aeon-ui/ui'
|
|
185
|
-
|
|
186
|
-
<Scroll.Root axis="both" maxH="md">
|
|
187
|
-
<Scroll.Viewport>
|
|
188
|
-
<Scroll.Content>{children}</Scroll.Content>
|
|
189
|
-
</Scroll.Viewport>
|
|
190
|
-
<Scroll.StateReadout />
|
|
191
|
-
</Scroll.Root>`,
|
|
163
|
+
'Submission is more than disabled: pending, success, and failure are states worth showing, not hidden side effects.',
|
|
192
164
|
},
|
|
193
165
|
field: {
|
|
194
|
-
title: 'Field
|
|
166
|
+
title: 'Field',
|
|
195
167
|
description:
|
|
196
|
-
'
|
|
197
|
-
example: `<Field.Root>
|
|
198
|
-
<Field.Label htmlFor="email">Email</Field.Label>
|
|
199
|
-
<Field.Control id="email" validate={isEmail} />
|
|
200
|
-
<Field.Message>Invalid email.</Field.Message>
|
|
201
|
-
</Field.Root>`,
|
|
202
|
-
},
|
|
203
|
-
buttons: {
|
|
204
|
-
title: 'Buttons',
|
|
205
|
-
description: 'Variants and sizes. Wrap groups in Button.Group for spacing.',
|
|
206
|
-
example: `<Button.Group>
|
|
207
|
-
<Button.Root>Primary</Button.Root>
|
|
208
|
-
<Button.Root variant="outline">Outline</Button.Root>
|
|
209
|
-
<Button.Root variant="ghost">Ghost</Button.Root>
|
|
210
|
-
</Button.Group>`,
|
|
168
|
+
'Interaction, validation, and submission as parallel regions — complexity without a single tangled error flag.',
|
|
211
169
|
},
|
|
212
|
-
|
|
213
|
-
title: '
|
|
214
|
-
description:
|
|
215
|
-
|
|
216
|
-
<Switch.Control><Switch.Thumb /></Switch.Control>
|
|
217
|
-
<Switch.Label>Notifications</Switch.Label>
|
|
218
|
-
</Switch.Root>`,
|
|
170
|
+
controls: {
|
|
171
|
+
title: 'Inputs & buttons',
|
|
172
|
+
description:
|
|
173
|
+
'Toggles, radio group, slider, and pin input — explicit states on every control surface.',
|
|
219
174
|
},
|
|
220
175
|
dialog: {
|
|
221
176
|
title: 'Dialog',
|
|
222
|
-
description: 'open and closed are explicit. Escape and backdrop clicks are defined transitions.',
|
|
223
|
-
example: `<Dialog.Root>
|
|
224
|
-
<Dialog.Trigger>Open</Dialog.Trigger>
|
|
225
|
-
<Dialog.Content>
|
|
226
|
-
<Dialog.Title>Title</Dialog.Title>
|
|
227
|
-
<Dialog.CloseTrigger>Close</Dialog.CloseTrigger>
|
|
228
|
-
</Dialog.Content>
|
|
229
|
-
</Dialog.Root>`,
|
|
230
|
-
},
|
|
231
|
-
combobox: {
|
|
232
|
-
title: 'Combobox',
|
|
233
177
|
description:
|
|
234
|
-
'
|
|
235
|
-
example: `import { Combobox } from '@aeon-ui/ui'
|
|
236
|
-
|
|
237
|
-
<Combobox.Root value={value} onValueChange={setValue}>
|
|
238
|
-
<Combobox.Input placeholder="Search…" aria-label="Country" />
|
|
239
|
-
<Combobox.Content>
|
|
240
|
-
<Combobox.Item value="us">United States</Combobox.Item>
|
|
241
|
-
<Combobox.Empty />
|
|
242
|
-
</Combobox.Content>
|
|
243
|
-
</Combobox.Root>`,
|
|
178
|
+
'Open and closed are first-class. Dismissal paths are transitions, not surprises.',
|
|
244
179
|
},
|
|
245
180
|
overlay: {
|
|
246
|
-
title: '
|
|
181
|
+
title: 'Overlays',
|
|
247
182
|
description:
|
|
248
|
-
'Floating surfaces
|
|
249
|
-
example: `import { Menu, Tooltip, Toast, useToast } from '@aeon-ui/ui'
|
|
250
|
-
|
|
251
|
-
// App root:
|
|
252
|
-
<Toast.Provider>
|
|
253
|
-
<App />
|
|
254
|
-
<Toast.Viewport placement="bottom-end" />
|
|
255
|
-
</Toast.Provider>
|
|
256
|
-
|
|
257
|
-
// Anywhere inside the provider:
|
|
258
|
-
const { publish } = useToast()
|
|
259
|
-
publish({ title: 'Saved', description: 'Changes applied.', durationMs: 4000 })`,
|
|
260
|
-
},
|
|
261
|
-
tabsAccordion: {
|
|
262
|
-
title: 'Tabs & accordion',
|
|
263
|
-
description: 'One active tab. Multiple accordion items can be open — each item has its own state.',
|
|
264
|
-
example: `<Tabs.Root defaultValue="a">
|
|
265
|
-
<Tabs.List>
|
|
266
|
-
<Tabs.Trigger value="a">Tab A</Tabs.Trigger>
|
|
267
|
-
</Tabs.List>
|
|
268
|
-
<Tabs.Content value="a">…</Tabs.Content>
|
|
269
|
-
</Tabs.Root>`,
|
|
270
|
-
},
|
|
271
|
-
} as const
|
|
272
|
-
|
|
273
|
-
export const tabsExamples = {
|
|
274
|
-
philosophy: {
|
|
275
|
-
label: 'Idea',
|
|
276
|
-
content: 'Define states in a chart. UI only shows conditions the chart allows.',
|
|
277
|
-
example: 'UI = f(snapshot)',
|
|
278
|
-
},
|
|
279
|
-
machines: {
|
|
280
|
-
label: 'Machines',
|
|
281
|
-
content: 'XState runs in the browser. Same model you sketch on a whiteboard.',
|
|
282
|
-
example: "import { createAsyncMachine } from '@aeon-ui/primitives'",
|
|
283
|
-
},
|
|
284
|
-
anatomy: {
|
|
285
|
-
label: 'Anatomy',
|
|
286
|
-
content: 'Style with data attributes — stable across refactors.',
|
|
287
|
-
example: '[data-aeon-scope="button"][data-aeon-state="pending"]',
|
|
288
|
-
},
|
|
289
|
-
} as const
|
|
290
|
-
|
|
291
|
-
export const accordionExamples = {
|
|
292
|
-
what: {
|
|
293
|
-
trigger: 'What is Aeon UI?',
|
|
294
|
-
content: 'Headless statechart components for React, plus an optional styled layer.',
|
|
295
|
-
example: 'git clone https://github.com/GenericCPU/aeon-ui.git',
|
|
296
|
-
},
|
|
297
|
-
why: {
|
|
298
|
-
trigger: 'Why statecharts?',
|
|
299
|
-
content: 'So async, forms, and modals stay explicit as products grow.',
|
|
300
|
-
example: 'See STATES.md in the repo for states traditional UI skips.',
|
|
183
|
+
'Floating surfaces with the same open/closed discipline — anchored behavior you can reason about.',
|
|
301
184
|
},
|
|
302
185
|
} as const
|
|
303
186
|
|
|
304
187
|
export const faq = [
|
|
305
188
|
{
|
|
306
|
-
id: '
|
|
307
|
-
question: 'What
|
|
189
|
+
id: 'what',
|
|
190
|
+
question: 'What is Aeon UI?',
|
|
308
191
|
answer:
|
|
309
|
-
'
|
|
310
|
-
example: `const status = snapshot.value // "loading"
|
|
311
|
-
<Async.Readout>{copy[status]}</Async.Readout>`,
|
|
192
|
+
'Infrastructure for the web where behavior is modeled explicitly and the interface expresses that model completely. Headless primitives, React bindings, and an optional styled layer — published as aeon-ui-engine.',
|
|
312
193
|
},
|
|
313
194
|
{
|
|
314
|
-
id: '
|
|
315
|
-
question: '
|
|
195
|
+
id: 'why',
|
|
196
|
+
question: 'Why statecharts?',
|
|
316
197
|
answer:
|
|
317
|
-
'
|
|
318
|
-
example: `<Checkbox.Root>
|
|
319
|
-
<Checkbox.Control />
|
|
320
|
-
<Checkbox.Label>Email me</Checkbox.Label>
|
|
321
|
-
</Checkbox.Root>`,
|
|
198
|
+
'Because products outgrow boolean soup. Statecharts name conditions, separate regions, and stay diagrammable as requirements grow — the thesis case for modern web UI is documented in our primary reference (linked in Foundation).',
|
|
322
199
|
},
|
|
323
200
|
{
|
|
324
|
-
id: '
|
|
325
|
-
question: '
|
|
201
|
+
id: 'totality',
|
|
202
|
+
question: 'What does “UI = f(state)” mean?',
|
|
326
203
|
answer:
|
|
327
|
-
'
|
|
328
|
-
example: `import '@aeon-ui/panda/styles.css'
|
|
329
|
-
import { applyAeonTheme } from '@aeon-ui/panda/themes'
|
|
330
|
-
|
|
331
|
-
applyAeonTheme('signal', 'dark')`,
|
|
204
|
+
'The machine snapshot picks what renders. If the chart includes loading, you build loading UI — and you do not imply success, failure, or empty unless the chart allows them.',
|
|
332
205
|
},
|
|
333
206
|
{
|
|
334
207
|
id: 'headless',
|
|
335
|
-
question: '
|
|
208
|
+
question: 'Do I have to use your styles?',
|
|
336
209
|
answer:
|
|
337
|
-
'
|
|
338
|
-
example: `import { Dialog } from '@aeon-ui/react'`,
|
|
210
|
+
'No. The headless layer is the point: same anatomy and states with your CSS. The styled package is a convenience, not a cage.',
|
|
339
211
|
},
|
|
340
212
|
{
|
|
341
|
-
id: '
|
|
342
|
-
question: '
|
|
343
|
-
answer:
|
|
344
|
-
|
|
345
|
-
example: 'pnpm dev → localhost:5173',
|
|
213
|
+
id: 'install',
|
|
214
|
+
question: 'How do I adopt it?',
|
|
215
|
+
answer: `Install ${npmPackage} from npm. Core, React, and styled UI ship together; import the layer that matches where you are in the stack.`,
|
|
216
|
+
example: npmInstallCommand,
|
|
346
217
|
},
|
|
347
218
|
] as const
|
package/docs/MACHINES.md
CHANGED
|
@@ -21,8 +21,13 @@
|
|
|
21
21
|
| **Toast** (item) | `toastMachine` | `useAeonMachine` | visible / hidden |
|
|
22
22
|
| **Select** | `popoverMachine` | `useAeonMachine` | `open` / `closed`; highlight in React |
|
|
23
23
|
| **Combobox** | — | **`useState`** (open, query, highlight) | `open` / `closed` + highlight attrs |
|
|
24
|
-
| **Tooltip** | — | **`useState(open)`** | `open` / `closed
|
|
24
|
+
| **Tooltip** | — | **`useState(open)`** | `open` / `closed`; touch tap toggle |
|
|
25
25
|
| **Scroll** | — | **`getScrollSnapshot()`** | Derived overflow/edge tokens |
|
|
26
|
+
| **Separator** | — | **static orientation prop** | horizontal / vertical |
|
|
27
|
+
| **Progress** | — | **props** (`value`, `indeterminate`) | idle / loading / complete |
|
|
28
|
+
| **RadioGroup** | `tabsMachine` | `useAeonMachine` | selected value |
|
|
29
|
+
| **Slider** | `sliderMachine` | `useAeonMachine` | idle / dragging + numeric value |
|
|
30
|
+
| **PinInput** | — | **`useState`** | idle / complete |
|
|
26
31
|
|
|
27
32
|
---
|
|
28
33
|
|
package/package.json
CHANGED
|
@@ -142,3 +142,26 @@ export const toastAnatomy = anatomy('toast', [
|
|
|
142
142
|
|
|
143
143
|
/** Profile image — loaded / loading / error on root `data-aeon-state`. */
|
|
144
144
|
export const avatarAnatomy = anatomy('avatar', ['root', 'image', 'fallback'] as const)
|
|
145
|
+
|
|
146
|
+
export const separatorAnatomy = anatomy('separator', ['root'] as const)
|
|
147
|
+
|
|
148
|
+
/** Progress bar — idle / loading / complete on root `data-aeon-state`. */
|
|
149
|
+
export const progressAnatomy = anatomy('progress', ['root', 'track', 'range', 'label'] as const)
|
|
150
|
+
|
|
151
|
+
export const radioGroupAnatomy = anatomy('radioGroup', [
|
|
152
|
+
'root',
|
|
153
|
+
'item',
|
|
154
|
+
'itemControl',
|
|
155
|
+
'itemIndicator',
|
|
156
|
+
'itemLabel',
|
|
157
|
+
] as const)
|
|
158
|
+
|
|
159
|
+
export const sliderAnatomy = anatomy('slider', [
|
|
160
|
+
'root',
|
|
161
|
+
'track',
|
|
162
|
+
'range',
|
|
163
|
+
'thumb',
|
|
164
|
+
'valueText',
|
|
165
|
+
] as const)
|
|
166
|
+
|
|
167
|
+
export const pinInputAnatomy = anatomy('pinInput', ['root', 'input'] as const)
|
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
dialogRecipe,
|
|
14
14
|
switchRecipe,
|
|
15
15
|
tabsRecipe,
|
|
16
|
+
separatorRecipe,
|
|
17
|
+
progressRecipe,
|
|
18
|
+
radioGroupRecipe,
|
|
19
|
+
sliderRecipe,
|
|
20
|
+
pinInputRecipe,
|
|
16
21
|
menuRecipe,
|
|
17
22
|
popoverRecipe,
|
|
18
23
|
tooltipRecipe,
|
|
@@ -42,7 +47,7 @@ export default defineConfig({
|
|
|
42
47
|
conditions: {
|
|
43
48
|
extend: {
|
|
44
49
|
aeonSelected: '&[data-aeon-state=selected]',
|
|
45
|
-
aeonChecked: '&[data-aeon-state
|
|
50
|
+
aeonChecked: '&[data-aeon-state~="checked"]',
|
|
46
51
|
aeonOpen: '&[data-aeon-state=open]',
|
|
47
52
|
aeonClosed: '&[data-aeon-state=closed]',
|
|
48
53
|
aeonPending: '&[data-aeon-state=pending]',
|
|
@@ -64,6 +69,11 @@ export default defineConfig({
|
|
|
64
69
|
aeonAccordion: accordionRecipe,
|
|
65
70
|
aeonDialog: dialogRecipe,
|
|
66
71
|
aeonTabs: tabsRecipe,
|
|
72
|
+
aeonSeparator: separatorRecipe,
|
|
73
|
+
aeonProgress: progressRecipe,
|
|
74
|
+
aeonRadioGroup: radioGroupRecipe,
|
|
75
|
+
aeonSlider: sliderRecipe,
|
|
76
|
+
aeonPinInput: pinInputRecipe,
|
|
67
77
|
aeonSwitch: switchRecipe,
|
|
68
78
|
aeonCheckbox: checkboxRecipe,
|
|
69
79
|
aeonAsync: asyncRegionRecipe,
|
|
@@ -225,5 +235,13 @@ export default defineConfig({
|
|
|
225
235
|
opacity: '0.7',
|
|
226
236
|
pointerEvents: 'none',
|
|
227
237
|
},
|
|
238
|
+
'@keyframes aeonProgressIndeterminate': {
|
|
239
|
+
'0%': { transform: 'translateX(-100%)' },
|
|
240
|
+
'100%': { transform: 'translateX(250%)' },
|
|
241
|
+
},
|
|
242
|
+
'[data-aeon-scope="radioGroup"] [data-aeon-part="item"]': {
|
|
243
|
+
display: 'inline-flex',
|
|
244
|
+
alignItems: 'center',
|
|
245
|
+
},
|
|
228
246
|
},
|
|
229
247
|
})
|
|
@@ -59,28 +59,28 @@ export const buttonRecipe = defineRecipe({
|
|
|
59
59
|
borderRadius: 'sm',
|
|
60
60
|
py: 'controlYSm',
|
|
61
61
|
px: 'controlXSm',
|
|
62
|
-
minH: '
|
|
62
|
+
minH: 'controlMinHXs',
|
|
63
63
|
},
|
|
64
64
|
sm: {
|
|
65
65
|
fontSize: '0.8125rem',
|
|
66
66
|
borderRadius: 'sm',
|
|
67
67
|
py: 'controlY',
|
|
68
68
|
px: 'controlX',
|
|
69
|
-
minH: '
|
|
69
|
+
minH: 'controlMinHSm',
|
|
70
70
|
},
|
|
71
71
|
md: {
|
|
72
72
|
fontSize: '0.875rem',
|
|
73
73
|
borderRadius: 'md',
|
|
74
74
|
py: '0.4375rem',
|
|
75
75
|
px: '0.875rem',
|
|
76
|
-
minH: '
|
|
76
|
+
minH: 'controlMinHMd',
|
|
77
77
|
},
|
|
78
78
|
lg: {
|
|
79
79
|
fontSize: '0.9375rem',
|
|
80
80
|
borderRadius: 'md',
|
|
81
81
|
py: 'insetYSm',
|
|
82
82
|
px: 'insetXSm',
|
|
83
|
-
minH: '
|
|
83
|
+
minH: 'controlMinHLg',
|
|
84
84
|
},
|
|
85
85
|
},
|
|
86
86
|
},
|
|
@@ -11,4 +11,9 @@ export { checkboxRecipe } from './checkbox.js'
|
|
|
11
11
|
export { dialogRecipe } from './dialog.js'
|
|
12
12
|
export { switchRecipe } from './switch.js'
|
|
13
13
|
export { tabsRecipe } from './tabs.js'
|
|
14
|
+
export { separatorRecipe } from './separator.js'
|
|
15
|
+
export { progressRecipe } from './progress.js'
|
|
16
|
+
export { radioGroupRecipe } from './radio-group.js'
|
|
17
|
+
export { sliderRecipe } from './slider.js'
|
|
18
|
+
export { pinInputRecipe } from './pin-input.js'
|
|
14
19
|
export { menuRecipe, popoverRecipe, toastRecipe, tooltipRecipe } from './floating.js'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineSlotRecipe } from '@pandacss/dev'
|
|
2
|
+
|
|
3
|
+
export const pinInputRecipe = defineSlotRecipe({
|
|
4
|
+
className: 'aeonPinInput',
|
|
5
|
+
slots: ['root', 'input'],
|
|
6
|
+
base: {
|
|
7
|
+
root: {
|
|
8
|
+
display: 'inline-flex',
|
|
9
|
+
gap: 'gapSm',
|
|
10
|
+
},
|
|
11
|
+
input: {
|
|
12
|
+
width: '2.5rem',
|
|
13
|
+
height: '2.75rem',
|
|
14
|
+
textAlign: 'center',
|
|
15
|
+
fontSize: 'lg',
|
|
16
|
+
fontFamily: 'ui',
|
|
17
|
+
fontWeight: 'semibold',
|
|
18
|
+
borderRadius: 'md',
|
|
19
|
+
borderWidth: '1px',
|
|
20
|
+
borderStyle: 'solid',
|
|
21
|
+
borderColor: 'border',
|
|
22
|
+
bg: 'surface',
|
|
23
|
+
color: 'fg',
|
|
24
|
+
outline: 'none',
|
|
25
|
+
_focusVisible: { boxShadow: 'focusRing', borderColor: 'accent' },
|
|
26
|
+
'&[data-aeon-state=filled]': { borderColor: 'accent' },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
})
|