astryx-dracula 0.5.1 → 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 +57 -6
- package/README.md +16 -6
- package/USAGE.md +50 -50
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +49 -11
- package/astryx-dracula.variants.d.ts +2 -2
- package/icons.mjs +4 -0
- package/icons.tsx +8 -0
- package/package.json +23 -18
- 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/chaptered-doc.tsx +1 -2
- package/shared/chart-hues.ts +21 -6
- package/shared/chart-legend.tsx +2 -2
- package/shared/data-bar.tsx +2 -2
- package/shared/order-desk.tsx +52 -54
- package/shared/revenue-chart.tsx +1 -1
- package/shared/scene-castle.tsx +21 -13
- package/shared/settings-data.ts +0 -6
- package/shared/settings-rows.tsx +1 -23
- package/templates/ai-chat-landing.template.mjs +1 -0
- package/templates/ai-chat-landing.tsx +30 -11
- package/templates/ai-chat.template.mjs +1 -0
- package/templates/ai-chat.tsx +9 -3
- package/templates/blank.template.mjs +1 -0
- package/templates/blank.tsx +4 -2
- package/templates/centered-hero.template.mjs +1 -0
- package/templates/centered-hero.tsx +5 -3
- package/templates/classic-gallery.template.mjs +1 -0
- package/templates/classic-gallery.tsx +9 -2
- package/templates/contact-form.template.mjs +1 -0
- package/templates/contact-form.tsx +13 -2
- package/templates/dashboard-portfolio.template.mjs +1 -0
- package/templates/dashboard-portfolio.tsx +21 -6
- package/templates/dashboard.template.mjs +1 -0
- package/templates/dashboard.tsx +9 -7
- package/templates/detail-page.template.mjs +1 -0
- package/templates/detail-page.tsx +28 -36
- package/templates/documentation-design.template.mjs +1 -0
- package/templates/documentation-design.tsx +8 -3
- package/templates/documentation-technical.template.mjs +1 -0
- package/templates/documentation-technical.tsx +11 -6
- package/templates/documentation.template.mjs +1 -0
- package/templates/documentation.tsx +16 -7
- package/templates/editor.template.mjs +1 -0
- package/templates/editor.tsx +4 -2
- package/templates/file-explorer.template.mjs +1 -0
- package/templates/file-explorer.tsx +4 -2
- package/templates/form-two-column.template.mjs +1 -0
- package/templates/form-two-column.tsx +19 -4
- package/templates/form-wizard.template.mjs +15 -0
- package/templates/form-wizard.tsx +878 -0
- package/templates/gallery-hero.template.mjs +1 -0
- package/templates/gallery-hero.tsx +14 -6
- package/templates/ide.template.mjs +1 -0
- package/templates/ide.tsx +187 -101
- package/templates/incident-console.template.mjs +1 -0
- package/templates/incident-console.tsx +137 -85
- package/templates/kanban-board.template.mjs +1 -0
- package/templates/kanban-board.tsx +51 -7
- package/templates/library.template.mjs +1 -0
- package/templates/library.tsx +15 -4
- package/templates/login-card.template.mjs +1 -0
- package/templates/login-card.tsx +4 -2
- package/templates/login-split.template.mjs +1 -0
- package/templates/login-split.tsx +8 -5
- package/templates/login-sso.template.mjs +1 -0
- package/templates/login-sso.tsx +13 -19
- package/templates/login.template.mjs +1 -0
- package/templates/login.tsx +4 -2
- package/templates/messaging-shell.template.mjs +1 -0
- package/templates/messaging-shell.tsx +114 -47
- package/templates/mixed-gallery.template.mjs +1 -0
- package/templates/mixed-gallery.tsx +10 -2
- package/templates/payment-form.template.mjs +1 -0
- package/templates/payment-form.tsx +34 -31
- package/templates/product-detail.template.mjs +1 -0
- package/templates/product-detail.tsx +21 -5
- package/templates/product-gallery.template.mjs +1 -0
- package/templates/product-gallery.tsx +12 -5
- package/templates/product-tour.template.mjs +1 -0
- package/templates/product-tour.tsx +21 -14
- package/templates/settings-dialog.template.mjs +1 -0
- package/templates/settings-dialog.tsx +10 -3
- package/templates/settings-sidebar.template.mjs +1 -0
- package/templates/settings-sidebar.tsx +609 -588
- package/templates/settings.template.mjs +1 -0
- package/templates/settings.tsx +11 -12
- package/templates/shell-nav.template.mjs +1 -0
- package/templates/shell-nav.tsx +47 -29
- package/templates/shell-side-nav.template.mjs +1 -0
- package/templates/shell-side-nav.tsx +88 -70
- package/templates/shell-top-nav.template.mjs +1 -0
- package/templates/shell-top-nav.tsx +102 -88
- package/templates/side-gallery.template.mjs +1 -0
- package/templates/side-gallery.tsx +4 -2
- package/templates/table-grouped.template.mjs +1 -0
- package/templates/table-grouped.tsx +150 -134
- package/templates/table-page-chart.template.mjs +1 -0
- package/templates/table-page-chart.tsx +4 -2
- package/templates/table-page-heatmap-status.template.mjs +1 -0
- package/templates/table-page-heatmap-status.tsx +4 -2
- package/templates/{table-page-shoe-store-heatmap.template.mjs → table-page-shoe-store.template.mjs} +1 -0
- package/templates/{table-page-shoe-store-heatmap.tsx → table-page-shoe-store.tsx} +5 -3
- package/templates/table-page.template.mjs +1 -0
- package/templates/table-page.tsx +12 -2
- package/templates/table-tree.template.mjs +16 -0
- package/templates/table-tree.tsx +707 -0
- package/templates/table.template.mjs +1 -0
- package/templates/table.tsx +4 -2
- package/templates/tech-report.template.mjs +1 -0
- package/templates/tech-report.tsx +18 -13
- package/templates/theme-showcase.template.mjs +1 -0
- package/templates/theme-showcase.tsx +41 -28
- package/templates/work-item-detail.template.mjs +8 -0
- package/templates/work-item-detail.tsx +1049 -0
- package/theme.css +71 -78
- package/theme.css.d.ts +9 -0
- package/tokens.css +79 -46
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/chaptered-doc.tsx
CHANGED
|
@@ -11,7 +11,6 @@ import { Button } from "@astryxdesign/core/Button";
|
|
|
11
11
|
import { CodeBlock } from "@astryxdesign/core/CodeBlock";
|
|
12
12
|
import { Divider } from "@astryxdesign/core/Divider";
|
|
13
13
|
import { useMediaQuery } from "@astryxdesign/core/hooks";
|
|
14
|
-
import type { IconType } from "@astryxdesign/core/Icon";
|
|
15
14
|
import { Icon } from "@astryxdesign/core/Icon";
|
|
16
15
|
import {
|
|
17
16
|
HStack,
|
|
@@ -39,7 +38,7 @@ import {
|
|
|
39
38
|
sectionId,
|
|
40
39
|
} from "astryx-dracula/shared/chaptered-doc-config";
|
|
41
40
|
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
42
|
-
import { type
|
|
41
|
+
import { type ReactNode, useState } from "react";
|
|
43
42
|
|
|
44
43
|
function ChapterRail({
|
|
45
44
|
groups,
|
package/shared/chart-hues.ts
CHANGED
|
@@ -5,18 +5,33 @@
|
|
|
5
5
|
// Purple is deliberately absent: purple means tappable, so it must never encode
|
|
6
6
|
// data. `muted` is a COMMENT-blue grey because it backs the `{label: 'Other'}`
|
|
7
7
|
// legend row (dashboard.tsx:229, :253) -- a labelled data category, not chrome.
|
|
8
|
+
//
|
|
9
|
+
// Every var() carries an explicit fallback equal to the theme's own pin for
|
|
10
|
+
// that token. Core moved the canonical `--color-data-*` defaults out of the
|
|
11
|
+
// generated theme CSS and into its own dataVars group, so a bare
|
|
12
|
+
// `var(--color-data-*)` is no longer guaranteed to resolve: the theme's pin is
|
|
13
|
+
// the only thing painting it. The fallback is the same value, so a theme that
|
|
14
|
+
// drops the token still paints the right colour instead of nothing. The
|
|
15
|
+
// fallback-vs-pin sync is enforced by scripts/check.ts, so the two cannot drift.
|
|
8
16
|
|
|
9
17
|
export const CHART_HUES = {
|
|
10
|
-
cyan: "var(--color-data-categorical-cyan)",
|
|
11
|
-
orange: "var(--color-data-categorical-orange)",
|
|
12
|
-
green: "var(--color-data-categorical-green)",
|
|
13
|
-
pink: "var(--color-data-categorical-pink)",
|
|
14
|
-
muted: "var(--color-data-categorical-blue)",
|
|
18
|
+
cyan: "var(--color-data-categorical-cyan, #8BE9FD)",
|
|
19
|
+
orange: "var(--color-data-categorical-orange, #FFB86C)",
|
|
20
|
+
green: "var(--color-data-categorical-green, #50FA7B)",
|
|
21
|
+
pink: "var(--color-data-categorical-pink, #FF79C6)",
|
|
22
|
+
muted: "var(--color-data-categorical-blue, #6272A4)",
|
|
15
23
|
// Added because --color-data-categorical-red exists in the palette
|
|
16
24
|
// (tokens.css:276) and heat maps need a severity colour, but no module
|
|
17
25
|
// exposed it. Without this, a chart could not reach red through the
|
|
18
26
|
// sanctioned source at all.
|
|
19
|
-
red: "var(--color-data-categorical-red)",
|
|
27
|
+
red: "var(--color-data-categorical-red, #FF5555)",
|
|
20
28
|
} as const;
|
|
21
29
|
|
|
22
30
|
export type ChartHue = (typeof CHART_HUES)[keyof typeof CHART_HUES];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Any `--color-data-*` colour: a categorical hue, or a step of a sequential
|
|
34
|
+
* ramp. The fallback inside the var() is part of the string, which is why the
|
|
35
|
+
* template-literal type below still admits it.
|
|
36
|
+
*/
|
|
37
|
+
export type DataColor = `var(--color-data-${string})`;
|
package/shared/chart-legend.tsx
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { HStack, StackItem, VStack } from "@astryxdesign/core/Stack";
|
|
25
25
|
import { Text } from "@astryxdesign/core/Text";
|
|
26
|
-
import type { ChartHue } from "astryx-dracula/shared/chart-hues";
|
|
26
|
+
import type { ChartHue, DataColor } from "astryx-dracula/shared/chart-hues";
|
|
27
27
|
import type { ReactNode } from "react";
|
|
28
28
|
|
|
29
29
|
export interface ChartLegendEntry {
|
|
@@ -35,7 +35,7 @@ export interface ChartLegendEntry {
|
|
|
35
35
|
* The surface arm is not optional: a legend whose first row is a recessive
|
|
36
36
|
* surface is normal (the heatmap's zero cell), so a data-only type would
|
|
37
37
|
* reject the value the heatmap is supposed to paint. */
|
|
38
|
-
color: ChartHue |
|
|
38
|
+
color: ChartHue | DataColor | `var(--color-background-${string})`;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Kept as a string-keyed fall-through on purpose, and DO NOT narrow it: call
|
package/shared/data-bar.tsx
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
import { HStack, StackItem, VStack } from "@astryxdesign/core/Stack";
|
|
40
40
|
import { Text } from "@astryxdesign/core/Text";
|
|
41
41
|
import type { CSSProperties } from "react";
|
|
42
|
-
import type { ChartHue } from "./chart-hues";
|
|
42
|
+
import type { ChartHue, DataColor } from "./chart-hues";
|
|
43
43
|
|
|
44
44
|
export interface DataBarSegment {
|
|
45
45
|
/** Stable key for the segment. */
|
|
@@ -48,7 +48,7 @@ export interface DataBarSegment {
|
|
|
48
48
|
/** MUST be a --color-data-categorical-* or --color-data-<family>-N var.
|
|
49
49
|
* Never a raw hex, never a --dracula-* primitive. Typed rather than
|
|
50
50
|
* documented: `string` made the rule a comment nobody could break. */
|
|
51
|
-
color: ChartHue |
|
|
51
|
+
color: ChartHue | DataColor;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Minimum segment share, so a tiny non-zero value stays visible. */
|
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
|
}
|
package/shared/settings-data.ts
CHANGED
|
@@ -12,18 +12,12 @@ import {
|
|
|
12
12
|
Briefcase,
|
|
13
13
|
CreditCard,
|
|
14
14
|
FileText,
|
|
15
|
-
Fingerprint,
|
|
16
15
|
Globe,
|
|
17
|
-
KeyRound,
|
|
18
|
-
Laptop,
|
|
19
16
|
Lock,
|
|
20
17
|
Share2,
|
|
21
18
|
ShieldCheck,
|
|
22
|
-
Smartphone,
|
|
23
19
|
SquarePen,
|
|
24
|
-
Tablet,
|
|
25
20
|
User,
|
|
26
|
-
UserCog,
|
|
27
21
|
} from "lucide-react";
|
|
28
22
|
import type { CSSProperties } from "react";
|
|
29
23
|
|
package/shared/settings-rows.tsx
CHANGED
|
@@ -8,29 +8,7 @@ import { Divider } from "@astryxdesign/core/Divider";
|
|
|
8
8
|
import { HStack, VStack } from "@astryxdesign/core/Layout";
|
|
9
9
|
import { Link } from "@astryxdesign/core/Link";
|
|
10
10
|
import { Text } from "@astryxdesign/core/Text";
|
|
11
|
-
import {
|
|
12
|
-
actionNoWrap,
|
|
13
|
-
DEVICE_ROWS,
|
|
14
|
-
type DeviceRow,
|
|
15
|
-
INFO_TILES,
|
|
16
|
-
type InfoRow,
|
|
17
|
-
type InfoTileData,
|
|
18
|
-
iconBox,
|
|
19
|
-
LOGIN_ROWS,
|
|
20
|
-
type SettingsNavItem,
|
|
21
|
-
SOCIAL_ROWS,
|
|
22
|
-
} from "astryx-dracula/shared/settings-data";
|
|
23
|
-
import {
|
|
24
|
-
Bell,
|
|
25
|
-
Briefcase,
|
|
26
|
-
CreditCard,
|
|
27
|
-
FileText,
|
|
28
|
-
Globe,
|
|
29
|
-
Lock,
|
|
30
|
-
Share2,
|
|
31
|
-
ShieldCheck,
|
|
32
|
-
SquarePen,
|
|
33
|
-
} from "lucide-react";
|
|
11
|
+
import { actionNoWrap, type InfoRow } from "astryx-dracula/shared/settings-data";
|
|
34
12
|
import type { CSSProperties, ReactNode } from "react";
|
|
35
13
|
|
|
36
14
|
// ─── Row components ──────────────────────────────────────────────────────────
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
// XLE (canonical structure
|
|
3
|
-
//
|
|
2
|
+
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
|
+
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
|
+
// human drift check — update it with the file, never alone):
|
|
5
|
+
// L[h=fill mw=720] > LC > V[g=8 a=center] > (V[g=1] > (H[g=2 a=center] > Ic + Tx"Hi, Vlad"[t=body]) + Hd"Where should we start?"[level=1 type=display-2]) + ChC"Ask anything" + (V[g=6] > (TgG"Category" > H[g=1 wrap] > Tg"Writing"*4) + VH"Suggested prompts"[as=h2] + (G[c={min:280} g=3] > (CC[p=4] > V[g=0.5] > Hd"Draft"[level=3] + Tx"Compose"[t=body])*4))
|
|
4
6
|
|
|
5
7
|
import {
|
|
6
8
|
ChatComposer,
|
|
@@ -215,15 +217,23 @@ function makeTrigger<TAux>({
|
|
|
215
217
|
items,
|
|
216
218
|
getDescription,
|
|
217
219
|
toToken,
|
|
220
|
+
hasMultiWordQuery,
|
|
221
|
+
emptySearchText,
|
|
218
222
|
}: {
|
|
219
223
|
character: string;
|
|
220
224
|
items: SearchableItem<TAux>[];
|
|
221
225
|
getDescription: (aux: TAux | undefined) => string | undefined;
|
|
222
226
|
toToken: (item: SearchableItem<TAux>) => string | ChatComposerToken;
|
|
227
|
+
/** Continue the query past spaces, for names that contain them. */
|
|
228
|
+
hasMultiWordQuery?: boolean;
|
|
229
|
+
/** Shown when the query matched nothing. */
|
|
230
|
+
emptySearchText?: string;
|
|
223
231
|
}): ChatComposerTrigger {
|
|
224
232
|
return {
|
|
225
233
|
character,
|
|
226
234
|
searchSource: createStaticSource(items),
|
|
235
|
+
...(hasMultiWordQuery ? { hasMultiWordQuery } : {}),
|
|
236
|
+
...(emptySearchText ? { emptySearchText } : {}),
|
|
227
237
|
renderItem: (item) => (
|
|
228
238
|
<TypeaheadItem
|
|
229
239
|
item={item}
|
|
@@ -238,6 +248,13 @@ const mentionTrigger = makeTrigger({
|
|
|
238
248
|
character: "@",
|
|
239
249
|
items: MENTION_ITEMS,
|
|
240
250
|
getDescription: (aux) => aux?.role,
|
|
251
|
+
// The mention labels are full names ("Vesper Lin", "Alex Nocturne"), so
|
|
252
|
+
// the query has to survive the space: without this the menu closes at the
|
|
253
|
+
// first space and "@Vesper L" narrows to "Vesper" and then matches
|
|
254
|
+
// nothing. Core hides the menu on an unmatched multi-word query unless
|
|
255
|
+
// the trigger sets emptySearchText, so both go together.
|
|
256
|
+
hasMultiWordQuery: true,
|
|
257
|
+
emptySearchText: "No one by that name tonight.",
|
|
241
258
|
toToken: (item) => ({
|
|
242
259
|
value: `@${item.id}`,
|
|
243
260
|
label: item.label,
|
|
@@ -257,6 +274,11 @@ const commandTrigger = makeTrigger({
|
|
|
257
274
|
});
|
|
258
275
|
|
|
259
276
|
const composerTriggers = [mentionTrigger, commandTrigger];
|
|
277
|
+
// The upstream `rename emptySearchResultsText to emptySearchText` codemod
|
|
278
|
+
// leaves a TODO on any ChatComposerInput whose `triggers` value it cannot
|
|
279
|
+
// attribute to a literal object — these are built by `makeTrigger`, so it
|
|
280
|
+
// cannot see inside them. The old key appears nowhere in this file, so that
|
|
281
|
+
// TODO would name a rename this template does not need.
|
|
260
282
|
|
|
261
283
|
/** Composer attachment. The name is user-facing; the id is the React key, since
|
|
262
284
|
* two dropped files can share a name. */
|
|
@@ -273,15 +295,10 @@ const INITIAL_ATTACHMENTS: Attachment[] = [
|
|
|
273
295
|
"dracula_spec.pdf",
|
|
274
296
|
].map((name) => ({ id: crypto.randomUUID(), name }));
|
|
275
297
|
|
|
276
|
-
// The composer's imperative insert methods mutate the DOM without emitting a
|
|
277
|
-
// change, so dispatch an input event to sync its value and clear the placeholder.
|
|
278
|
-
const syncComposerValue = () => {
|
|
279
|
-
document.activeElement?.dispatchEvent(new Event("input", { bubbles: true }));
|
|
280
|
-
};
|
|
281
|
-
|
|
282
298
|
// One inserter for every composer write. `insertText` replaces the selection
|
|
283
299
|
// (suggestion prompts); `insertToken` inserts a chip at the cursor without
|
|
284
|
-
// 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.
|
|
285
302
|
function insertIntoComposer(
|
|
286
303
|
input: ChatComposerInputHandle | null,
|
|
287
304
|
write: { text: string } | { token: ChatComposerToken },
|
|
@@ -305,7 +322,6 @@ function insertIntoComposer(
|
|
|
305
322
|
} else {
|
|
306
323
|
input.insertToken(write.token);
|
|
307
324
|
}
|
|
308
|
-
syncComposerValue();
|
|
309
325
|
}
|
|
310
326
|
|
|
311
327
|
// Main component
|
|
@@ -513,7 +529,10 @@ export default function AiChatLanding() {
|
|
|
513
529
|
<ClickableCard
|
|
514
530
|
key={suggestion.heading}
|
|
515
531
|
label={suggestion.heading}
|
|
516
|
-
|
|
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"
|
|
517
536
|
padding={4}
|
|
518
537
|
onClick={() => {
|
|
519
538
|
applySuggestion(suggestion.prompt);
|
package/templates/ai-chat.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
// XLE (canonical structure
|
|
3
|
-
//
|
|
2
|
+
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
|
+
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
|
+
// human drift check — update it with the file, never alone):
|
|
5
|
+
// L > LC[p=0] > H > (V > ChL > (VH"Night thread"[as=h1] + ChML > (ChSM"Today" + (ChM > ChBu)*8 + ChSM"Changes sealed in the crypt") + ChC"Ask the night anything...")) + (RH + C.transparent > Tbar + S.transparent > (V[g=2] > Hd"JWT Token Refresh: Design & Rollout"[level=2 t=display-2] + MD)) ;; Dlg#artifact[variant=fullscreen] > L > (DH"JWT Token Refresh" + (LC[p=0] > S.transparent > (V[g=2] > Hd"JWT Token Refresh: Design & Rollout"[level=2 t=display-2] + MD)))
|
|
4
6
|
|
|
5
7
|
import { Avatar } from "@astryxdesign/core/Avatar";
|
|
6
8
|
import { Button } from "@astryxdesign/core/Button";
|
|
@@ -229,7 +231,9 @@ function ArtifactCard({ onOpen }: { onOpen: () => void }) {
|
|
|
229
231
|
<ClickableCard
|
|
230
232
|
label={`Open ${ARTIFACT_TITLE}`}
|
|
231
233
|
onClick={onOpen}
|
|
232
|
-
|
|
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"
|
|
233
237
|
padding={3}
|
|
234
238
|
maxWidth={360}
|
|
235
239
|
style={artifactCard}
|
|
@@ -372,6 +376,7 @@ export default function AiChat() {
|
|
|
372
376
|
</ChatMessageBubble>
|
|
373
377
|
<ChatToolCalls
|
|
374
378
|
defaultIsExpanded
|
|
379
|
+
label="Explored the auth files"
|
|
375
380
|
calls={[
|
|
376
381
|
{
|
|
377
382
|
name: "read",
|
|
@@ -478,6 +483,7 @@ The fix is to catch \`TokenExpiredError\` specifically and attempt a refresh bef
|
|
|
478
483
|
<ChatMessage sender="assistant" avatar={<Avatar name="Agent" size="md" />}>
|
|
479
484
|
<ChatToolCalls
|
|
480
485
|
defaultIsExpanded
|
|
486
|
+
label="Added and ran the refresh test"
|
|
481
487
|
calls={[
|
|
482
488
|
{
|
|
483
489
|
name: "read",
|
package/templates/blank.tsx
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
// XLE (canonical structure
|
|
3
|
-
//
|
|
2
|
+
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
|
+
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
4
|
+
// human drift check — update it with the file, never alone):
|
|
5
|
+
// L[h=auto] > LC > Hd"A blank crypt awaits its story"[level=1 t=display-2]
|
|
4
6
|
|
|
5
7
|
import { Layout, LayoutContent } from "@astryxdesign/core/Layout";
|
|
6
8
|
import { Heading } from "@astryxdesign/core/Text";
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
// XLE (canonical structure
|
|
3
|
-
//
|
|
2
|
+
// XLE (canonical structure of the tree below; the CLI had no XLE validator
|
|
3
|
+
// after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
|
|
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.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}))
|
|
4
6
|
|
|
5
7
|
import { AspectRatio } from "@astryxdesign/core/AspectRatio";
|
|
6
8
|
import { Button } from "@astryxdesign/core/Button";
|
|
@@ -39,7 +41,7 @@ export default function CenteredHero() {
|
|
|
39
41
|
Sometimes all it takes is one small ritual to turn the whole night around.
|
|
40
42
|
</Text>
|
|
41
43
|
<Text type="supporting" color="secondary" justify="center" hasTabularNumbers>
|
|
42
|
-
astryx-dracula v0.
|
|
44
|
+
astryx-dracula v0.7.0
|
|
43
45
|
</Text>
|
|
44
46
|
</VStack>
|
|
45
47
|
<HStack gap={3} wrap="wrap" justify="center">
|