astryx-dracula 0.4.0 → 0.5.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/package.json +6 -2
- package/shared/auth-chrome.tsx +156 -6
- package/shared/chaptered-doc.tsx +3 -4
- package/shared/chart-hues.ts +0 -5
- package/shared/chart-legend.tsx +2 -3
- package/shared/data-bar.tsx +5 -5
- package/shared/format-money.ts +3 -0
- package/shared/metric-card.tsx +127 -0
- package/shared/scene-hues.ts +0 -8
- package/shared/scene-tile.tsx +3 -11
- package/shared/settings-data.ts +28 -0
- package/shared/settings-rows.tsx +1 -6
- package/shared/stars.tsx +28 -0
- package/templates/ai-chat-landing.tsx +88 -58
- package/templates/ai-chat.tsx +65 -78
- package/templates/centered-hero.tsx +2 -2
- package/templates/dashboard-portfolio.tsx +77 -155
- package/templates/dashboard.tsx +17 -134
- package/templates/detail-page.tsx +14 -18
- package/templates/documentation-design.tsx +66 -519
- package/templates/documentation-technical.tsx +2 -22
- package/templates/editor.tsx +168 -185
- package/templates/file-explorer.tsx +10 -24
- package/templates/ide.tsx +12 -18
- package/templates/incident-console.tsx +48 -46
- package/templates/kanban-board.tsx +36 -85
- package/templates/library.tsx +42 -66
- package/templates/login-card.tsx +33 -126
- package/templates/login-split.tsx +91 -123
- package/templates/login-sso.tsx +12 -33
- package/templates/login.tsx +21 -121
- package/templates/messaging-shell.tsx +162 -168
- package/templates/payment-form.tsx +24 -26
- package/templates/product-detail.tsx +7 -40
- package/templates/product-tour.tsx +38 -60
- package/templates/settings-dialog.tsx +10 -32
- package/templates/settings-sidebar.tsx +44 -77
- package/templates/settings.tsx +208 -121
- package/templates/shell-nav.tsx +20 -75
- package/templates/shell-side-nav.tsx +16 -90
- package/templates/shell-top-nav.tsx +20 -53
- package/templates/side-gallery.tsx +19 -19
- package/templates/table-grouped.tsx +17 -164
- package/templates/table-page-chart.tsx +12 -11
- package/templates/table-page-heatmap-status.tsx +0 -17
- package/templates/table-page-shoe-store-heatmap.tsx +12 -12
- package/templates/table-page.tsx +4 -31
- package/templates/table.tsx +7 -10
- package/templates/tech-report.tsx +58 -72
- package/templates/theme-showcase.tsx +124 -415
package/package.json
CHANGED
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
"./shared/settings-data": "./shared/settings-data.ts",
|
|
42
42
|
"./shared/settings-rows": "./shared/settings-rows.tsx",
|
|
43
43
|
"./shared/sparkline": "./shared/sparkline.tsx",
|
|
44
|
+
"./shared/format-money": "./shared/format-money.ts",
|
|
45
|
+
"./shared/metric-card": "./shared/metric-card.tsx",
|
|
46
|
+
"./shared/stars": "./shared/stars.tsx",
|
|
44
47
|
"./shared/sso-icons": "./shared/sso-icons.tsx",
|
|
45
48
|
".": {
|
|
46
49
|
"types": "./astryx-dracula.d.ts",
|
|
@@ -92,7 +95,8 @@
|
|
|
92
95
|
"url": "git+https://github.com/yuzu-octopus/astryx-dracula.git"
|
|
93
96
|
},
|
|
94
97
|
"scripts": {
|
|
95
|
-
"audit": "bun scripts/check.ts",
|
|
98
|
+
"audit": "bun run fonts:sync && bun scripts/check.ts",
|
|
99
|
+
"fonts:sync": "mkdir -p public/fonts && cp fonts/*.woff2 public/fonts/",
|
|
96
100
|
"build": "tsc --noEmit && vite build",
|
|
97
101
|
"dev": "vite",
|
|
98
102
|
"preview": "vite preview",
|
|
@@ -111,7 +115,7 @@
|
|
|
111
115
|
"@astryxdesign/cli"
|
|
112
116
|
],
|
|
113
117
|
"type": "module",
|
|
114
|
-
"version": "0.
|
|
118
|
+
"version": "0.5.0",
|
|
115
119
|
"patchedDependencies": {
|
|
116
120
|
"@astryxdesign/core@0.6.3": "patches/@astryxdesign%2Fcore@0.6.3.patch"
|
|
117
121
|
}
|
package/shared/auth-chrome.tsx
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
// Shared login chrome: page wash + 400px column + Moon brand row + the
|
|
2
2
|
// autocomplete widen-record TextInput needs (prop type omits input attrs).
|
|
3
|
-
import type {
|
|
3
|
+
import type {ReactNode} from 'react';
|
|
4
4
|
import {Moon} from 'lucide-react';
|
|
5
5
|
import {VStack} from '@astryxdesign/core/Layout';
|
|
6
6
|
import {Text} from '@astryxdesign/core/Text';
|
|
7
7
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
8
|
-
import {
|
|
8
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
9
|
+
import {Link} from '@astryxdesign/core/Link';
|
|
10
|
+
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
11
|
+
import {Button} from '@astryxdesign/core/Button';
|
|
12
|
+
import {Heading} from '@astryxdesign/core/Text';
|
|
9
13
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
AUTH_BRAND_NAME,
|
|
15
|
+
AUTH_HEADING,
|
|
16
|
+
AUTH_SUBTITLE,
|
|
17
|
+
AUTH_PRIMARY_CTA,
|
|
18
|
+
AUTH_SIGNUP_PROMPT,
|
|
19
|
+
AUTH_SIGNUP_LINK,
|
|
20
|
+
AUTH_FORGOT_PASSWORD,
|
|
21
|
+
AUTH_EMAIL_PLACEHOLDER,
|
|
22
|
+
AUTH_PASSWORD_PLACEHOLDER,
|
|
23
|
+
AUTH_TERMS_PREFIX,
|
|
24
|
+
AUTH_TERMS_SERVICE,
|
|
25
|
+
AUTH_TERMS_PRIVACY,
|
|
26
|
+
} from 'astryx-dracula/shared/auth-copy';
|
|
27
|
+
import {inputAutoComplete} from 'astryx-dracula/shared/auth-chrome-config';
|
|
14
28
|
|
|
15
29
|
|
|
16
30
|
export function LoginBrand() {
|
|
@@ -23,3 +37,139 @@ export function LoginBrand() {
|
|
|
23
37
|
</VStack>
|
|
24
38
|
);
|
|
25
39
|
}
|
|
40
|
+
|
|
41
|
+
// Shared password-login core: the centered header, credential fields, primary
|
|
42
|
+
// CTA, signup line and terms line every password page in the family carries.
|
|
43
|
+
// The three pages differed only in what sits between the CTA and the signup
|
|
44
|
+
// line (nothing, or the SSO divider + provider buttons) and in their outer
|
|
45
|
+
// shell — login-split's two-column grid and login-sso's step machine keep
|
|
46
|
+
// their own markup and take the pieces below instead of this whole card.
|
|
47
|
+
export function AuthCard({
|
|
48
|
+
selfHash,
|
|
49
|
+
signupLink = AUTH_SIGNUP_LINK,
|
|
50
|
+
children,
|
|
51
|
+
}: {
|
|
52
|
+
selfHash: string;
|
|
53
|
+
signupLink?: string;
|
|
54
|
+
children: ReactNode;
|
|
55
|
+
}) {
|
|
56
|
+
return (
|
|
57
|
+
<>
|
|
58
|
+
<Card padding={4} width="100%">
|
|
59
|
+
<VStack gap={4} hAlign="stretch">
|
|
60
|
+
<VStack gap={1} hAlign="center">
|
|
61
|
+
<Heading level={1} type="display-2" justify="center">
|
|
62
|
+
{AUTH_HEADING}
|
|
63
|
+
</Heading>
|
|
64
|
+
<Text type="body" color="secondary">
|
|
65
|
+
{AUTH_SUBTITLE}
|
|
66
|
+
</Text>
|
|
67
|
+
</VStack>
|
|
68
|
+
{children}
|
|
69
|
+
{/* Prose link: colour alone is not a cue inside a sentence
|
|
70
|
+
(WCAG 1.4.1 / F73), so it asks for the underline the kit
|
|
71
|
+
only gives on hover. */}
|
|
72
|
+
<VStack hAlign="center">
|
|
73
|
+
<Text type="supporting" color="secondary">
|
|
74
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
75
|
+
<Link href={selfHash} type="supporting" hasUnderline>
|
|
76
|
+
{signupLink}
|
|
77
|
+
</Link>
|
|
78
|
+
</Text>
|
|
79
|
+
</VStack>
|
|
80
|
+
</VStack>
|
|
81
|
+
</Card>
|
|
82
|
+
<AuthTerms selfHash={selfHash} />
|
|
83
|
+
</>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Email + password fields with the shared error/forgot-password rhythm, plus
|
|
88
|
+
// the primary CTA that always follows them. The field gap is 8px (gap={2}),
|
|
89
|
+
// not the outer 16px: without the group the two inputs sat twice as far
|
|
90
|
+
// apart as the fields below them.
|
|
91
|
+
export function AuthLoginFields({
|
|
92
|
+
email,
|
|
93
|
+
password,
|
|
94
|
+
error,
|
|
95
|
+
selfHash,
|
|
96
|
+
isLoading,
|
|
97
|
+
onEmailChange,
|
|
98
|
+
onPasswordChange,
|
|
99
|
+
onSubmit,
|
|
100
|
+
}: {
|
|
101
|
+
email: string;
|
|
102
|
+
password: string;
|
|
103
|
+
error: string | null;
|
|
104
|
+
selfHash: string;
|
|
105
|
+
isLoading: boolean;
|
|
106
|
+
onEmailChange: (value: string) => void;
|
|
107
|
+
onPasswordChange: (value: string) => void;
|
|
108
|
+
onSubmit: () => void;
|
|
109
|
+
}) {
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<VStack gap={2}>
|
|
113
|
+
<TextInput
|
|
114
|
+
label="Email"
|
|
115
|
+
isLabelHidden
|
|
116
|
+
value={email}
|
|
117
|
+
onChange={onEmailChange}
|
|
118
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
119
|
+
type="email"
|
|
120
|
+
{...inputAutoComplete('email')}
|
|
121
|
+
size="lg"
|
|
122
|
+
onEnter={onSubmit}
|
|
123
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
124
|
+
/>
|
|
125
|
+
<VStack gap={1}>
|
|
126
|
+
<TextInput
|
|
127
|
+
label="Password"
|
|
128
|
+
isLabelHidden
|
|
129
|
+
value={password}
|
|
130
|
+
onChange={onPasswordChange}
|
|
131
|
+
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
132
|
+
type="password"
|
|
133
|
+
{...inputAutoComplete('current-password')}
|
|
134
|
+
size="lg"
|
|
135
|
+
onEnter={onSubmit}
|
|
136
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
137
|
+
/>
|
|
138
|
+
{error && (
|
|
139
|
+
<VStack hAlign="end">
|
|
140
|
+
<Link href={selfHash} color="secondary" isStandalone>
|
|
141
|
+
{AUTH_FORGOT_PASSWORD}
|
|
142
|
+
</Link>
|
|
143
|
+
</VStack>
|
|
144
|
+
)}
|
|
145
|
+
</VStack>
|
|
146
|
+
</VStack>
|
|
147
|
+
<Button
|
|
148
|
+
label={AUTH_PRIMARY_CTA}
|
|
149
|
+
variant="primary"
|
|
150
|
+
size="lg"
|
|
151
|
+
isLoading={isLoading}
|
|
152
|
+
onClick={onSubmit}
|
|
153
|
+
/>
|
|
154
|
+
</>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// The family terms line, rendered below the card in the page column.
|
|
159
|
+
export function AuthTerms({selfHash}: {selfHash: string}) {
|
|
160
|
+
return (
|
|
161
|
+
<VStack hAlign="center" width="100%">
|
|
162
|
+
<Text type="supporting" color="secondary" justify="center">
|
|
163
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
164
|
+
<Link href={selfHash} type="supporting" hasUnderline>
|
|
165
|
+
{AUTH_TERMS_SERVICE}
|
|
166
|
+
</Link>{' '}
|
|
167
|
+
and{' '}
|
|
168
|
+
<Link href={selfHash} type="supporting" hasUnderline>
|
|
169
|
+
{AUTH_TERMS_PRIVACY}
|
|
170
|
+
</Link>
|
|
171
|
+
.
|
|
172
|
+
</Text>
|
|
173
|
+
</VStack>
|
|
174
|
+
);
|
|
175
|
+
}
|
package/shared/chaptered-doc.tsx
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
buildOutlineByChapter,
|
|
10
10
|
outlinePanel,
|
|
11
11
|
sceneClip,
|
|
12
|
-
sceneFill,
|
|
13
12
|
scrollToSection,
|
|
14
13
|
sectionId,
|
|
15
14
|
type ChapterGroup,
|
|
@@ -49,7 +48,7 @@ import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
|
49
48
|
|
|
50
49
|
import {ChevronLeft, ChevronRight} from 'lucide-react';
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
function ChapterRail({
|
|
53
52
|
groups,
|
|
54
53
|
activeId,
|
|
55
54
|
onSelect,
|
|
@@ -100,7 +99,7 @@ export function ChapterRail({
|
|
|
100
99
|
|
|
101
100
|
// ─── Chapter body ────────────────────────────────────────────────────────────
|
|
102
101
|
|
|
103
|
-
|
|
102
|
+
function SectionBlock({
|
|
104
103
|
chapterId,
|
|
105
104
|
section,
|
|
106
105
|
}: {
|
|
@@ -134,7 +133,7 @@ export function SectionBlock({
|
|
|
134
133
|
);
|
|
135
134
|
}
|
|
136
135
|
|
|
137
|
-
|
|
136
|
+
function ChapterNav({
|
|
138
137
|
chapters,
|
|
139
138
|
chapter,
|
|
140
139
|
onSelect,
|
package/shared/chart-hues.ts
CHANGED
|
@@ -20,8 +20,3 @@ export const CHART_HUES = {
|
|
|
20
20
|
} as const;
|
|
21
21
|
|
|
22
22
|
export type ChartHue = (typeof CHART_HUES)[keyof typeof CHART_HUES];
|
|
23
|
-
|
|
24
|
-
// The value list exists for the gate: it reads the object's VALUES, never a
|
|
25
|
-
// grep across shared/. A text-level filter cannot separate the sanctioned rows
|
|
26
|
-
// from a drifting neighbour, which is why the check is here and not in a lint.
|
|
27
|
-
export const CHART_HUE_VALUES: readonly ChartHue[] = Object.values(CHART_HUES);
|
package/shared/chart-legend.tsx
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
import type {ReactNode} from 'react';
|
|
25
25
|
import {HStack, StackItem, VStack} from '@astryxdesign/core/Stack';
|
|
26
26
|
import {Text} from '@astryxdesign/core/Text';
|
|
27
|
-
import
|
|
27
|
+
import type {ChartHue} from 'astryx-dracula/shared/chart-hues';
|
|
28
28
|
|
|
29
29
|
export interface ChartLegendEntry {
|
|
30
30
|
/** REQUIRED. The hue never travels alone — this is the 1.4.1 contract. */
|
|
@@ -45,7 +45,6 @@ export interface ChartLegendEntry {
|
|
|
45
45
|
// would render `background: undefined` at every call site, compile clean, and
|
|
46
46
|
// break at runtime. The old comment here claimed call sites may pass a KEY;
|
|
47
47
|
// none does. The behaviour is correct; only the comment was wrong.
|
|
48
|
-
const HUE_LOOKUP: Record<string, string> = CHART_HUES;
|
|
49
48
|
|
|
50
49
|
export interface ChartLegendProps {
|
|
51
50
|
entries: readonly ChartLegendEntry[];
|
|
@@ -81,7 +80,7 @@ export function ChartLegend({
|
|
|
81
80
|
width: `${swatchSize}px`,
|
|
82
81
|
height: `${swatchSize}px`,
|
|
83
82
|
flexShrink: 0,
|
|
84
|
-
background:
|
|
83
|
+
background: entry.color,
|
|
85
84
|
borderRadius: 'var(--radius-inner)',
|
|
86
85
|
}}
|
|
87
86
|
/>
|
package/shared/data-bar.tsx
CHANGED
|
@@ -66,9 +66,6 @@ export interface DataBarProps {
|
|
|
66
66
|
formatValue?: (total: number) => string;
|
|
67
67
|
/** Gap between segments as a spacing-scale step. @default 0 */
|
|
68
68
|
gap?: 0 | 0.5 | 1 | 1.5 | 2 | 3;
|
|
69
|
-
/** Neutral fill shown when every segment is zero. Deliberately
|
|
70
|
-
* --color-progress-value, NOT a text token — see the header. */
|
|
71
|
-
trackColor?: string;
|
|
72
69
|
}
|
|
73
70
|
|
|
74
71
|
const segmentStyle: CSSProperties = {
|
|
@@ -82,7 +79,6 @@ export function DataBar({
|
|
|
82
79
|
hasValueLabel = false,
|
|
83
80
|
formatValue = total => total.toLocaleString(),
|
|
84
81
|
gap = 0,
|
|
85
|
-
trackColor = 'var(--color-progress-value)',
|
|
86
82
|
}: DataBarProps) {
|
|
87
83
|
// Negative values are not a magnitude and are clamped out before the ratio is
|
|
88
84
|
// taken: summing a negative into `total` inflates every other segment's
|
|
@@ -136,7 +132,11 @@ export function DataBar({
|
|
|
136
132
|
style={
|
|
137
133
|
total > 0
|
|
138
134
|
? undefined
|
|
139
|
-
: {
|
|
135
|
+
: {
|
|
136
|
+
// Deliberately --color-progress-value, NOT a text token — see the header.
|
|
137
|
+
background: 'var(--color-progress-value)',
|
|
138
|
+
borderRadius: 'var(--radius-inner)',
|
|
139
|
+
}
|
|
140
140
|
}>
|
|
141
141
|
{drawn.map(segment => (
|
|
142
142
|
<StackItem
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Shared dashboard cards: the KPI tile (MetricCard), the linked table panel
|
|
2
|
+
// (TableCard), and the two table-cell renderers the engagement tables share
|
|
3
|
+
// (BarCell = magnitude bar + formatted count, CountCell = plain tabular
|
|
4
|
+
// count). One home so the analytics and portfolio templates can't drift into
|
|
5
|
+
// a second KPI/table vocabulary.
|
|
6
|
+
|
|
7
|
+
import type {ReactNode} from 'react';
|
|
8
|
+
import {VStack, HStack} from '@astryxdesign/core/Stack';
|
|
9
|
+
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
10
|
+
import {Card} from '@astryxdesign/core/Card';
|
|
11
|
+
import {Link} from '@astryxdesign/core/Link';
|
|
12
|
+
import {Table} from '@astryxdesign/core/Table';
|
|
13
|
+
import type {TableColumn} from '@astryxdesign/core/Table';
|
|
14
|
+
import {ProgressBar} from '@astryxdesign/core/ProgressBar';
|
|
15
|
+
import {MetricDelta} from 'astryx-dracula/shared/metric-delta';
|
|
16
|
+
import {Sparkline, type SparkPoint} from 'astryx-dracula/shared/sparkline';
|
|
17
|
+
|
|
18
|
+
export function MetricCard({
|
|
19
|
+
label,
|
|
20
|
+
value,
|
|
21
|
+
change,
|
|
22
|
+
positive,
|
|
23
|
+
caption = 'Last 30 days vs. Previous',
|
|
24
|
+
sparkline,
|
|
25
|
+
}: {
|
|
26
|
+
label: string;
|
|
27
|
+
value: string;
|
|
28
|
+
change: string;
|
|
29
|
+
/** Defaults to "no leading minus means positive". */
|
|
30
|
+
positive?: boolean;
|
|
31
|
+
caption?: string;
|
|
32
|
+
/** Rendered as a thirty-day trend sparkline when provided. */
|
|
33
|
+
sparkline?: SparkPoint[];
|
|
34
|
+
}) {
|
|
35
|
+
const isPositive = positive ?? !change.startsWith('-');
|
|
36
|
+
return (
|
|
37
|
+
<Card>
|
|
38
|
+
<VStack gap={2}>
|
|
39
|
+
<Text type="supporting" color="secondary">
|
|
40
|
+
{label}
|
|
41
|
+
</Text>
|
|
42
|
+
<HStack gap={2} vAlign="center">
|
|
43
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
44
|
+
{value}
|
|
45
|
+
</Text>
|
|
46
|
+
<MetricDelta value={change} positive={isPositive} />
|
|
47
|
+
</HStack>
|
|
48
|
+
<Text type="supporting" color="secondary">
|
|
49
|
+
{caption}
|
|
50
|
+
</Text>
|
|
51
|
+
{sparkline && (
|
|
52
|
+
<Sparkline
|
|
53
|
+
data={sparkline}
|
|
54
|
+
label={`${label} thirty-day trend`}
|
|
55
|
+
positive={isPositive}
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
</VStack>
|
|
59
|
+
</Card>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function TableCard<T extends {id: string}>({
|
|
64
|
+
title,
|
|
65
|
+
linkLabel,
|
|
66
|
+
linkHref,
|
|
67
|
+
data,
|
|
68
|
+
columns,
|
|
69
|
+
}: {
|
|
70
|
+
title: string;
|
|
71
|
+
/** Omit both for a linkless panel (e.g. portfolio trending stocks). */
|
|
72
|
+
linkLabel?: string;
|
|
73
|
+
linkHref?: string;
|
|
74
|
+
data: T[];
|
|
75
|
+
columns: TableColumn<T>[];
|
|
76
|
+
}) {
|
|
77
|
+
return (
|
|
78
|
+
<Card>
|
|
79
|
+
<VStack gap={6}>
|
|
80
|
+
<HStack hAlign="between" vAlign="center">
|
|
81
|
+
<Heading level={3}>{title}</Heading>
|
|
82
|
+
{linkLabel && linkHref && <Link href={linkHref}>{linkLabel}</Link>}
|
|
83
|
+
</HStack>
|
|
84
|
+
<Table<T>
|
|
85
|
+
data={data}
|
|
86
|
+
columns={columns}
|
|
87
|
+
idKey="id"
|
|
88
|
+
density="compact"
|
|
89
|
+
dividers="rows"
|
|
90
|
+
textOverflow="truncate"
|
|
91
|
+
hasHover
|
|
92
|
+
/>
|
|
93
|
+
</VStack>
|
|
94
|
+
</Card>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function BarCell({
|
|
99
|
+
label,
|
|
100
|
+
value,
|
|
101
|
+
max,
|
|
102
|
+
children,
|
|
103
|
+
}: {
|
|
104
|
+
/** Accessible bar label, e.g. "/home views". */
|
|
105
|
+
label: string;
|
|
106
|
+
value: number;
|
|
107
|
+
max: number;
|
|
108
|
+
/** Formatted count rendered under the bar. */
|
|
109
|
+
children: ReactNode;
|
|
110
|
+
}) {
|
|
111
|
+
return (
|
|
112
|
+
<VStack gap={1}>
|
|
113
|
+
<ProgressBar value={value} max={max} label={label} isLabelHidden />
|
|
114
|
+
<Text type="body" hasTabularNumbers maxLines={1}>
|
|
115
|
+
{children}
|
|
116
|
+
</Text>
|
|
117
|
+
</VStack>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function CountCell({children}: {children: ReactNode}) {
|
|
122
|
+
return (
|
|
123
|
+
<Text hasTabularNumbers maxLines={1}>
|
|
124
|
+
{children}
|
|
125
|
+
</Text>
|
|
126
|
+
);
|
|
127
|
+
}
|
package/shared/scene-hues.ts
CHANGED
|
@@ -26,14 +26,6 @@ export const SCENE_HUES = {
|
|
|
26
26
|
|
|
27
27
|
export type SceneHue = (typeof SCENE_HUES)[keyof typeof SCENE_HUES];
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* The union above constrains the KEY, not the value it resolves to. An entry
|
|
31
|
-
* rewritten to `var(--dracula-purple)` still typechecks. `scripts/check.ts`
|
|
32
|
-
* gates the values of this object; add the gate there if it is ever removed.
|
|
33
|
-
* A prose rule does not stop a caller — see the remit note above.
|
|
34
|
-
*/
|
|
35
|
-
export const SCENE_HUE_VALUES: readonly SceneHue[] = Object.values(SCENE_HUES);
|
|
36
|
-
|
|
37
29
|
// Shared alt list for the nine-tile side/classic wall (folded into one
|
|
38
30
|
// module so the two surfaces cannot drift apart again).
|
|
39
31
|
export const SCENE_TILE_ALTS = [
|
package/shared/scene-tile.tsx
CHANGED
|
@@ -22,11 +22,7 @@
|
|
|
22
22
|
// site with the exact offending value named.
|
|
23
23
|
|
|
24
24
|
import {galleryImage} from 'astryx-dracula/shared/gallery-image';
|
|
25
|
-
import {
|
|
26
|
-
SCENE_HUES,
|
|
27
|
-
type SceneHue,
|
|
28
|
-
type SceneTileSize,
|
|
29
|
-
} from 'astryx-dracula/shared/scene-hues';
|
|
25
|
+
import type {SceneHue, SceneTileSize} from 'astryx-dracula/shared/scene-hues';
|
|
30
26
|
|
|
31
27
|
|
|
32
28
|
interface SceneTileProps {
|
|
@@ -43,9 +39,6 @@ interface SceneTileProps {
|
|
|
43
39
|
label: string;
|
|
44
40
|
/** `lg` for grid thumbs, `sm` for the classic/side landscape tiles. */
|
|
45
41
|
size?: SceneTileSize;
|
|
46
|
-
/** Star wash for the sm landscape. Decorative, and purple-free by the same
|
|
47
|
-
* remit as `hue`, so it takes the same union. */
|
|
48
|
-
stars?: SceneHue;
|
|
49
42
|
/** Positional seed so repeated sm tiles vary moon, hills, and star field.
|
|
50
43
|
* Ignored by the lg fork (one centered glyph, no variance). */
|
|
51
44
|
index?: number;
|
|
@@ -56,7 +49,6 @@ export function SceneTile({
|
|
|
56
49
|
hue,
|
|
57
50
|
label,
|
|
58
51
|
size = 'lg',
|
|
59
|
-
stars = 'var(--dracula-comment)',
|
|
60
52
|
index = 0,
|
|
61
53
|
}: SceneTileProps) {
|
|
62
54
|
const moonX = 90 + ((index * 53) % 220);
|
|
@@ -73,8 +65,8 @@ export function SceneTile({
|
|
|
73
65
|
<rect width="400" height="300" fill="var(--dracula-bg-light)" />
|
|
74
66
|
{size === 'sm' && (
|
|
75
67
|
<>
|
|
76
|
-
{/* Stars */}
|
|
77
|
-
<g fill=
|
|
68
|
+
{/* Stars: decorative wash, comment so purple stays reserved for tappables. */}
|
|
69
|
+
<g fill="var(--dracula-comment)" opacity={0.55}>
|
|
78
70
|
<circle cx={40 + ((index * 37) % 320)} cy={30} r={2} />
|
|
79
71
|
<circle cx={120 + ((index * 23) % 200)} cy={52} r={1.6} />
|
|
80
72
|
<circle cx={260 + ((index * 11) % 110)} cy={26} r={2.2} />
|
package/shared/settings-data.ts
CHANGED
|
@@ -165,3 +165,31 @@ export const sideNavHeading: CSSProperties = {
|
|
|
165
165
|
marginInline: 'var(--spacing-4)',
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
// Locale pickers shared by the sidebar and dialog templates. The two copies
|
|
169
|
+
// were byte-identical walls; a single source keeps them in sync.
|
|
170
|
+
export const LANGUAGES: {label: string; value: string}[] = [
|
|
171
|
+
{label: 'English (Canada)', value: 'en-CA'},
|
|
172
|
+
{label: 'English (US)', value: 'en-US'},
|
|
173
|
+
{label: 'French', value: 'fr'},
|
|
174
|
+
{label: 'Spanish', value: 'es'},
|
|
175
|
+
{label: 'German', value: 'de'},
|
|
176
|
+
{label: 'Japanese', value: 'ja'},
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
export const CURRENCIES: {label: string; value: string}[] = [
|
|
180
|
+
{label: 'Canadian dollar (CAD)', value: 'CAD'},
|
|
181
|
+
{label: 'US dollar (USD)', value: 'USD'},
|
|
182
|
+
{label: 'Euro (EUR)', value: 'EUR'},
|
|
183
|
+
{label: 'British pound (GBP)', value: 'GBP'},
|
|
184
|
+
{label: 'Japanese yen (JPY)', value: 'JPY'},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
export const TIMEZONES: {label: string; value: string}[] = [
|
|
188
|
+
{label: '(GMT-05:00) Eastern Time (US & Canada)', value: 'ET'},
|
|
189
|
+
{label: '(GMT-06:00) Central Time (US & Canada)', value: 'CT'},
|
|
190
|
+
{label: '(GMT-07:00) Mountain Time (US & Canada)', value: 'MT'},
|
|
191
|
+
{label: '(GMT-08:00) Pacific Time (US & Canada)', value: 'PT'},
|
|
192
|
+
{label: '(GMT+00:00) UTC', value: 'UTC'},
|
|
193
|
+
{label: '(GMT+01:00) London', value: 'GMT+1'},
|
|
194
|
+
];
|
|
195
|
+
|
package/shared/settings-rows.tsx
CHANGED
|
@@ -72,10 +72,7 @@ import {Divider} from '@astryxdesign/core/Divider';
|
|
|
72
72
|
// the page reads tighter than its neighbours.
|
|
73
73
|
//
|
|
74
74
|
// `href` is a real field on `InfoRow`: where a `'destination'` action goes,
|
|
75
|
-
// read only when `actionKind` says the row navigates.
|
|
76
|
-
// `ExpandableRow` stays for call-site compatibility — it anchored Links this
|
|
77
|
-
// file no longer renders, nothing reads it now, and removing it would break
|
|
78
|
-
// any caller that still passes it.
|
|
75
|
+
// read only when `actionKind` says the row navigates.
|
|
79
76
|
|
|
80
77
|
export function InfoRowItem({
|
|
81
78
|
label,
|
|
@@ -200,8 +197,6 @@ export function ExpandableRow({
|
|
|
200
197
|
onEdit: () => void;
|
|
201
198
|
onCancel: () => void;
|
|
202
199
|
onSave: () => void;
|
|
203
|
-
/** Accepted for call-site compatibility; unused — see the note above. */
|
|
204
|
-
href?: string;
|
|
205
200
|
style?: CSSProperties;
|
|
206
201
|
/** Draw a Divider after the row. True only at a group boundary. */
|
|
207
202
|
hasDivider?: boolean;
|
package/shared/stars.tsx
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {HStack} from '@astryxdesign/core/Stack';
|
|
2
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
3
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
4
|
+
import {Star} from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export function StarRating({rating, count}: {rating: number; count: number}) {
|
|
7
|
+
const filled = Math.round(rating);
|
|
8
|
+
const empty = 5 - filled;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<HStack gap={1} vAlign="center">
|
|
12
|
+
{Array.from({length: filled}, (_, i) => (
|
|
13
|
+
<Icon key={`full-${i}`} icon={Star} size="sm" color="yellow" />
|
|
14
|
+
))}
|
|
15
|
+
{/* secondary, not disabled: --color-icon-disabled #6272A4 is 2.51:1 on
|
|
16
|
+
the card #343746, under the 3:1 non-text floor, and reads as "this
|
|
17
|
+
control is unavailable" — but a star is a data mark, not a control.
|
|
18
|
+
--color-icon-secondary #9AA1BC is 4.60:1, one step under the filled
|
|
19
|
+
yellow (7.4:1), so the two halves still read as one rating. */}
|
|
20
|
+
{Array.from({length: empty}, (_, i) => (
|
|
21
|
+
<Icon key={`empty-${i}`} icon={Star} size="sm" color="secondary" />
|
|
22
|
+
))}
|
|
23
|
+
<Text type="body" color="secondary" hasTabularNumbers>
|
|
24
|
+
{rating} ({count})
|
|
25
|
+
</Text>
|
|
26
|
+
</HStack>
|
|
27
|
+
);
|
|
28
|
+
}
|