aeon-ui-engine 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -0
- package/CONSUMER.md +86 -0
- package/INTEGRATION.md +2 -0
- package/README.md +15 -4
- package/package.json +17 -1
- package/packages/core/dist/index.d.ts +257 -0
- package/packages/core/dist/index.js +378 -0
- package/packages/core/node_modules/.bin/esbuild +21 -0
- package/packages/core/node_modules/.bin/jiti +21 -0
- package/packages/core/node_modules/.bin/tsc +21 -0
- package/packages/core/node_modules/.bin/tsserver +21 -0
- package/packages/core/node_modules/.bin/tsup +21 -0
- package/packages/core/node_modules/.bin/tsup-node +21 -0
- package/packages/core/node_modules/.bin/tsx +21 -0
- package/packages/core/node_modules/.bin/vite +21 -0
- package/packages/core/node_modules/.bin/vitest +21 -0
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/geometry/dist/index.d.ts +50 -0
- package/packages/geometry/dist/index.js +88 -0
- package/packages/geometry/node_modules/.bin/esbuild +21 -0
- package/packages/geometry/node_modules/.bin/jiti +21 -0
- package/packages/geometry/node_modules/.bin/tsc +21 -0
- package/packages/geometry/node_modules/.bin/tsserver +21 -0
- package/packages/geometry/node_modules/.bin/tsup +21 -0
- package/packages/geometry/node_modules/.bin/tsup-node +21 -0
- package/packages/geometry/node_modules/.bin/tsx +21 -0
- package/packages/panda/src/recipes/button.ts +4 -4
- package/packages/panda/src/recipes/combobox.ts +1 -1
- package/packages/panda/src/recipes/select.ts +1 -1
- package/packages/panda/src/theme/tokens.ts +5 -0
- package/packages/panda/styled-system/styles.css +10 -6
- package/packages/panda/styled-system/tokens/index.mjs +32 -0
- package/packages/panda/styled-system/tokens/tokens.d.ts +2 -2
- package/packages/primitives/dist/chunk-5S4DIRL4.js +42 -0
- package/packages/primitives/dist/chunk-H4KOEPYC.js +52 -0
- package/packages/primitives/dist/chunk-HONROWUG.js +41 -0
- package/packages/primitives/dist/chunk-J6JHKMVM.js +90 -0
- package/packages/primitives/dist/chunk-JBAG5E6W.js +46 -0
- package/packages/primitives/dist/chunk-LVNCD5F2.js +43 -0
- package/packages/primitives/dist/chunk-PB3LFHXU.js +72 -0
- package/packages/primitives/dist/chunk-SZV6KGXN.js +49 -0
- package/packages/primitives/dist/chunk-WVICCOQH.js +56 -0
- package/packages/primitives/dist/chunk-ZMGRX64G.js +74 -0
- package/packages/primitives/dist/index.d.ts +11 -0
- package/packages/primitives/dist/index.js +60 -0
- package/packages/primitives/dist/machines/accordion.d.ts +34 -0
- package/packages/primitives/dist/machines/accordion.js +6 -0
- package/packages/primitives/dist/machines/async.d.ts +111 -0
- package/packages/primitives/dist/machines/async.js +12 -0
- package/packages/primitives/dist/machines/button.d.ts +45 -0
- package/packages/primitives/dist/machines/button.js +8 -0
- package/packages/primitives/dist/machines/dialog.d.ts +57 -0
- package/packages/primitives/dist/machines/dialog.js +6 -0
- package/packages/primitives/dist/machines/field.d.ts +72 -0
- package/packages/primitives/dist/machines/field.js +12 -0
- package/packages/primitives/dist/machines/popover.d.ts +54 -0
- package/packages/primitives/dist/machines/popover.js +6 -0
- package/packages/primitives/dist/machines/slider.d.ts +71 -0
- package/packages/primitives/dist/machines/slider.js +10 -0
- package/packages/primitives/dist/machines/tabs.d.ts +39 -0
- package/packages/primitives/dist/machines/tabs.js +6 -0
- package/packages/primitives/dist/machines/toast.d.ts +31 -0
- package/packages/primitives/dist/machines/toast.js +6 -0
- package/packages/primitives/dist/machines/toggle.d.ts +54 -0
- package/packages/primitives/dist/machines/toggle.js +6 -0
- package/packages/primitives/node_modules/.bin/jiti +21 -0
- package/packages/primitives/node_modules/.bin/tsc +21 -0
- package/packages/primitives/node_modules/.bin/tsserver +21 -0
- package/packages/primitives/node_modules/.bin/tsup +21 -0
- package/packages/primitives/node_modules/.bin/tsup-node +21 -0
- package/packages/primitives/node_modules/.bin/tsx +21 -0
- package/packages/primitives/node_modules/.bin/vitest +21 -0
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/react/dist/index.d.ts +564 -0
- package/packages/react/dist/index.js +3461 -0
- package/packages/react/node_modules/.bin/jiti +21 -0
- package/packages/react/node_modules/.bin/tsc +21 -0
- package/packages/react/node_modules/.bin/tsserver +21 -0
- package/packages/react/node_modules/.bin/tsup +21 -0
- package/packages/react/node_modules/.bin/tsup-node +21 -0
- package/packages/react/node_modules/.bin/tsx +21 -0
- package/packages/react/node_modules/.bin/vitest +21 -0
- package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/react-scene/dist/index.d.ts +121 -0
- package/packages/react-scene/dist/index.js +430 -0
- package/packages/react-scene/node_modules/.bin/jiti +21 -0
- package/packages/react-scene/node_modules/.bin/tsc +21 -0
- package/packages/react-scene/node_modules/.bin/tsserver +21 -0
- package/packages/react-scene/node_modules/.bin/tsup +21 -0
- package/packages/react-scene/node_modules/.bin/tsup-node +21 -0
- package/packages/react-scene/node_modules/.bin/tsx +21 -0
- package/packages/scene/dist/index.d.ts +46 -0
- package/packages/scene/dist/index.js +54 -0
- package/packages/scene/node_modules/.bin/jiti +21 -0
- package/packages/scene/node_modules/.bin/tsc +21 -0
- package/packages/scene/node_modules/.bin/tsserver +21 -0
- package/packages/scene/node_modules/.bin/tsup +21 -0
- package/packages/scene/node_modules/.bin/tsup-node +21 -0
- package/packages/scene/node_modules/.bin/tsx +21 -0
- package/packages/signal/dist/index.d.ts +54 -0
- package/packages/signal/dist/index.js +169 -0
- package/packages/signal/node_modules/.bin/jiti +21 -0
- package/packages/signal/node_modules/.bin/tsc +21 -0
- package/packages/signal/node_modules/.bin/tsserver +21 -0
- package/packages/signal/node_modules/.bin/tsup +21 -0
- package/packages/signal/node_modules/.bin/tsup-node +21 -0
- package/packages/signal/node_modules/.bin/tsx +21 -0
- package/packages/site/dist/index.d.ts +31 -0
- package/packages/site/dist/index.js +62 -0
- package/packages/site/node_modules/.bin/jiti +21 -0
- package/packages/site/node_modules/.bin/tsc +21 -0
- package/packages/site/node_modules/.bin/tsserver +21 -0
- package/packages/site/node_modules/.bin/tsup +21 -0
- package/packages/site/node_modules/.bin/tsup-node +21 -0
- package/packages/site/node_modules/.bin/tsx +21 -0
- package/packages/solid/dist/index.d.ts +30 -0
- package/packages/solid/dist/index.js +174 -0
- package/packages/solid/node_modules/.bin/jiti +21 -0
- package/packages/solid/node_modules/.bin/tsc +21 -0
- package/packages/solid/node_modules/.bin/tsserver +21 -0
- package/packages/solid/node_modules/.bin/tsup +21 -0
- package/packages/solid/node_modules/.bin/tsup-node +21 -0
- package/packages/solid/node_modules/.bin/tsx +21 -0
- package/packages/surface/dist/index.d.ts +8 -0
- package/packages/surface/dist/index.js +14 -0
- package/packages/surface/node_modules/.bin/jiti +21 -0
- package/packages/surface/node_modules/.bin/tsc +21 -0
- package/packages/surface/node_modules/.bin/tsserver +21 -0
- package/packages/surface/node_modules/.bin/tsup +21 -0
- package/packages/surface/node_modules/.bin/tsup-node +21 -0
- package/packages/surface/node_modules/.bin/tsx +21 -0
- package/packages/svelte/dist/index.d.ts +12 -0
- package/packages/svelte/dist/index.js +15 -0
- package/packages/svelte/node_modules/.bin/acorn +21 -0
- package/packages/svelte/node_modules/.bin/jiti +21 -0
- package/packages/svelte/node_modules/.bin/svelte-check +21 -0
- package/packages/svelte/node_modules/.bin/tsc +21 -0
- package/packages/svelte/node_modules/.bin/tsserver +21 -0
- package/packages/svelte/node_modules/.bin/tsup +21 -0
- package/packages/svelte/node_modules/.bin/tsup-node +21 -0
- package/packages/svelte/node_modules/.bin/tsx +21 -0
- package/packages/svelte/node_modules/.bin/vite +21 -0
- package/packages/ui/dist/index.d.ts +200 -0
- package/packages/ui/dist/index.js +429 -0
- package/packages/ui/node_modules/.bin/jiti +21 -0
- package/packages/ui/node_modules/.bin/tsc +21 -0
- package/packages/ui/node_modules/.bin/tsserver +21 -0
- package/packages/ui/node_modules/.bin/tsup +21 -0
- package/packages/ui/node_modules/.bin/tsup-node +21 -0
- package/packages/ui/node_modules/.bin/tsx +21 -0
- package/packages/ui/src/select.tsx +3 -4
- package/packages/vue/dist/index.d.ts +71 -0
- package/packages/vue/dist/index.js +193 -0
- package/packages/vue/node_modules/.bin/jiti +21 -0
- package/packages/vue/node_modules/.bin/tsc +21 -0
- package/packages/vue/node_modules/.bin/tsserver +21 -0
- package/packages/vue/node_modules/.bin/tsup +21 -0
- package/packages/vue/node_modules/.bin/tsup-node +21 -0
- package/packages/vue/node_modules/.bin/tsx +21 -0
- package/scripts/vite-aliases.mjs +125 -0
- package/.changeset/README.md +0 -23
- package/.changeset/combobox-and-changesets.md +0 -9
- package/.changeset/config.json +0 -30
- package/.changeset/framework-adapters.md +0 -12
- package/.changeset/scene-3d.md +0 -6
- package/.changeset/scene-plane.md +0 -8
- package/.changeset/site-og.md +0 -5
- package/.cursor/rules/aeon-ui.mdc +0 -22
- package/ACCESSIBILITY.md +0 -32
- package/AGENTS.md +0 -226
- package/COMPONENTS.md +0 -41
- package/FRAMEWORKS.md +0 -57
- package/PHILOSOPHY.md +0 -71
- package/PUBLISHING.md +0 -85
- package/SCENE.md +0 -113
- package/STATES.md +0 -243
- package/apps/demo/index.html +0 -31
- package/apps/demo/og.config.mjs +0 -20
- package/apps/demo/package.json +0 -41
- package/apps/demo/public/favicon.svg +0 -16
- package/apps/demo/public/og-aeon-ui-engine.png +0 -0
- package/apps/demo/public/og.svg +0 -35
- package/apps/demo/scripts/generate-og-png.mjs +0 -62
- package/apps/demo/src/App.tsx +0 -31
- package/apps/demo/src/aeonDiagrams.ts +0 -42
- package/apps/demo/src/app.css +0 -16
- package/apps/demo/src/components/ActionCluster.tsx +0 -20
- package/apps/demo/src/components/AsyncMachineDemo.tsx +0 -97
- package/apps/demo/src/components/ButtonLifecycleDemo.tsx +0 -63
- package/apps/demo/src/components/CodeExample.tsx +0 -7
- package/apps/demo/src/components/ComboboxDemo.tsx +0 -30
- package/apps/demo/src/components/DiagramsSection.tsx +0 -37
- package/apps/demo/src/components/FieldStateDemo.tsx +0 -51
- package/apps/demo/src/components/GridMesh.tsx +0 -8
- package/apps/demo/src/components/HeroInstall.tsx +0 -13
- package/apps/demo/src/components/LandingFaq.tsx +0 -29
- package/apps/demo/src/components/LandingFooter.tsx +0 -30
- package/apps/demo/src/components/LandingHeader.tsx +0 -62
- package/apps/demo/src/components/LandingHero.tsx +0 -55
- package/apps/demo/src/components/LayersSection.tsx +0 -47
- package/apps/demo/src/components/MermaidDiagram.tsx +0 -86
- package/apps/demo/src/components/PhilosophySection.tsx +0 -31
- package/apps/demo/src/components/PlaygroundBlock.tsx +0 -18
- package/apps/demo/src/components/PlaygroundControlsDemo.tsx +0 -63
- package/apps/demo/src/components/PlaygroundSection.tsx +0 -120
- package/apps/demo/src/components/ScrollRegionDemo.tsx +0 -74
- package/apps/demo/src/components/ThemeSwitcher.tsx +0 -79
- package/apps/demo/src/demo-layout.css +0 -293
- package/apps/demo/src/landing.css +0 -1151
- package/apps/demo/src/main.tsx +0 -14
- package/apps/demo/src/nav.ts +0 -9
- package/apps/demo/src/site.ts +0 -218
- package/apps/demo/tsconfig.json +0 -17
- package/apps/demo/vite-plugins/og-meta.ts +0 -30
- package/apps/demo/vite.config.ts +0 -69
- package/docs/API_PATTERNS.md +0 -234
- package/docs/ARCHITECTURE.md +0 -125
- package/docs/COMPONENT_CHECKLIST.md +0 -103
- package/docs/FILE_MAP.md +0 -122
- package/docs/GLOSSARY.md +0 -125
- package/docs/INDEX.md +0 -53
- package/docs/LAYOUT_COORDINATES.md +0 -178
- package/docs/MACHINES.md +0 -171
- package/docs/MIGRATION_FROM_ARK.md +0 -295
- package/docs/VITE_CONSUMER.md +0 -114
- package/pnpm-workspace.yaml +0 -3
- package/vercel.json +0 -12
package/STATES.md
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
# States traditional UI forgets — and how Aeon models them
|
|
2
|
-
|
|
3
|
-
**Agents:** start at [AGENTS.md](./AGENTS.md) and [docs/MACHINES.md](./docs/MACHINES.md) for which components use machines vs props.
|
|
4
|
-
|
|
5
|
-
Aeon’s north star: **[state is defined first; UI expresses the totality of that definition](./PHILOSOPHY.md)** (`UI = f(snapshot)`).
|
|
6
|
-
|
|
7
|
-
Traditional interfaces collapse rich behavior into a few booleans (`disabled`, `loading`, `open`) or hide conditions entirely. This document catalogs common gaps, how Aeon addresses them today, and how to extend the library.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## The attribute contract
|
|
12
|
-
|
|
13
|
-
Every styled surface should be targetable consistently:
|
|
14
|
-
|
|
15
|
-
| Attribute | Purpose |
|
|
16
|
-
|-----------|---------|
|
|
17
|
-
| `data-aeon-scope` | Component family (`button`, `async`, `field`, …) |
|
|
18
|
-
| `data-aeon-part` | Compound part (`root`, `track`, `control`, …) |
|
|
19
|
-
| `data-aeon-state` | Active condition(s) from the machine snapshot |
|
|
20
|
-
|
|
21
|
-
Nested parallel regions flatten with `stateToAttr` from `@aeon-ui/core`:
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import { stateToAttr } from '@aeon-ui/core'
|
|
25
|
-
// e.g. "interaction:dirty validation:invalid submission:idle"
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
Panda recipes and `globalCss` in `@aeon-ui/panda` key off these attributes — not ad hoc class names in product code.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 1. Async data regions
|
|
33
|
-
|
|
34
|
-
### What traditional UI skips
|
|
35
|
-
|
|
36
|
-
| Condition | Typical mistake |
|
|
37
|
-
|-----------|-----------------|
|
|
38
|
-
| **Idle** (never fetched) | Looks like “empty” |
|
|
39
|
-
| **Loading** | Spinner with no named state |
|
|
40
|
-
| **Success** | Only happy path styled |
|
|
41
|
-
| **Failure** | Toast only, easy to miss |
|
|
42
|
-
| **Empty** (success, zero results) | Same UI as idle |
|
|
43
|
-
| **Stale** (success + background refresh) | Silent flicker |
|
|
44
|
-
|
|
45
|
-
### Aeon model
|
|
46
|
-
|
|
47
|
-
Machine: `createAsyncMachine()` in `@aeon-ui/primitives`
|
|
48
|
-
States: `idle` · `loading` · `success` · `failure` · `empty`
|
|
49
|
-
Context flag: `stale` on success → readout uses `data-aeon-state="success stale"`
|
|
50
|
-
|
|
51
|
-
Constants: `ASYNC_LIFECYCLE_STATES`, `isEmptyAsyncData()`
|
|
52
|
-
|
|
53
|
-
### UI kit (totality pattern)
|
|
54
|
-
|
|
55
|
-
```tsx
|
|
56
|
-
import { Async, Button } from '@aeon-ui/ui'
|
|
57
|
-
|
|
58
|
-
<Async.Root>
|
|
59
|
-
<Async.Track /> {/* every lifecycle state visible on the rail */}
|
|
60
|
-
<Async.Readout>…</Async.Readout>
|
|
61
|
-
<Async.Actions>
|
|
62
|
-
<Button.Root>Fetch</Button.Root>
|
|
63
|
-
</Async.Actions>
|
|
64
|
-
</Async.Root>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Headless: `@aeon-ui/react` → `Async`, `useAsyncContext`
|
|
68
|
-
Anatomy: `asyncAnatomy` (`track`, `segment`, `readout`, `actions`, …)
|
|
69
|
-
Styles: `aeonAsync` slot recipe
|
|
70
|
-
|
|
71
|
-
**Rule:** If the chart lists five states, the track lists five segments — one active, none implicit.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## 2. Action button lifecycle
|
|
76
|
-
|
|
77
|
-
### What traditional UI skips
|
|
78
|
-
|
|
79
|
-
`disabled` is used for: in-flight submit, success, permission denied, validation blocked, and “busy”.
|
|
80
|
-
|
|
81
|
-
Missing named states: **pending**, **success**, **failure**, distinct from **disabled**.
|
|
82
|
-
|
|
83
|
-
### Aeon model
|
|
84
|
-
|
|
85
|
-
Machine: `buttonLifecycleMachine`
|
|
86
|
-
States: `idle` · `pending` · `success` · `failure` · `disabled`
|
|
87
|
-
|
|
88
|
-
### Component API
|
|
89
|
-
|
|
90
|
-
```tsx
|
|
91
|
-
<Button.Root status="pending" aria-busy>
|
|
92
|
-
Submitting…
|
|
93
|
-
</Button.Root>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
`status` maps to `data-aeon-state`. Global styles adjust cursor/opacity/border per state.
|
|
97
|
-
|
|
98
|
-
For full chart-driven flows, run `buttonLifecycleMachine` with `useAeonMachine` and drive `status` from `snapshot.value` (see playground **Button lifecycle**).
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## 3. Form field (orthogonal regions)
|
|
103
|
-
|
|
104
|
-
### What traditional UI skips
|
|
105
|
-
|
|
106
|
-
Interaction, validation, and submission are merged into one visual “error state.”
|
|
107
|
-
|
|
108
|
-
### Aeon model
|
|
109
|
-
|
|
110
|
-
Machine: `fieldMachine` (parallel)
|
|
111
|
-
|
|
112
|
-
| Region | States |
|
|
113
|
-
|--------|--------|
|
|
114
|
-
| `interaction` | `pristine` · `dirty` |
|
|
115
|
-
| `validation` | `valid` · `invalid` |
|
|
116
|
-
| `submission` | `idle` · `pending` |
|
|
117
|
-
|
|
118
|
-
Root: `data-aeon-state="interaction:dirty validation:invalid submission:idle"`
|
|
119
|
-
|
|
120
|
-
### Component API
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
import { Field } from '@aeon-ui/ui'
|
|
124
|
-
|
|
125
|
-
<Field.Root>
|
|
126
|
-
<Field.Label htmlFor="email">Email</Field.Label>
|
|
127
|
-
<Field.Control id="email" validate={(v) => isValidEmail(v)} />
|
|
128
|
-
<Field.Message>Enter a valid email.</Field.Message>
|
|
129
|
-
<Field.Hint>Shown when pristine.</Field.Hint>
|
|
130
|
-
</Field.Root>
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
`Field.Control` sends `INPUT`, `BLUR`, and `VALIDATE` events.
|
|
134
|
-
`Field.Message` renders only when `validation:invalid`.
|
|
135
|
-
Pending submission dims the control via `[data-aeon-state*="submission:pending"]` in global CSS.
|
|
136
|
-
|
|
137
|
-
---
|
|
138
|
-
|
|
139
|
-
## 4. Primitives already chart-backed
|
|
140
|
-
|
|
141
|
-
| Primitive | Stable states (high level) |
|
|
142
|
-
|-----------|----------------------------|
|
|
143
|
-
| **Dialog** | `closed` · `open` |
|
|
144
|
-
| **Toggle / Switch** | `checked` / `unchecked` + interaction `idle` · `pressed` |
|
|
145
|
-
| **Tabs** | single active value |
|
|
146
|
-
| **Accordion** | per-item open/closed |
|
|
147
|
-
|
|
148
|
-
Future optional substates (documented, not all implemented): dialog `entering` / `exiting`, accordion `expanding` / `collapsing`.
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## 5. Scroll regions
|
|
153
|
-
|
|
154
|
-
### What traditional UI skips
|
|
155
|
-
|
|
156
|
-
Scroll position is implicit. Overflow axis (vertical, horizontal, or both) and whether the user is at the start, middle, or end of each axis are rarely reflected in the DOM — so edge fades, scrollbar styling, and assistive hints stay out of sync.
|
|
157
|
-
|
|
158
|
-
### Aeon model
|
|
159
|
-
|
|
160
|
-
No XState machine — `getScrollSnapshot()` in `@aeon-ui/core` derives state from the viewport element on scroll and resize.
|
|
161
|
-
|
|
162
|
-
| `data-aeon-state` token | Meaning |
|
|
163
|
-
|-------------------------|---------|
|
|
164
|
-
| `idle` | No overflow on either axis |
|
|
165
|
-
| `overflow-y` · `overflow-x` · `overflow-both` | Which axes scroll |
|
|
166
|
-
| `at-start-y` · `between-y` · `at-end-y` | Vertical edge position |
|
|
167
|
-
| `at-start-x` · `between-x` · `at-end-x` | Horizontal edge position |
|
|
168
|
-
|
|
169
|
-
### Component API
|
|
170
|
-
|
|
171
|
-
```tsx
|
|
172
|
-
import { Scroll } from '@aeon-ui/ui'
|
|
173
|
-
|
|
174
|
-
<Scroll.Root axis="both" maxH="md">
|
|
175
|
-
<Scroll.Viewport>
|
|
176
|
-
<Scroll.Content>{children}</Scroll.Content>
|
|
177
|
-
</Scroll.Viewport>
|
|
178
|
-
<Scroll.StateReadout />
|
|
179
|
-
</Scroll.Root>
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Import `@aeon-ui/panda/scrollbars.css` after `theme-runtime.css` for WebKit + Firefox scrollbar styling on `html`, `[data-aeon-scroll]`, and `.code-example`.
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## 6. States to model in product code
|
|
187
|
-
|
|
188
|
-
Aeon does not ship every domain machine, but the same rules apply:
|
|
189
|
-
|
|
190
|
-
| Concern | Suggested states |
|
|
191
|
-
|---------|------------------|
|
|
192
|
-
| **Offline** | `online` · `offline` · `reconnecting` |
|
|
193
|
-
| **Permissions** | `allowed` · `denied` (not `disabled`) |
|
|
194
|
-
| **Optimistic UI** | `optimistic` · `confirmed` · `rolledBack` |
|
|
195
|
-
| **Rate limit** | `ok` · `throttled` |
|
|
196
|
-
| **Selection** | add `indeterminate` on checkboxes |
|
|
197
|
-
|
|
198
|
-
Use **orthogonal regions** when concerns are independent (Nguyen / Harel statecharts).
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
## 7. Styling checklist
|
|
203
|
-
|
|
204
|
-
Full ordered steps: **[docs/COMPONENT_CHECKLIST.md](./docs/COMPONENT_CHECKLIST.md)**.
|
|
205
|
-
|
|
206
|
-
1. Model the chart in `@aeon-ui/primitives` (or app-level XState).
|
|
207
|
-
2. Add anatomy in `@aeon-ui/core` if new scope.
|
|
208
|
-
3. Project snapshot in `@aeon-ui/react` with `partAttrs` / `stateToAttr`.
|
|
209
|
-
4. Add slot recipe or `globalCss` keyed on `data-aeon-state` in `@aeon-ui/panda`.
|
|
210
|
-
5. Export styled compound from `@aeon-ui/ui`.
|
|
211
|
-
6. Demo the **full rail** — track + readout + actions.
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
## 8. Play-surface layout (spatial, not XState)
|
|
216
|
-
|
|
217
|
-
Games and spatial UIs need **desktop vs mobile composition** without breaking pointer math. Aeon models this separately from behavior:
|
|
218
|
-
|
|
219
|
-
- **Layout mode** — `compact` | `wide` on `data-aeon-layout-mode`
|
|
220
|
-
- **Composition** — `split` | `stack` | `overlay` on `data-aeon-layout-composition`
|
|
221
|
-
- **Normalized coordinates** — `normFromClient` / `clientFromNorm` in `@aeon-ui/core` (0–100 plane per measured region)
|
|
222
|
-
|
|
223
|
-
Full guide: **[docs/LAYOUT_COORDINATES.md](./docs/LAYOUT_COORDINATES.md)** · Anatomy: `playSurfaceAnatomy` · Hook: `useLayoutMode`.
|
|
224
|
-
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
## 9. Package map
|
|
228
|
-
|
|
229
|
-
| Package | Role |
|
|
230
|
-
|---------|------|
|
|
231
|
-
| `@aeon-ui/primitives` | Machines: `async`, `buttonLifecycle`, `field`, … |
|
|
232
|
-
| `@aeon-ui/core` | `asyncAnatomy`, `fieldAnatomy`, `scrollAnatomy`, `getScrollSnapshot` |
|
|
233
|
-
| `@aeon-ui/react` | `Async.*`, `Field.*`, `Scroll.*`, `Button` + `status` |
|
|
234
|
-
| `@aeon-ui/panda` | `aeonAsync`, `aeonField`, `aeonScroll`, `scrollbars.css` |
|
|
235
|
-
| `@aeon-ui/ui` | Styled exports |
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## References
|
|
240
|
-
|
|
241
|
-
- [PHILOSOPHY.md](./PHILOSOPHY.md) — north star and tenets
|
|
242
|
-
- [Thanh Nguyen (2020) — Statecharts for modern web application state management](https://www.theseus.fi/bitstream/handle/10024/334118/Thanh_Nguyen.pdf?sequence=2&isAllowed=y)
|
|
243
|
-
- Live examples: `apps/demo` playground (**Async region**, **Button lifecycle**, **Field regions**, **Scroll regions**)
|
package/apps/demo/index.html
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>Aeon UI — Future-proof UI infrastructure</title>
|
|
7
|
-
<!-- OG_META -->
|
|
8
|
-
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
9
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
10
|
-
<link
|
|
11
|
-
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600;700&family=Syne:wght@600;700;800&display=swap"
|
|
12
|
-
rel="stylesheet"
|
|
13
|
-
/>
|
|
14
|
-
</head>
|
|
15
|
-
<body>
|
|
16
|
-
<div id="root"></div>
|
|
17
|
-
<script>
|
|
18
|
-
(function () {
|
|
19
|
-
var t = localStorage.getItem('aeon-theme') || 'default';
|
|
20
|
-
var m = localStorage.getItem('aeon-mode');
|
|
21
|
-
if (m !== 'light' && m !== 'dark') {
|
|
22
|
-
m = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
23
|
-
}
|
|
24
|
-
document.documentElement.dataset.aeonTheme = t;
|
|
25
|
-
document.documentElement.dataset.aeonMode = m;
|
|
26
|
-
document.documentElement.style.colorScheme = m;
|
|
27
|
-
})();
|
|
28
|
-
</script>
|
|
29
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
30
|
-
</body>
|
|
31
|
-
</html>
|
package/apps/demo/og.config.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/** Shared OG / canonical URLs — override in CI with VITE_SITE_ORIGIN. */
|
|
2
|
-
export const siteOrigin = (process.env.VITE_SITE_ORIGIN ?? 'https://aeon-ui.com').replace(
|
|
3
|
-
/\/$/,
|
|
4
|
-
'',
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
/** Published npm package — keep in sync with apps/demo/src/site.ts */
|
|
8
|
-
export const npmPackage = 'aeon-ui-engine'
|
|
9
|
-
export const npmInstallCommand = `npm install ${npmPackage}`
|
|
10
|
-
|
|
11
|
-
export const og = {
|
|
12
|
-
title: 'Aeon UI — Future-proof UI infrastructure',
|
|
13
|
-
description:
|
|
14
|
-
'Statechart-driven primitives and a styled layer you control — dependable behavior today, room to grow tomorrow. Built to last.',
|
|
15
|
-
siteName: 'Aeon UI',
|
|
16
|
-
imagePath: '/og-aeon-ui-engine.png',
|
|
17
|
-
imageAlt: `Aeon UI — Future-proof UI infrastructure (${npmInstallCommand})`,
|
|
18
|
-
themeColor: '#0c0e12',
|
|
19
|
-
npmInstallCommand,
|
|
20
|
-
}
|
package/apps/demo/package.json
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@aeon-ui/demo",
|
|
3
|
-
"private": true,
|
|
4
|
-
"type": "module",
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "vite",
|
|
7
|
-
"generate:og": "node scripts/generate-og-png.mjs",
|
|
8
|
-
"build": "pnpm --filter @aeon-ui/panda codegen && pnpm --filter @aeon-ui/site build && node scripts/generate-og-png.mjs && vite build",
|
|
9
|
-
"preview": "vite preview",
|
|
10
|
-
"typecheck": "tsc --noEmit"
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@aeon-ui/geometry": "workspace:*",
|
|
14
|
-
"@aeon-ui/panda": "workspace:*",
|
|
15
|
-
"@aeon-ui/primitives": "workspace:*",
|
|
16
|
-
"@aeon-ui/react": "workspace:*",
|
|
17
|
-
"@aeon-ui/react-scene": "workspace:*",
|
|
18
|
-
"@aeon-ui/signal": "workspace:*",
|
|
19
|
-
"@aeon-ui/surface": "workspace:*",
|
|
20
|
-
"@aeon-ui/ui": "workspace:*",
|
|
21
|
-
"@xstate/react": "^5.0.3",
|
|
22
|
-
"mermaid": "^11.15.0",
|
|
23
|
-
"react": "^19.1.0",
|
|
24
|
-
"react-dom": "^19.1.0",
|
|
25
|
-
"xstate": "^5.19.2"
|
|
26
|
-
},
|
|
27
|
-
"devDependencies": {
|
|
28
|
-
"@aeon-ui/site": "workspace:*",
|
|
29
|
-
"@fontsource/ibm-plex-mono": "^5.2.7",
|
|
30
|
-
"@fontsource/instrument-sans": "^5.2.8",
|
|
31
|
-
"@fontsource/syne": "^5.2.7",
|
|
32
|
-
"@resvg/resvg-js": "^2.6.2",
|
|
33
|
-
"@types/node": "^22.15.3",
|
|
34
|
-
"@types/react": "^19.1.2",
|
|
35
|
-
"@types/react-dom": "^19.1.2",
|
|
36
|
-
"@vitejs/plugin-react": "^4.4.1",
|
|
37
|
-
"typescript": "^5.8.3",
|
|
38
|
-
"vite": "^6.3.5",
|
|
39
|
-
"wawoff2": "^2.0.1"
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="Aeon UI">
|
|
2
|
-
<rect width="32" height="32" rx="8" fill="#0a0c10"/>
|
|
3
|
-
<rect
|
|
4
|
-
x="6.5"
|
|
5
|
-
y="6.5"
|
|
6
|
-
width="15"
|
|
7
|
-
height="15"
|
|
8
|
-
rx="3.5"
|
|
9
|
-
fill="none"
|
|
10
|
-
stroke="#5eead4"
|
|
11
|
-
stroke-width="1.75"
|
|
12
|
-
opacity="0.9"
|
|
13
|
-
/>
|
|
14
|
-
<rect x="10.5" y="10.5" width="15" height="15" rx="3.5" fill="#e8ecf4"/>
|
|
15
|
-
<circle cx="21" cy="21" r="2.5" fill="#5eead4"/>
|
|
16
|
-
</svg>
|
|
Binary file
|
package/apps/demo/public/og.svg
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-label="Aeon UI - Future-proof UI infrastructure">
|
|
2
|
-
<defs>
|
|
3
|
-
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
|
-
<stop offset="0%" stop-color="#121820"/>
|
|
5
|
-
<stop offset="55%" stop-color="#0c0e12"/>
|
|
6
|
-
<stop offset="100%" stop-color="#080a0e"/>
|
|
7
|
-
</linearGradient>
|
|
8
|
-
<linearGradient id="accent" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
9
|
-
<stop offset="0%" stop-color="#2dd4bf" stop-opacity="0"/>
|
|
10
|
-
<stop offset="45%" stop-color="#5eead4"/>
|
|
11
|
-
<stop offset="100%" stop-color="#2dd4bf" stop-opacity="0"/>
|
|
12
|
-
</linearGradient>
|
|
13
|
-
<pattern id="grid" width="48" height="48" patternUnits="userSpaceOnUse">
|
|
14
|
-
<path d="M 48 0 L 0 0 0 48" fill="none" stroke="#5eead4" stroke-width="0.5" opacity="0.08"/>
|
|
15
|
-
</pattern>
|
|
16
|
-
<radialGradient id="glow" cx="72%" cy="28%" r="45%">
|
|
17
|
-
<stop offset="0%" stop-color="#5eead4" stop-opacity="0.14"/>
|
|
18
|
-
<stop offset="100%" stop-color="#5eead4" stop-opacity="0"/>
|
|
19
|
-
</radialGradient>
|
|
20
|
-
</defs>
|
|
21
|
-
<rect width="1200" height="630" fill="url(#bg)"/>
|
|
22
|
-
<rect width="1200" height="630" fill="url(#grid)"/>
|
|
23
|
-
<rect width="1200" height="630" fill="url(#glow)"/>
|
|
24
|
-
|
|
25
|
-
<text x="80" y="268" fill="#f0f4e8" font-family="Syne" font-size="88" font-weight="700" letter-spacing="-0.04em">Aeon UI</text>
|
|
26
|
-
<text x="80" y="340" fill="#94a3b8" font-family="Instrument Sans" font-size="36" font-weight="500">Future-proof UI infrastructure</text>
|
|
27
|
-
<text x="80" y="400" fill="#64748b" font-family="IBM Plex Mono" font-size="22" font-weight="400">Statecharts / Headless primitives / Panda CSS</text>
|
|
28
|
-
|
|
29
|
-
<rect x="80" y="428" width="404" height="40" rx="6" fill="rgba(94, 234, 212, 0.12)" stroke="rgba(94, 234, 212, 0.35)" stroke-width="1"/>
|
|
30
|
-
<text x="104" y="454" fill="#5eead4" font-family="IBM Plex Mono" font-size="14" font-weight="500">{{NPM_INSTALL_COMMAND}}</text>
|
|
31
|
-
|
|
32
|
-
<text x="80" y="548" fill="#94a3b8" font-family="Instrument Sans" font-size="17" font-weight="500">A project by</text>
|
|
33
|
-
<text x="228" y="548" fill="#5eead4" font-family="Syne" font-size="22" font-weight="700">SpiderCorp</text>
|
|
34
|
-
<text x="80" y="578" fill="#64748b" font-family="IBM Plex Mono" font-size="14" font-weight="400">spidercorp.xyz</text>
|
|
35
|
-
</svg>
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'node:module'
|
|
2
|
-
import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { dirname, join } from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { Resvg } from '@resvg/resvg-js'
|
|
7
|
-
import wawoff2 from 'wawoff2'
|
|
8
|
-
import { og } from '../og.config.mjs'
|
|
9
|
-
|
|
10
|
-
const require = createRequire(import.meta.url)
|
|
11
|
-
const root = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
12
|
-
const svgPath = join(root, 'public', 'og.svg')
|
|
13
|
-
const outPath = join(root, 'public', og.imagePath.replace(/^\//, ''))
|
|
14
|
-
|
|
15
|
-
/** @type {Array<[slug: string, pkg: string, file: string]>} */
|
|
16
|
-
const FONT_SOURCES = [
|
|
17
|
-
['Syne-700', '@fontsource/syne', 'syne-latin-700-normal.woff2'],
|
|
18
|
-
['InstrumentSans-500', '@fontsource/instrument-sans', 'instrument-sans-latin-500-normal.woff2'],
|
|
19
|
-
['InstrumentSans-400', '@fontsource/instrument-sans', 'instrument-sans-latin-400-normal.woff2'],
|
|
20
|
-
['IBMPlexMono-500', '@fontsource/ibm-plex-mono', 'ibm-plex-mono-latin-500-normal.woff2'],
|
|
21
|
-
['IBMPlexMono-400', '@fontsource/ibm-plex-mono', 'ibm-plex-mono-latin-400-normal.woff2'],
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
function woff2Path(pkg, filename) {
|
|
25
|
-
return join(dirname(require.resolve(`${pkg}/package.json`)), 'files', filename)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** resvg `fontFiles` only loads SFNT/TTF — convert @fontsource WOFF2 first. */
|
|
29
|
-
async function loadFontFiles() {
|
|
30
|
-
const dir = mkdtempSync(join(tmpdir(), 'aeon-og-fonts-'))
|
|
31
|
-
const fontFiles = []
|
|
32
|
-
|
|
33
|
-
for (const [slug, pkg, file] of FONT_SOURCES) {
|
|
34
|
-
const woff2 = readFileSync(woff2Path(pkg, file))
|
|
35
|
-
const sfnt = await wawoff2.decompress(woff2)
|
|
36
|
-
const ttfPath = join(dir, `${slug}.ttf`)
|
|
37
|
-
writeFileSync(ttfPath, Buffer.from(sfnt))
|
|
38
|
-
fontFiles.push(ttfPath)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return fontFiles
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const svgTemplate = readFileSync(svgPath, 'utf8')
|
|
45
|
-
const svg = svgTemplate.replaceAll('{{NPM_INSTALL_COMMAND}}', og.npmInstallCommand)
|
|
46
|
-
const fontFiles = await loadFontFiles()
|
|
47
|
-
|
|
48
|
-
const resvg = new Resvg(svg, {
|
|
49
|
-
fitTo: { mode: 'width', value: 1200 },
|
|
50
|
-
font: {
|
|
51
|
-
loadSystemFonts: false,
|
|
52
|
-
fontFiles,
|
|
53
|
-
defaultFontFamily: 'Instrument Sans',
|
|
54
|
-
sansSerifFamily: 'Instrument Sans',
|
|
55
|
-
serifFamily: 'Syne',
|
|
56
|
-
monospaceFamily: 'IBM Plex Mono',
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
const png = resvg.render().asPng()
|
|
61
|
-
writeFileSync(outPath, png)
|
|
62
|
-
console.log(`Wrote ${outPath} (${png.length} bytes)`)
|
package/apps/demo/src/App.tsx
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Toast } from '@aeon-ui/ui'
|
|
2
|
-
import { GridMesh } from './components/GridMesh.js'
|
|
3
|
-
import { LandingFaq } from './components/LandingFaq.js'
|
|
4
|
-
import { LandingFooter } from './components/LandingFooter.js'
|
|
5
|
-
import { LandingHeader } from './components/LandingHeader.js'
|
|
6
|
-
import { LandingHero } from './components/LandingHero.js'
|
|
7
|
-
import { DiagramsSection } from './components/DiagramsSection.js'
|
|
8
|
-
import { LayersSection } from './components/LayersSection.js'
|
|
9
|
-
import { PhilosophySection } from './components/PhilosophySection.js'
|
|
10
|
-
import { PlaygroundSection } from './components/PlaygroundSection.js'
|
|
11
|
-
|
|
12
|
-
export function App() {
|
|
13
|
-
return (
|
|
14
|
-
<Toast.Provider>
|
|
15
|
-
<GridMesh />
|
|
16
|
-
<div className="landing">
|
|
17
|
-
<LandingHeader />
|
|
18
|
-
<main>
|
|
19
|
-
<LandingHero />
|
|
20
|
-
<PhilosophySection />
|
|
21
|
-
<PlaygroundSection />
|
|
22
|
-
<LayersSection />
|
|
23
|
-
<DiagramsSection />
|
|
24
|
-
<LandingFaq />
|
|
25
|
-
</main>
|
|
26
|
-
<LandingFooter />
|
|
27
|
-
</div>
|
|
28
|
-
<Toast.Viewport placement="bottom-end" />
|
|
29
|
-
</Toast.Provider>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/** Mermaid sources — kept in sync with aeon-ui-engine machines (STATES.md). */
|
|
2
|
-
|
|
3
|
-
export type AeonDiagram = {
|
|
4
|
-
id: string
|
|
5
|
-
title: string
|
|
6
|
-
caption: string
|
|
7
|
-
source: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/** Model → snapshot → DOM. The north-star flow. */
|
|
11
|
-
export const aeonNorthStarDiagram = `flowchart LR
|
|
12
|
-
chart["Statechart"]
|
|
13
|
-
snap["Snapshot"]
|
|
14
|
-
ui["UI"]
|
|
15
|
-
chart --> snap --> ui`
|
|
16
|
-
|
|
17
|
-
/** Main async path — full machine has reset/re-fetch edges in STATES.md. */
|
|
18
|
-
export const asyncLifecycleDiagram = `stateDiagram-v2
|
|
19
|
-
direction LR
|
|
20
|
-
[*] --> idle
|
|
21
|
-
idle --> loading: fetch
|
|
22
|
-
loading --> success: ok
|
|
23
|
-
loading --> failure: err
|
|
24
|
-
loading --> empty: empty
|
|
25
|
-
success --> idle: reset
|
|
26
|
-
failure --> idle: reset
|
|
27
|
-
empty --> idle: reset`
|
|
28
|
-
|
|
29
|
-
export const aeonDiagrams: AeonDiagram[] = [
|
|
30
|
-
{
|
|
31
|
-
id: 'north-star',
|
|
32
|
-
title: 'Model → UI',
|
|
33
|
-
caption: 'Behavior is defined once; the interface is its projection — structure, copy, and state attributes in agreement.',
|
|
34
|
-
source: aeonNorthStarDiagram,
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
id: 'async',
|
|
38
|
-
title: 'Async region',
|
|
39
|
-
caption: 'A named lifecycle teams can reason about together — not an unnamed spinner behind a boolean.',
|
|
40
|
-
source: asyncLifecycleDiagram,
|
|
41
|
-
},
|
|
42
|
-
]
|
package/apps/demo/src/app.css
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Button, cn } from '@aeon-ui/ui'
|
|
2
|
-
import type { ReactNode } from 'react'
|
|
3
|
-
|
|
4
|
-
export interface ActionClusterProps {
|
|
5
|
-
children: ReactNode
|
|
6
|
-
className?: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/** Machine console actions — spaced buttons by default. */
|
|
10
|
-
export function ActionCluster({ children, className }: ActionClusterProps) {
|
|
11
|
-
return (
|
|
12
|
-
<Button.Group
|
|
13
|
-
className={cn('machine-console__actions', className)}
|
|
14
|
-
data-aeon-scope="machine"
|
|
15
|
-
data-aeon-part="actions"
|
|
16
|
-
>
|
|
17
|
-
{children}
|
|
18
|
-
</Button.Group>
|
|
19
|
-
)
|
|
20
|
-
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { useAsyncContext } from '@aeon-ui/react'
|
|
2
|
-
import { Async, Button } from '@aeon-ui/ui'
|
|
3
|
-
import { ActionCluster } from './ActionCluster.js'
|
|
4
|
-
|
|
5
|
-
export interface AsyncMachineDemoProps {
|
|
6
|
-
/** Playground shows failure and empty paths; hero always succeeds for a crisp preview. */
|
|
7
|
-
variant?: 'hero' | 'playground'
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const readouts: Record<string, Record<'hero' | 'playground', string>> = {
|
|
11
|
-
idle: {
|
|
12
|
-
hero: 'Idle — ready to fetch',
|
|
13
|
-
playground: 'Idle — press Simulate fetch when ready',
|
|
14
|
-
},
|
|
15
|
-
loading: {
|
|
16
|
-
hero: 'Loading…',
|
|
17
|
-
playground: 'Loading…',
|
|
18
|
-
},
|
|
19
|
-
success: {
|
|
20
|
-
hero: 'Success',
|
|
21
|
-
playground: 'Success — data on context',
|
|
22
|
-
},
|
|
23
|
-
failure: {
|
|
24
|
-
hero: 'Failure',
|
|
25
|
-
playground: 'Failure — error on context',
|
|
26
|
-
},
|
|
27
|
-
empty: {
|
|
28
|
-
hero: 'Empty',
|
|
29
|
-
playground: 'Empty — resolved with no rows',
|
|
30
|
-
},
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function AsyncMachineControls({ variant }: { variant: 'hero' | 'playground' }) {
|
|
34
|
-
const { status, send } = useAsyncContext()
|
|
35
|
-
const runFetch = () => {
|
|
36
|
-
send({ type: 'FETCH' })
|
|
37
|
-
const delay = variant === 'hero' ? 700 : 900
|
|
38
|
-
setTimeout(() => {
|
|
39
|
-
if (variant === 'hero') {
|
|
40
|
-
send({ type: 'RESOLVE', data: 'Payload OK' })
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
const roll = Math.random()
|
|
44
|
-
if (roll < 0.25) {
|
|
45
|
-
send({ type: 'REJECT', error: 'Network unreachable' })
|
|
46
|
-
} else if (roll < 0.4) {
|
|
47
|
-
send({ type: 'RESOLVE', data: [] })
|
|
48
|
-
} else {
|
|
49
|
-
send({ type: 'RESOLVE', data: 'Aeon payload received' })
|
|
50
|
-
}
|
|
51
|
-
}, delay)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<ActionCluster className="machine-console__actions">
|
|
56
|
-
<Button.Root size="sm" onClick={runFetch} disabled={status === 'loading'}>
|
|
57
|
-
{variant === 'hero' ? 'Run' : 'Simulate fetch'}
|
|
58
|
-
</Button.Root>
|
|
59
|
-
<Button.Root size="sm" variant="outline" onClick={() => send({ type: 'RESET' })}>
|
|
60
|
-
Reset
|
|
61
|
-
</Button.Root>
|
|
62
|
-
{variant === 'playground' && status === 'success' && (
|
|
63
|
-
<Button.Root size="sm" variant="ghost" onClick={() => send({ type: 'STALE' })}>
|
|
64
|
-
Mark stale
|
|
65
|
-
</Button.Root>
|
|
66
|
-
)}
|
|
67
|
-
</ActionCluster>
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function AsyncMachineReadout({ variant }: { variant: 'hero' | 'playground' }) {
|
|
72
|
-
const { status, data, error, stale } = useAsyncContext()
|
|
73
|
-
const readout =
|
|
74
|
-
status === 'success'
|
|
75
|
-
? String(data ?? (variant === 'hero' ? 'Payload OK' : 'Aeon payload received'))
|
|
76
|
-
: status === 'failure'
|
|
77
|
-
? String(error ?? 'Network unreachable')
|
|
78
|
-
: (readouts[status]?.[variant] ?? readouts.idle[variant])
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<p className="live-status__text">
|
|
82
|
-
{stale && status === 'success' ? `${readout} (stale — refresh recommended)` : readout}
|
|
83
|
-
</p>
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function AsyncMachineDemo({ variant = 'playground' }: AsyncMachineDemoProps) {
|
|
88
|
-
return (
|
|
89
|
-
<Async.Root data-aeon-scope="machine" data-aeon-part="console">
|
|
90
|
-
<Async.Track />
|
|
91
|
-
<Async.Readout>
|
|
92
|
-
<AsyncMachineReadout variant={variant} />
|
|
93
|
-
</Async.Readout>
|
|
94
|
-
<AsyncMachineControls variant={variant} />
|
|
95
|
-
</Async.Root>
|
|
96
|
-
)
|
|
97
|
-
}
|