astryx-dracula 0.6.2 → 0.7.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/BRAND.md +54 -3
- package/README.md +11 -4
- package/USAGE.md +1 -1
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +26 -4
- package/astryx-dracula.variants.d.ts +2 -2
- package/package.json +11 -11
- package/shared/auth-chrome-config.ts +0 -4
- package/shared/auth-chrome.tsx +2 -3
- package/shared/chaptered-doc-config.ts +3 -2
- package/shared/order-desk.tsx +52 -54
- package/shared/revenue-chart.tsx +1 -1
- package/shared/scene-castle.tsx +21 -13
- package/templates/ai-chat-landing.tsx +6 -9
- package/templates/ai-chat.tsx +5 -1
- package/templates/centered-hero.tsx +2 -2
- package/templates/classic-gallery.tsx +5 -0
- package/templates/contact-form.tsx +9 -0
- package/templates/dashboard-portfolio.tsx +17 -4
- package/templates/dashboard.tsx +5 -5
- package/templates/detail-page.tsx +20 -28
- package/templates/documentation-design.tsx +3 -0
- package/templates/documentation-technical.tsx +8 -5
- package/templates/documentation.tsx +13 -6
- package/templates/form-two-column.tsx +11 -2
- package/templates/form-wizard.template.mjs +15 -0
- package/templates/form-wizard.tsx +878 -0
- package/templates/gallery-hero.tsx +10 -4
- package/templates/ide.tsx +177 -96
- package/templates/incident-console.tsx +15 -5
- package/templates/kanban-board.tsx +46 -4
- package/templates/library.tsx +12 -3
- package/templates/login-split.tsx +3 -3
- package/templates/login-sso.tsx +9 -17
- package/templates/messaging-shell.tsx +109 -46
- package/templates/mixed-gallery.tsx +6 -0
- package/templates/payment-form.tsx +29 -28
- package/templates/product-detail.tsx +12 -3
- package/templates/product-gallery.tsx +8 -3
- package/templates/product-tour.tsx +11 -7
- package/templates/settings-dialog.tsx +5 -0
- package/templates/settings-sidebar.tsx +603 -584
- package/templates/settings.tsx +5 -2
- package/templates/shell-nav.tsx +23 -3
- package/templates/shell-side-nav.tsx +94 -81
- package/templates/shell-top-nav.tsx +98 -86
- package/templates/table-grouped.tsx +146 -132
- package/templates/{table-page-shoe-store-heatmap.template.mjs → table-page-shoe-store.template.mjs} +1 -1
- package/templates/{table-page-shoe-store-heatmap.tsx → table-page-shoe-store.tsx} +1 -1
- package/templates/table-page.tsx +8 -0
- package/templates/table-tree.template.mjs +16 -0
- package/templates/table-tree.tsx +707 -0
- package/templates/tech-report.tsx +8 -5
- package/templates/theme-showcase.tsx +18 -11
- package/templates/work-item-detail.template.mjs +8 -0
- package/templates/work-item-detail.tsx +1049 -0
- package/theme.css +38 -3
- package/theme.css.d.ts +2 -2
- package/tokens.css +41 -4
package/BRAND.md
CHANGED
|
@@ -9,7 +9,7 @@ Source of truth is `astryx-theme.ts`. Do not add hexes; `bun run audit` enforces
|
|
|
9
9
|
|---|---|---|
|
|
10
10
|
| Background | #282A36 | page bg |
|
|
11
11
|
| Current line | #6272A4 | line highlight, subtle borders (doubles as comment)[^current-line] |
|
|
12
|
-
| Selection | #44475A | selected rows,
|
|
12
|
+
| Selection | #44475A | selected rows and text selection (a semantic, never an elevation step) |
|
|
13
13
|
| Background Light | #343746 | cards, surfaces |
|
|
14
14
|
| Background Lighter | #424450 | popovers, floating elements |
|
|
15
15
|
| Background Dark | #21222C | shadows, on-fill text |
|
|
@@ -29,6 +29,56 @@ Source of truth is `astryx-theme.ts`. Do not add hexes; `bun run audit` enforces
|
|
|
29
29
|
|
|
30
30
|
[^current-line]: Upstream pins both Current Line and Comment to #6272A4. This kit follows suit for line affordances, including scrollbar thumbs in `tokens.css`. Do not "correct" one of them to #44475A.
|
|
31
31
|
|
|
32
|
+
### The spec's four palettes
|
|
33
|
+
|
|
34
|
+
`draculatheme.com/spec` publishes four palettes, and the theme draws on all of
|
|
35
|
+
them. Read the spec before calling a hex off-brand — `#DE5735` looks like a
|
|
36
|
+
stray Stone colour until you find **Functional Red** in the UI section.
|
|
37
|
+
|
|
38
|
+
| Palette | Hexes |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Syntax | the twelve above |
|
|
41
|
+
| ANSI | `#21222C` black, the eight base hues, and the brights `#6272A4`, `#FF6E6E`, `#69FF94`, `#FFFFA5`, `#D6ACFF`, `#FF92DF`, `#A4FFFF`, `#FFFFFF` |
|
|
42
|
+
| UI | `#343746` light, `#424450` lighter, `#21222C` dark, `#191A21` darker |
|
|
43
|
+
| Functional | `#DE5735`, `#A39514`, `#089108`, `#0081D6`, `#815CD6` — interactive elements, borders and indicators, never status and never editor text |
|
|
44
|
+
|
|
45
|
+
The categorical chart set draws its teal and indigo from the ANSI brights
|
|
46
|
+
(`#A4FFFF`, `#D6ACFF`), which is why those two are spec values rather than
|
|
47
|
+
inventions.
|
|
48
|
+
|
|
49
|
+
### Derived values
|
|
50
|
+
|
|
51
|
+
`scripts/check.ts` allows every hex in `@layer astryx-theme` only if it is in one
|
|
52
|
+
of those four palettes or named in `DERIVED_HEXES` with a reason. Anything else
|
|
53
|
+
fails the build and prints the hex. That gate exists because core's builder
|
|
54
|
+
inlines a token it derives instead of aliasing it, so any colour role the theme
|
|
55
|
+
does not pin keeps Stone's value — 0.7.10 shipped `#E9AF08` for warning text and
|
|
56
|
+
`#F77D77` for error, and the light-dark symmetry gate caught the first only
|
|
57
|
+
because its two branches happened to differ.
|
|
58
|
+
|
|
59
|
+
Six values are ours, each traceable to a spec value:
|
|
60
|
+
|
|
61
|
+
- `#9AA1BC` — secondary text, border-emphasized and the gray family. Comment is
|
|
62
|
+
the obvious pick and fails the 4.5 floor on every surface.
|
|
63
|
+
- `#B0B3C4` — chart axis and caption text, one step above secondary so an axis
|
|
64
|
+
reads over a grid.
|
|
65
|
+
- `#8C939B` — chart neutral, deliberately not a Dracula hue so a neutral series
|
|
66
|
+
is not read as one.
|
|
67
|
+
- `#FFD5CC` — the pale inverted error surface behind the inverted Toast.
|
|
68
|
+
- `#FF7575` — error **text**: the ANSI Bright Red lifted 5% toward Foreground.
|
|
69
|
+
Bright Red alone is 5.23:1 on the body but 4.33:1 on a surface, just under the
|
|
70
|
+
4.5 floor the spec itself sets; the lift reads 5.46 and 4.52.
|
|
71
|
+
|
|
72
|
+
Pure black `#000000` is also allowed, for `on-light` and the overlay hover and
|
|
73
|
+
press steps; the spec never asks for black text, but a black overlay is how a
|
|
74
|
+
scrollbar and a pressed state darken.
|
|
75
|
+
|
|
76
|
+
Two legacy text tokens (`#8288A6`, `#4C5067`) are allowed too, contrast-gated at
|
|
77
|
+
their own floors rather than an AA threshold.
|
|
78
|
+
|
|
79
|
+
To add a colour, pin it in `astryx-theme.ts` against a spec value, or put it in
|
|
80
|
+
`DERIVED_HEXES` with the reason it exists.
|
|
81
|
+
|
|
32
82
|
## Semantic map (glimpse)
|
|
33
83
|
|
|
34
84
|
- Purple links/titles unvisited. Visited falls back to text-base, hover goes primary + underline. Users learn purple means tappable title.
|
|
@@ -59,8 +109,9 @@ Each status scope redefines its muted token to the categorical tint
|
|
|
59
109
|
(`--color-background-<hue>`, 10% accent wash) with a semantic accent border.
|
|
60
110
|
Text on fills is always `#21222C`; spec never uses raw black. Links render accent
|
|
61
111
|
with underline and resolve to foreground on hover. Focus and interactive edges
|
|
62
|
-
use accent, not Functional Purple: `--color-functional-*`
|
|
63
|
-
|
|
112
|
+
use accent, not Functional Purple: `--color-functional-*` carries the spec's UI
|
|
113
|
+
Functional palette for interactive elements, borders and indicators — never a
|
|
114
|
+
status channel, and no shipped component consumes it today.
|
|
64
115
|
|
|
65
116
|
## Interactions
|
|
66
117
|
|
package/README.md
CHANGED
|
@@ -11,12 +11,14 @@ Live showcase: https://yuzu-octopus.github.io/astryx-dracula/
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- Dracula tokens pinned in `astryx-theme.ts` (`grep -c "pin(" astryx-theme.ts` pins) (the public props the prebuilt `theme.css` ships, `grep -c "^ --" theme.css`, plus 3 private `--_*-radius` aliases; `tokens.css` mirrors the theme's colour, spacing, radius, type and motion tokens as plain `:root` custom properties, unlayered so it beats core's layers, `grep -c "^\s*--" tokens.css` vars), dark-only. No gate asserts these counts — `bun run theme:check` asserts freshness, `bun run audit` asserts palette purity and contrast.
|
|
14
|
+
- The spec's four palettes (Syntax, ANSI, UI, Functional), with the ANSI bright row
|
|
15
|
+
available as `--dracula-ansi-bright-*` for emphasis and for a chart series past the base eight
|
|
14
16
|
- Syntax theme plus chart series and sequential ramps
|
|
15
17
|
- Prebuilt CSS plus runtime theme injection, or runtime injection alone for prototyping
|
|
16
18
|
- WCAG contrast gates enforced in CI via `bun run audit`
|
|
17
19
|
- JetBrains Mono fonts and Lucide icons, one runtime dependency (`lucide-react`)
|
|
18
20
|
- Agent skill, `llms.txt`, and copy-paste quickstart for AI-assisted adoption
|
|
19
|
-
-
|
|
21
|
+
- 48 themed pages in `templates/`, published as an Astryx integration pack and viewable live at `/astryx-dracula/#/templates`
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
22
24
|
|
|
@@ -52,7 +54,7 @@ theme.css prebuilt output of bun run theme:build
|
|
|
52
54
|
tokens.css plain-CSS :root mirror for any stack
|
|
53
55
|
icons.tsx Lucide icon registry (runtime `lucide-react` dependency)
|
|
54
56
|
fonts/ JetBrains Mono woff2 files
|
|
55
|
-
templates/
|
|
57
|
+
templates/ 48 themed pages, each with an XLE structure header
|
|
56
58
|
shared/ reusable modules templates compose (23 files)
|
|
57
59
|
scripts/check.ts palette, contrast, packaging, and XLE-header gates
|
|
58
60
|
demo/ showcase source, deployed to GitHub Pages
|
|
@@ -68,11 +70,16 @@ demo/ showcase source, deployed to GitHub Pages
|
|
|
68
70
|
| `bun run build` | Typecheck plus showcase build |
|
|
69
71
|
| `bun run fmt` | Format the source (`bun fmt`; style in `bunfig.toml`) |
|
|
70
72
|
| `bun run fmt:check` | Fail if the source is not formatted |
|
|
71
|
-
| `bun run lint` |
|
|
73
|
+
| `bun run lint` | `biome check`: lint plus import order. Formatting is `bun fmt` (Biome's formatter is off) |
|
|
74
|
+
| `bun audit` | Bun's builtin vulnerability scan — not this repo's `audit` script |
|
|
72
75
|
| `bunx react-doctor@latest` | Component health scan (currently 100/100) |
|
|
73
76
|
|
|
77
|
+
`bun audit` is a bun builtin and `bun run audit` is the kit gate. Bun's own
|
|
78
|
+
command runs first, so the two never collide, but a bare `bun audit` checks
|
|
79
|
+
dependencies and says nothing about the palette.
|
|
80
|
+
|
|
74
81
|
## Attribution & License
|
|
75
82
|
|
|
76
|
-
- **Astryx Design System**: Created by the Astryx team at Meta Platforms, Inc. under the [MIT License](https://github.com/facebook/astryx/blob/main/LICENSE). The template files in `templates/` (
|
|
83
|
+
- **Astryx Design System**: Created by the Astryx team at Meta Platforms, Inc. under the [MIT License](https://github.com/facebook/astryx/blob/main/LICENSE). The template files in `templates/` (46) are derived from Astryx's open source page templates and adapted for the Dracula brand (pure Dracula spec retokening, recharts replaced with zero-dependency inline SVGs, heroicons mapped to Lucide, XLE/XLO structural expression headers, and responsive layout hardening).
|
|
77
84
|
- **Dracula Theme**: Color palette and specification by [Zeno Rocha and the Dracula Theme community](https://draculatheme.com).
|
|
78
85
|
- **Package & Additions**: Released under the [MIT License](LICENSE).
|
package/USAGE.md
CHANGED
|
@@ -73,7 +73,7 @@ Then use `var(--color-primary)`, `var(--dracula-purple)`, `var(--space-gap)`.
|
|
|
73
73
|
|
|
74
74
|
## Templates
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
48 themed pages ship in `templates/`, each with a `<id>.template.mjs` spec, published
|
|
77
77
|
as an Astryx integration pack (`astryx.integration.mjs`). List this package in your
|
|
78
78
|
`astryx.config`, then scaffold any of them:
|
|
79
79
|
|
package/astryx-dracula.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: astryx-theme.ts
|
|
4
4
|
* Command: astryx theme build astryx-theme.ts --out theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.10
|
|
6
|
+
* Core: @astryxdesign/core@0.7.10
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
/// <reference path="./astryx-dracula.variants.d.ts" />
|
package/astryx-dracula.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: astryx-theme.ts
|
|
4
4
|
* Command: astryx theme build astryx-theme.ts --out theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.10
|
|
6
|
+
* Core: @astryxdesign/core@0.7.10
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { draculaIconRegistry } from "./icons.mjs";
|
|
@@ -81,6 +81,18 @@ export const astryxDraculaTheme = {
|
|
|
81
81
|
"--duration-slow-min": "730ms",
|
|
82
82
|
"--duration-slow": "975ms",
|
|
83
83
|
"--duration-slow-max": "1300ms",
|
|
84
|
+
"--_astryx-pre-tempo-duration-fast-min": "130ms",
|
|
85
|
+
"--_astryx-pre-tempo-duration-fast": "175ms",
|
|
86
|
+
"--_astryx-pre-tempo-duration-fast-max": "235ms",
|
|
87
|
+
"--_astryx-pre-tempo-duration-medium-min": "310ms",
|
|
88
|
+
"--_astryx-pre-tempo-duration-medium": "410ms",
|
|
89
|
+
"--_astryx-pre-tempo-duration-medium-max": "545ms",
|
|
90
|
+
"--_astryx-pre-tempo-duration-slow-min": "730ms",
|
|
91
|
+
"--_astryx-pre-tempo-duration-slow": "975ms",
|
|
92
|
+
"--_astryx-pre-tempo-duration-slow-max": "1300ms",
|
|
93
|
+
"--_astryx-motion-tempo": "1",
|
|
94
|
+
"--ease-entrance": "cubic-bezier(0.24, 1, 0.4, 1)",
|
|
95
|
+
"--ease-move": "cubic-bezier(0.24, 1, 0.4, 1)",
|
|
84
96
|
"--font-family-body": "\"JetBrains Mono\", monospace",
|
|
85
97
|
"--font-family-heading": "\"JetBrains Mono\", monospace",
|
|
86
98
|
"--font-family-code": "\"JetBrains Mono\", monospace",
|
|
@@ -146,7 +158,7 @@ export const astryxDraculaTheme = {
|
|
|
146
158
|
"--color-background-surface": "light-dark(#343746, #343746)",
|
|
147
159
|
"--color-background-card": "light-dark(#343746, #343746)",
|
|
148
160
|
"--color-background-popover": "light-dark(#424450, #424450)",
|
|
149
|
-
"--color-background-muted": "light-dark(#
|
|
161
|
+
"--color-background-muted": "light-dark(#21222C, #21222C)",
|
|
150
162
|
"--color-overlay": "light-dark(#191A21CC, #191A21CC)",
|
|
151
163
|
"--color-overlay-hover": "light-dark(#0000001F, #0000001F)",
|
|
152
164
|
"--color-overlay-pressed": "light-dark(#00000033, #00000033)",
|
|
@@ -161,6 +173,14 @@ export const astryxDraculaTheme = {
|
|
|
161
173
|
"--color-text-secondary": "light-dark(#9AA1BC, #9AA1BC)",
|
|
162
174
|
"--color-text-disabled": "light-dark(#6272A4, #6272A4)",
|
|
163
175
|
"--color-text-accent": "light-dark(#BD93F9, #BD93F9)",
|
|
176
|
+
"--color-text-info": "light-dark(#8BE9FD, #8BE9FD)",
|
|
177
|
+
"--color-text-success": "light-dark(#50FA7B, #50FA7B)",
|
|
178
|
+
"--color-text-warning": "light-dark(#F1FA8C, #F1FA8C)",
|
|
179
|
+
"--color-text-error": "light-dark(#FF7575, #FF7575)",
|
|
180
|
+
"--color-on-info-muted": "light-dark(#8BE9FD, #8BE9FD)",
|
|
181
|
+
"--color-on-success-muted": "light-dark(#50FA7B, #50FA7B)",
|
|
182
|
+
"--color-on-warning-muted": "light-dark(#F1FA8C, #F1FA8C)",
|
|
183
|
+
"--color-on-error-muted": "light-dark(#FF7575, #FF7575)",
|
|
164
184
|
"--color-icon-primary": "light-dark(#F8F8F2, #F8F8F2)",
|
|
165
185
|
"--color-icon-secondary": "light-dark(#9AA1BC, #9AA1BC)",
|
|
166
186
|
"--color-icon-disabled": "light-dark(#6272A4, #6272A4)",
|
|
@@ -689,7 +709,9 @@ export const astryxDraculaTheme = {
|
|
|
689
709
|
"fast": 175,
|
|
690
710
|
"medium": 410,
|
|
691
711
|
"slow": 975,
|
|
692
|
-
"ratio": 0.75
|
|
712
|
+
"ratio": 0.75,
|
|
713
|
+
"tempo": 1,
|
|
714
|
+
"character": "standard"
|
|
693
715
|
}
|
|
694
716
|
},
|
|
695
717
|
icons: draculaIconRegistry,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @generated by `astryx theme build` — do not edit manually.
|
|
3
3
|
* Source: astryx-theme.ts
|
|
4
4
|
* Command: astryx theme build astryx-theme.ts --out theme.css
|
|
5
|
-
* CLI: @astryxdesign/cli@0.7.
|
|
6
|
-
* Core: @astryxdesign/core@0.7.
|
|
5
|
+
* CLI: @astryxdesign/cli@0.7.10
|
|
6
|
+
* Core: @astryxdesign/core@0.7.10
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
// Generated by astryx theme build
|
package/package.json
CHANGED
|
@@ -4,20 +4,20 @@
|
|
|
4
4
|
"url": "https://github.com/yuzu-octopus/astryx-dracula/issues"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"lucide-react": "
|
|
7
|
+
"lucide-react": "1.70.0"
|
|
8
8
|
},
|
|
9
9
|
"description": "Pure Dracula brand theme for Astryx React sites. Dark-only tokens, syntax highlighting, and chart colors with prebuilt CSS.",
|
|
10
10
|
"devDependencies": {
|
|
11
|
-
"@astryxdesign/cli": "0.7.
|
|
12
|
-
"@astryxdesign/core": "0.7.
|
|
13
|
-
"@biomejs/biome": "2.6.
|
|
14
|
-
"@types/bun": "
|
|
15
|
-
"@types/node": "
|
|
11
|
+
"@astryxdesign/cli": "0.7.10",
|
|
12
|
+
"@astryxdesign/core": "0.7.10",
|
|
13
|
+
"@biomejs/biome": "2.6.6",
|
|
14
|
+
"@types/bun": "1.4.4",
|
|
15
|
+
"@types/node": "26.6.6",
|
|
16
16
|
"@types/react": "^19.3.0",
|
|
17
17
|
"@types/react-dom": "^19.3.0",
|
|
18
|
-
"@vitejs/plugin-react": "
|
|
18
|
+
"@vitejs/plugin-react": "6.2.1",
|
|
19
19
|
"typescript": "^7.0.2",
|
|
20
|
-
"vite": "
|
|
20
|
+
"vite": "8.3.6"
|
|
21
21
|
},
|
|
22
22
|
"exports": {
|
|
23
23
|
"./shared/auth-chrome": "./shared/auth-chrome.tsx",
|
|
@@ -110,10 +110,10 @@
|
|
|
110
110
|
"theme:check": "bun run icons:check && bunx astryx theme build astryx-theme.ts -o theme.css --icons-specifier ./icons.mjs --check --json | bun -e 'const r = JSON.parse(await Bun.stdin.text()); if (r.type !== \"theme.build.check\") { console.error(\"theme:check: unexpected CLI response \" + JSON.stringify(r).slice(0, 200)); process.exit(1); } const stale = r.data.stale; for (const s of stale) console.error(\"stale: \" + s.path + \" (\" + s.reason + \")\"); if (stale.length) process.exit(1); const entry = await import(\"./astryx-dracula.js\"); if (!entry.astryxDraculaTheme || !entry.draculaIconRegistry) { console.error(\"astryx-dracula.js is missing its theme or icon registry export\"); process.exit(1); } console.log(\"theme outputs up to date; astryx-dracula.js (hand-maintained) loads and exports\");' && bun run entry:check",
|
|
111
111
|
"icons:check": "NODE_ENV=production bun build icons.tsx --outfile .tmp-icons.mjs --format esm --external react --external lucide-react; s=$?; cmp -s .tmp-icons.mjs icons.mjs; c=$?; rm -f .tmp-icons.mjs; [ $s -eq 0 ] && [ $c -eq 0 ] || { echo 'stale: icons.mjs (rerun bun run icons:build)'; exit 1; }",
|
|
112
112
|
"entry:check": "node -e \"const{pathToFileURL}=require('url');const{resolve}=require('path');import(pathToFileURL(resolve('astryx-dracula.js')).href).then(m=>{if(!m.astryxDraculaTheme||!m.draculaIconRegistry){console.error('astryx-dracula.js is missing its theme or icon registry export');process.exit(1)}console.log('astryx-dracula.js loads under plain Node ESM')}).catch(e=>{console.error('astryx-dracula.js is not importable by Node ESM: '+e.message);process.exit(1)})\"",
|
|
113
|
-
"prepublishOnly": "bun run theme:check && bun run icons:check && bun run entry:check && bun run audit && bun check && bun run fmt:check && bun run lint && bunx react-doctor@latest --score",
|
|
113
|
+
"prepublishOnly": "bun audit && bun run theme:check && bun run icons:check && bun run entry:check && bun run audit && bun check && bun run fmt:check && bun run lint && bunx react-doctor@latest --score",
|
|
114
114
|
"fmt": "bun fmt '**/*.{ts,tsx,mjs,js,jsx,json,css}'",
|
|
115
115
|
"fmt:check": "bun fmt --check '**/*.{ts,tsx,mjs,js,jsx,json,css}'",
|
|
116
|
-
"lint": "biome
|
|
116
|
+
"lint": "biome check"
|
|
117
117
|
},
|
|
118
118
|
"sideEffects": [
|
|
119
119
|
"**/*.css"
|
|
@@ -123,5 +123,5 @@
|
|
|
123
123
|
"@astryxdesign/cli"
|
|
124
124
|
],
|
|
125
125
|
"type": "module",
|
|
126
|
-
"version": "0.
|
|
126
|
+
"version": "0.7.0"
|
|
127
127
|
}
|
|
@@ -12,7 +12,3 @@ export const authPageStyle: CSSProperties = {
|
|
|
12
12
|
};
|
|
13
13
|
// Cap the column at 400px but let it shrink to fit narrow screens.
|
|
14
14
|
export const authContentStyle: CSSProperties = { width: "100%", maxWidth: 400 };
|
|
15
|
-
// WCAG 1.3.5 autocomplete: TextInput forwards unknown props to <input> but its
|
|
16
|
-
// prop type omits input-only attributes, so spread through a widened record.
|
|
17
|
-
export const inputAutoComplete = (value: string) =>
|
|
18
|
-
({ autoComplete: value }) as Record<string, string>;
|
package/shared/auth-chrome.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import { VStack } from "@astryxdesign/core/Layout";
|
|
|
8
8
|
import { Link } from "@astryxdesign/core/Link";
|
|
9
9
|
import { Heading, Text } from "@astryxdesign/core/Text";
|
|
10
10
|
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
11
|
-
import { inputAutoComplete } from "astryx-dracula/shared/auth-chrome-config";
|
|
12
11
|
import {
|
|
13
12
|
AUTH_BRAND_NAME,
|
|
14
13
|
AUTH_EMAIL_PLACEHOLDER,
|
|
@@ -116,7 +115,7 @@ export function AuthLoginFields({
|
|
|
116
115
|
onChange={onEmailChange}
|
|
117
116
|
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
118
117
|
type="email"
|
|
119
|
-
|
|
118
|
+
autoComplete="email"
|
|
120
119
|
size="lg"
|
|
121
120
|
onEnter={onSubmit}
|
|
122
121
|
status={error ? { type: "error", message: error } : undefined}
|
|
@@ -129,7 +128,7 @@ export function AuthLoginFields({
|
|
|
129
128
|
onChange={onPasswordChange}
|
|
130
129
|
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
131
130
|
type="password"
|
|
132
|
-
|
|
131
|
+
autoComplete="current-password"
|
|
133
132
|
size="lg"
|
|
134
133
|
onEnter={onSubmit}
|
|
135
134
|
status={error ? { type: "error", message: error } : undefined}
|
|
@@ -57,11 +57,12 @@ export const outlinePanel: CSSProperties = {
|
|
|
57
57
|
top: "var(--spacing-6)",
|
|
58
58
|
alignSelf: "start",
|
|
59
59
|
// No paddingBlockStart. This object is applied via `style=` to the outline
|
|
60
|
-
// LayoutPanels
|
|
61
|
-
// documentation-technical.tsx:82), and an inline style prop BEATS the stylex
|
|
60
|
+
// LayoutPanels, and an inline style prop BEATS the stylex
|
|
62
61
|
// class -- so this 8px silently overrode the panel's own 16px block padding
|
|
63
62
|
// at the top edge only, leaving every "On this page" rail in the kit
|
|
64
63
|
// asymmetric by 8px. The panel already owns its block padding.
|
|
64
|
+
// Call sites are named by file, not by line: line numbers in a comment go
|
|
65
|
+
// stale the first time anyone edits above them, and these three did.
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
// Derived once per page so the Outline receives a stable array identity
|
package/shared/order-desk.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Parametric order desk shared by the table-page-chart (Matcha Bar) and
|
|
2
|
-
// table-page-shoe-store
|
|
2
|
+
// table-page-shoe-store (Midnight Kicks) templates. The two pages are
|
|
3
3
|
// one archetype — revenue line over the order log — differing only in catalogue,
|
|
4
4
|
// rows, and chart scale, so the frame/columns live here once.
|
|
5
5
|
|
|
@@ -37,7 +37,7 @@ import type { CSSProperties } from "react";
|
|
|
37
37
|
// minHeight appears in none of them.
|
|
38
38
|
//
|
|
39
39
|
// The wrapper is shared by all three callers (table-page-chart.tsx:410,
|
|
40
|
-
// table-page-shoe-store
|
|
40
|
+
// table-page-shoe-store.tsx:759, and this module's own default), so
|
|
41
41
|
// anchoring it here covers them together. Anchoring inside each caller instead
|
|
42
42
|
// would put a 100dvh box inside this one and overflow by the header height.
|
|
43
43
|
//
|
|
@@ -200,63 +200,61 @@ export function OrderDesk({
|
|
|
200
200
|
gridTicks: number[];
|
|
201
201
|
}) {
|
|
202
202
|
return (
|
|
203
|
-
//
|
|
204
|
-
// `height="fill"`
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
<
|
|
214
|
-
<
|
|
215
|
-
|
|
216
|
-
{/* maxLines={1}, not wrap="wrap": at 390 the 35px display-2 title
|
|
203
|
+
// Templates ship standalone, so the page owns the definite ancestor for
|
|
204
|
+
// `height="fill"` — 100dvh on the Layout itself, the same anchor every
|
|
205
|
+
// other page uses. It used to sit on a wrapper <div>, which contradicted
|
|
206
|
+
// the no-raw-div rule for a reason that does not hold: the height lands on
|
|
207
|
+
// the same box either way.
|
|
208
|
+
<Layout
|
|
209
|
+
style={pageStyle}
|
|
210
|
+
height="fill"
|
|
211
|
+
header={
|
|
212
|
+
<LayoutHeader hasDivider padding={6}>
|
|
213
|
+
<HStack gap={2} vAlign="center">
|
|
214
|
+
<StackItem size="fill">
|
|
215
|
+
{/* maxLines={1}, not wrap="wrap": at 390 the 35px display-2 title
|
|
217
216
|
wraps 2-3 lines in a 342px header (measured: 136px tall for
|
|
218
217
|
"Midnight Kicks", 180px for an 18-char title). Wrapping the
|
|
219
218
|
row measured IDENTICAL header heights in both configurations
|
|
220
219
|
— the controls already fit, the title is what is tall. And
|
|
221
220
|
maxLines wires core's useTruncation, so the full value stays
|
|
222
221
|
reachable in a Tooltip (SC 1.4.10, "truncation must reveal"). */}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
</div>
|
|
222
|
+
<Heading level={1} type="display-2" maxLines={1}>
|
|
223
|
+
{title}
|
|
224
|
+
</Heading>
|
|
225
|
+
</StackItem>
|
|
226
|
+
<IconButton
|
|
227
|
+
label="Filter"
|
|
228
|
+
icon={<Icon icon={Filter} size="sm" />}
|
|
229
|
+
variant="ghost"
|
|
230
|
+
tooltip="Filter"
|
|
231
|
+
/>
|
|
232
|
+
<IconButton
|
|
233
|
+
label="Export"
|
|
234
|
+
icon={<Icon icon={Download} size="sm" />}
|
|
235
|
+
variant="ghost"
|
|
236
|
+
tooltip="Export"
|
|
237
|
+
/>
|
|
238
|
+
<Button label="New order" variant="primary" icon={<Icon icon={Plus} size="sm" />} />
|
|
239
|
+
</HStack>
|
|
240
|
+
</LayoutHeader>
|
|
241
|
+
}
|
|
242
|
+
content={
|
|
243
|
+
<LayoutContent padding={3}>
|
|
244
|
+
<VStack gap={4}>
|
|
245
|
+
<RevenueChart data={revenueData} chartMax={chartMax} gridTicks={gridTicks} />
|
|
246
|
+
<Table<OrderDeskRow>
|
|
247
|
+
data={orders}
|
|
248
|
+
columns={columnsFor(selfHash, products)}
|
|
249
|
+
idKey="id"
|
|
250
|
+
density="balanced"
|
|
251
|
+
dividers="rows"
|
|
252
|
+
textOverflow="truncate"
|
|
253
|
+
hasHover
|
|
254
|
+
/>
|
|
255
|
+
</VStack>
|
|
256
|
+
</LayoutContent>
|
|
257
|
+
}
|
|
258
|
+
/>
|
|
261
259
|
);
|
|
262
260
|
}
|
package/shared/revenue-chart.tsx
CHANGED
|
@@ -169,7 +169,7 @@ export function RevenueChart({
|
|
|
169
169
|
y1={y}
|
|
170
170
|
x2={CHART_W - CHART_PAD_RIGHT}
|
|
171
171
|
y2={y}
|
|
172
|
-
stroke="var(--color-
|
|
172
|
+
stroke="var(--color-graph-gridlines)"
|
|
173
173
|
strokeDasharray={tick === 0 ? undefined : "3 3"}
|
|
174
174
|
opacity={tick === 0 ? 1 : 0.5}
|
|
175
175
|
/>
|
package/shared/scene-castle.tsx
CHANGED
|
@@ -23,13 +23,13 @@ const CARD_HILLS = {
|
|
|
23
23
|
|
|
24
24
|
const TALL_HILL = "M0 380 Q200 320 400 370 V500 H0 Z" as const;
|
|
25
25
|
|
|
26
|
-
function WideCastle() {
|
|
26
|
+
function WideCastle({ label }: { label?: string }) {
|
|
27
27
|
return (
|
|
28
28
|
<svg
|
|
29
29
|
style={galleryImage}
|
|
30
30
|
viewBox="0 0 800 450"
|
|
31
31
|
role="img"
|
|
32
|
-
aria-label="Moonlit castle on a hill in Dracula theme colors"
|
|
32
|
+
aria-label={label ?? "Moonlit castle on a hill in Dracula theme colors"}
|
|
33
33
|
>
|
|
34
34
|
<rect width="800" height="450" fill="var(--dracula-bg-dark)" />
|
|
35
35
|
{/* Stars */}
|
|
@@ -95,13 +95,13 @@ function WideCastle() {
|
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function CardCastle() {
|
|
98
|
+
function CardCastle({ label }: { label?: string }) {
|
|
99
99
|
return (
|
|
100
100
|
<svg
|
|
101
101
|
style={galleryImage}
|
|
102
102
|
viewBox="0 0 400 500"
|
|
103
103
|
role="img"
|
|
104
|
-
aria-label="Moonlit castle on midnight hills"
|
|
104
|
+
aria-label={label ?? "Moonlit castle on midnight hills"}
|
|
105
105
|
>
|
|
106
106
|
<rect width="400" height="500" fill="var(--dracula-bg-dark)" />
|
|
107
107
|
{/* Stars */}
|
|
@@ -142,13 +142,13 @@ function CardCastle() {
|
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
function TallPines() {
|
|
145
|
+
function TallPines({ label }: { label?: string }) {
|
|
146
146
|
return (
|
|
147
147
|
<svg
|
|
148
148
|
style={galleryImage}
|
|
149
149
|
viewBox="0 0 400 500"
|
|
150
150
|
role="img"
|
|
151
|
-
aria-label="Harvest moon over midnight pines"
|
|
151
|
+
aria-label={label ?? "Harvest moon over midnight pines"}
|
|
152
152
|
>
|
|
153
153
|
<rect width="400" height="500" fill="var(--dracula-bg-dark)" />
|
|
154
154
|
{/* Stars */}
|
|
@@ -186,14 +186,14 @@ function TallPines() {
|
|
|
186
186
|
);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
function Rooftops() {
|
|
189
|
+
function Rooftops({ label }: { label?: string }) {
|
|
190
190
|
return (
|
|
191
191
|
<svg
|
|
192
192
|
style={galleryImage}
|
|
193
193
|
viewBox="0 0 400 300"
|
|
194
194
|
preserveAspectRatio="xMidYMid slice"
|
|
195
195
|
role="img"
|
|
196
|
-
aria-label="Harvest moon over the castle rooftops"
|
|
196
|
+
aria-label={label ?? "Harvest moon over the castle rooftops"}
|
|
197
197
|
>
|
|
198
198
|
<rect width="400" height="300" fill="var(--dracula-bg)" />
|
|
199
199
|
<g fill="var(--dracula-fg)">
|
|
@@ -229,9 +229,17 @@ function Rooftops() {
|
|
|
229
229
|
);
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
-
export function SceneCastle({
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
232
|
+
export function SceneCastle({
|
|
233
|
+
variant,
|
|
234
|
+
label,
|
|
235
|
+
}: {
|
|
236
|
+
variant: SceneCastleVariant;
|
|
237
|
+
// Callers that keep their own alt text pass it here, so the accessible name
|
|
238
|
+
// cannot drift from the data the way a hardcoded label lets it.
|
|
239
|
+
label?: string;
|
|
240
|
+
}) {
|
|
241
|
+
if (variant === "wide") return <WideCastle label={label} />;
|
|
242
|
+
if (variant === "card") return <CardCastle label={label} />;
|
|
243
|
+
if (variant === "rooftops") return <Rooftops label={label} />;
|
|
244
|
+
return <TallPines label={label} />;
|
|
237
245
|
}
|
|
@@ -295,15 +295,10 @@ const INITIAL_ATTACHMENTS: Attachment[] = [
|
|
|
295
295
|
"dracula_spec.pdf",
|
|
296
296
|
].map((name) => ({ id: crypto.randomUUID(), name }));
|
|
297
297
|
|
|
298
|
-
// The composer's imperative insert methods mutate the DOM without emitting a
|
|
299
|
-
// change, so dispatch an input event to sync its value and clear the placeholder.
|
|
300
|
-
const syncComposerValue = () => {
|
|
301
|
-
document.activeElement?.dispatchEvent(new Event("input", { bubbles: true }));
|
|
302
|
-
};
|
|
303
|
-
|
|
304
298
|
// One inserter for every composer write. `insertText` replaces the selection
|
|
305
299
|
// (suggestion prompts); `insertToken` inserts a chip at the cursor without
|
|
306
|
-
// touching the selection (mentions, mode tokens).
|
|
300
|
+
// touching the selection (mentions, mode tokens). Both emit a change in 0.7.10,
|
|
301
|
+
// so no input-event sync is needed after either call.
|
|
307
302
|
function insertIntoComposer(
|
|
308
303
|
input: ChatComposerInputHandle | null,
|
|
309
304
|
write: { text: string } | { token: ChatComposerToken },
|
|
@@ -327,7 +322,6 @@ function insertIntoComposer(
|
|
|
327
322
|
} else {
|
|
328
323
|
input.insertToken(write.token);
|
|
329
324
|
}
|
|
330
|
-
syncComposerValue();
|
|
331
325
|
}
|
|
332
326
|
|
|
333
327
|
// Main component
|
|
@@ -535,7 +529,10 @@ export default function AiChatLanding() {
|
|
|
535
529
|
<ClickableCard
|
|
536
530
|
key={suggestion.heading}
|
|
537
531
|
label={suggestion.heading}
|
|
538
|
-
|
|
532
|
+
// Raised tappable card. `muted` is a recessed well since
|
|
533
|
+
// the 2026-11 token change, so it would read as a hole
|
|
534
|
+
// in the page rather than a prompt you can press.
|
|
535
|
+
variant="default"
|
|
539
536
|
padding={4}
|
|
540
537
|
onClick={() => {
|
|
541
538
|
applySuggestion(suggestion.prompt);
|
package/templates/ai-chat.tsx
CHANGED
|
@@ -231,7 +231,9 @@ function ArtifactCard({ onOpen }: { onOpen: () => void }) {
|
|
|
231
231
|
<ClickableCard
|
|
232
232
|
label={`Open ${ARTIFACT_TITLE}`}
|
|
233
233
|
onClick={onOpen}
|
|
234
|
-
|
|
234
|
+
// A raised tappable chip, not a well: `muted` is #21222C since the
|
|
235
|
+
// 2026-11 token change and would sink this card below the chat body.
|
|
236
|
+
variant="default"
|
|
235
237
|
padding={3}
|
|
236
238
|
maxWidth={360}
|
|
237
239
|
style={artifactCard}
|
|
@@ -374,6 +376,7 @@ export default function AiChat() {
|
|
|
374
376
|
</ChatMessageBubble>
|
|
375
377
|
<ChatToolCalls
|
|
376
378
|
defaultIsExpanded
|
|
379
|
+
label="Explored the auth files"
|
|
377
380
|
calls={[
|
|
378
381
|
{
|
|
379
382
|
name: "read",
|
|
@@ -480,6 +483,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
480
483
|
<ChatMessage sender="assistant" avatar={<Avatar name="Agent" size="md" />}>
|
|
481
484
|
<ChatToolCalls
|
|
482
485
|
defaultIsExpanded
|
|
486
|
+
label="Added and ran the refresh test"
|
|
483
487
|
calls={[
|
|
484
488
|
{
|
|
485
489
|
name: "read",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
3
|
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
4
|
// human drift check — update it with the file, never alone):
|
|
5
|
-
// L[h=auto] > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.
|
|
5
|
+
// L[h=auto] > LC[p=6] > V[g=10] > ((V[g=6 a=center] > (V[g=3 a=center] > Hd"Small joys for creatures of the night"[level=1 t=display-2] + Tx"Sometimes all it takes is one small ritual to turn the whole night around."[t=body] + Tx"astryx-dracula v0.7.0"[t=supporting]) + (H[j=center g=3] > B.primary"Embrace the night" + B.secondary"Read the lore")) + (S.transparent[p=0] > AR[ratio=16/9] > Scene{SceneCastle}))
|
|
6
6
|
|
|
7
7
|
import { AspectRatio } from "@astryxdesign/core/AspectRatio";
|
|
8
8
|
import { Button } from "@astryxdesign/core/Button";
|
|
@@ -41,7 +41,7 @@ export default function CenteredHero() {
|
|
|
41
41
|
Sometimes all it takes is one small ritual to turn the whole night around.
|
|
42
42
|
</Text>
|
|
43
43
|
<Text type="supporting" color="secondary" justify="center" hasTabularNumbers>
|
|
44
|
-
astryx-dracula v0.
|
|
44
|
+
astryx-dracula v0.7.0
|
|
45
45
|
</Text>
|
|
46
46
|
</VStack>
|
|
47
47
|
<HStack gap={3} wrap="wrap" justify="center">
|