astryx-dracula 0.2.0 → 0.3.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 +30 -18
- package/README.md +22 -11
- package/USAGE.md +29 -10
- package/astryx-dracula.js +19 -13
- package/astryx-dracula.variants.d.ts +15 -0
- package/package.json +59 -17
- package/shared/auth-chrome-config.ts +18 -0
- package/shared/auth-chrome.tsx +25 -0
- package/shared/auth-copy.ts +22 -0
- package/shared/chaptered-doc-config.ts +92 -0
- package/shared/chaptered-doc.tsx +332 -0
- package/shared/chart-hues.ts +27 -0
- package/shared/chart-labels.tsx +44 -0
- package/shared/chart-legend.tsx +100 -0
- package/shared/chart-panel-style.ts +13 -0
- package/shared/data-bar.tsx +160 -0
- package/shared/gallery-image.ts +20 -0
- package/shared/login-demo.ts +7 -0
- package/shared/metric-delta.tsx +36 -0
- package/shared/order-desk.tsx +273 -0
- package/shared/revenue-chart.tsx +174 -0
- package/shared/scene-castle.tsx +322 -0
- package/shared/scene-frame.tsx +88 -0
- package/shared/scene-hues.ts +49 -0
- package/shared/scene-tile.tsx +128 -0
- package/shared/settings-data.ts +125 -0
- package/shared/settings-rows.tsx +179 -0
- package/shared/sparkline.tsx +94 -0
- package/shared/sso-icons.tsx +30 -0
- package/templates/ai-chat-landing.tsx +67 -32
- package/templates/ai-chat.tsx +41 -20
- package/templates/blank.tsx +5 -2
- package/templates/centered-hero.tsx +15 -124
- package/templates/classic-gallery.tsx +21 -65
- package/templates/contact-form.tsx +62 -40
- package/templates/dashboard-portfolio.tsx +146 -227
- package/templates/dashboard.tsx +123 -121
- package/templates/detail-page.tsx +101 -95
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +85 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +33 -21
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +54 -74
- package/templates/editor.tsx +119 -89
- package/templates/file-explorer.tsx +133 -84
- package/templates/form-two-column.tsx +62 -124
- package/templates/gallery-hero.tsx +17 -171
- package/templates/ide.tsx +27 -21
- package/templates/incident-console.tsx +105 -69
- package/templates/kanban-board.tsx +100 -41
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +38 -41
- package/templates/login-card.tsx +59 -86
- package/templates/login-split.tsx +88 -128
- package/templates/login-sso.tsx +107 -68
- package/templates/login.tsx +108 -52
- package/templates/messaging-shell.tsx +172 -143
- package/templates/mixed-gallery.tsx +31 -130
- package/templates/payment-form.tsx +369 -289
- package/templates/product-detail.tsx +40 -57
- package/templates/product-gallery.tsx +31 -40
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +145 -286
- package/templates/settings-sidebar.tsx +179 -267
- package/templates/settings.tsx +42 -13
- package/templates/shell-nav.tsx +32 -23
- package/templates/shell-side-nav.tsx +30 -19
- package/templates/shell-top-nav.tsx +81 -43
- package/templates/side-gallery.tsx +39 -93
- package/templates/table-grouped.tsx +63 -33
- package/templates/table-page-chart.tsx +24 -297
- package/templates/table-page-heatmap-status.tsx +157 -91
- package/templates/table-page-shoe-store-heatmap.tsx +19 -288
- package/templates/table-page.tsx +24 -6
- package/templates/table.tsx +24 -7
- package/templates/tech-report.template.mjs +6 -0
- package/templates/tech-report.tsx +1486 -0
- package/templates/theme-showcase.template.mjs +1 -1
- package/templates/theme-showcase.tsx +313 -249
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/login-card.tsx
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// Ctr > V[g=4 a=center] > (V[g=2 a=center] > Ic + Tx"Castle Dracula"[t=body]) + (C[p=4] > V[g=4] > (V[g=1 a=center] > Hd"Welcome back to the night"[level=1] + Tx"Sign in to your crypt"[t=body]) + (V[g=2] > TI"Email"[t=email] + (V[g=1] > TI"Password"[t=password] + Lk"Forgot password?")) + B.primary"Enter the night" + D"Or continue with" + (V[g=3] > B.secondary"Login with Apple" + B.secondary"Login with Google") + (V[a=center] > Tx"New to the castle?"[t=supporting] + Lk"Sign up")) + (V[a=center] > Tx"By clicking continue, you agree to our Terms of service and Privacy policy"[t=supporting])
|
|
2
4
|
|
|
3
|
-
import {useState,
|
|
4
|
-
import {
|
|
5
|
+
import {useState, useTransition} from 'react';
|
|
6
|
+
import {demoLogin} from 'astryx-dracula/shared/login-demo';
|
|
7
|
+
import {AppleIcon, GoogleIcon} from 'astryx-dracula/shared/sso-icons';
|
|
8
|
+
import {
|
|
9
|
+
AUTH_HEADING,
|
|
10
|
+
AUTH_SUBTITLE,
|
|
11
|
+
AUTH_PRIMARY_CTA,
|
|
12
|
+
AUTH_SIGNUP_PROMPT,
|
|
13
|
+
AUTH_SIGNUP_LINK,
|
|
14
|
+
AUTH_SSO_DIVIDER,
|
|
15
|
+
AUTH_FORGOT_PASSWORD,
|
|
16
|
+
AUTH_ERROR_MESSAGE,
|
|
17
|
+
AUTH_EMAIL_PLACEHOLDER,
|
|
18
|
+
AUTH_PASSWORD_PLACEHOLDER,
|
|
19
|
+
AUTH_TERMS_PREFIX,
|
|
20
|
+
AUTH_TERMS_SERVICE,
|
|
21
|
+
AUTH_TERMS_PRIVACY,
|
|
22
|
+
} from 'astryx-dracula/shared/auth-copy';
|
|
5
23
|
import {VStack} from '@astryxdesign/core/Layout';
|
|
6
24
|
import {Center} from '@astryxdesign/core/Center';
|
|
7
25
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
@@ -10,87 +28,43 @@ import {Button} from '@astryxdesign/core/Button';
|
|
|
10
28
|
import {Card} from '@astryxdesign/core/Card';
|
|
11
29
|
import {Link} from '@astryxdesign/core/Link';
|
|
12
30
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
13
|
-
import {Icon} from '@astryxdesign/core/Icon';
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<svg
|
|
18
|
-
viewBox="0 0 24 24"
|
|
19
|
-
fill="currentColor"
|
|
20
|
-
width={16}
|
|
21
|
-
height={16}
|
|
22
|
-
aria-hidden="true"
|
|
23
|
-
{...props}>
|
|
24
|
-
<path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" />
|
|
25
|
-
</svg>
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const GoogleIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|
29
|
-
<svg
|
|
30
|
-
viewBox="0 0 24 24"
|
|
31
|
-
fill="currentColor"
|
|
32
|
-
width={16}
|
|
33
|
-
height={16}
|
|
34
|
-
aria-hidden="true"
|
|
35
|
-
{...props}>
|
|
36
|
-
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" />
|
|
37
|
-
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
38
|
-
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
|
39
|
-
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
|
40
|
-
</svg>
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
// Standalone auth page paints its own body background (no host shell).
|
|
44
|
-
const pageStyle: CSSProperties = {
|
|
45
|
-
minHeight: '100%',
|
|
46
|
-
backgroundColor: 'var(--color-background-body)',
|
|
47
|
-
padding: 'var(--spacing-6)',
|
|
48
|
-
};
|
|
49
|
-
// Cap the column at 400px but let it shrink to fit narrow screens (Stack
|
|
50
|
-
// has no maxWidth prop, so it's set here).
|
|
51
|
-
const contentStyle: CSSProperties = {
|
|
52
|
-
width: '100%',
|
|
53
|
-
maxWidth: 400,
|
|
54
|
-
};
|
|
32
|
+
import {authPageStyle as pageStyle, authContentStyle as contentStyle, inputAutoComplete} from 'astryx-dracula/shared/auth-chrome-config';
|
|
33
|
+
import {LoginBrand} from 'astryx-dracula/shared/auth-chrome';
|
|
55
34
|
|
|
56
35
|
export default function LoginCard() {
|
|
57
36
|
const [email, setEmail] = useState('');
|
|
58
37
|
const [password, setPassword] = useState('');
|
|
59
|
-
const [
|
|
60
|
-
const [isLoading,
|
|
38
|
+
const [error, setError] = useState<string | null>(null);
|
|
39
|
+
const [isLoading, startTransition] = useTransition();
|
|
61
40
|
|
|
62
41
|
const handleLogin = () => {
|
|
63
42
|
if (!email || !password) {
|
|
64
|
-
|
|
43
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
65
44
|
return;
|
|
66
45
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}, 2000);
|
|
46
|
+
setError(null);
|
|
47
|
+
startTransition(async () => {
|
|
48
|
+
await demoLogin();
|
|
49
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
50
|
+
});
|
|
73
51
|
};
|
|
74
52
|
|
|
75
53
|
return (
|
|
76
54
|
<Center axis="both" style={pageStyle}>
|
|
77
55
|
<VStack gap={4} hAlign="center" style={contentStyle}>
|
|
78
|
-
|
|
79
|
-
<VStack gap={2} hAlign="center">
|
|
80
|
-
<Icon icon={Moon} size="lg" color="accent" />
|
|
81
|
-
<Text type="body" weight="bold" size="lg">
|
|
82
|
-
Castle Dracula
|
|
83
|
-
</Text>
|
|
84
|
-
</VStack>
|
|
56
|
+
<LoginBrand />
|
|
85
57
|
|
|
86
58
|
{/* Card */}
|
|
87
|
-
<Card padding={
|
|
59
|
+
<Card padding={4} width="100%">
|
|
88
60
|
<VStack gap={4} hAlign="stretch">
|
|
89
61
|
{/* Header */}
|
|
90
62
|
<VStack gap={1} hAlign="center">
|
|
91
|
-
<Heading level={
|
|
63
|
+
<Heading level={1} type="display-2">
|
|
64
|
+
{AUTH_HEADING}
|
|
65
|
+
</Heading>
|
|
92
66
|
<Text type="body" color="secondary">
|
|
93
|
-
|
|
67
|
+
{AUTH_SUBTITLE}
|
|
94
68
|
</Text>
|
|
95
69
|
</VStack>
|
|
96
70
|
|
|
@@ -100,40 +74,39 @@ export default function LoginCard() {
|
|
|
100
74
|
label="Email"
|
|
101
75
|
isLabelHidden
|
|
102
76
|
type="email"
|
|
103
|
-
|
|
77
|
+
{...inputAutoComplete('email')}
|
|
78
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
104
79
|
value={email}
|
|
105
|
-
onChange={
|
|
80
|
+
onChange={(v: string) => {
|
|
81
|
+
setEmail(v);
|
|
82
|
+
setError(null);
|
|
83
|
+
}}
|
|
106
84
|
size="lg"
|
|
85
|
+
onEnter={handleLogin}
|
|
86
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
107
87
|
/>
|
|
108
88
|
<VStack gap={1}>
|
|
109
89
|
<TextInput
|
|
110
90
|
label="Password"
|
|
111
91
|
isLabelHidden
|
|
112
|
-
placeholder=
|
|
92
|
+
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
113
93
|
type="password"
|
|
94
|
+
{...inputAutoComplete('current-password')}
|
|
114
95
|
value={password}
|
|
115
96
|
onChange={(v: string) => {
|
|
116
97
|
setPassword(v);
|
|
117
|
-
|
|
98
|
+
setError(null);
|
|
118
99
|
}}
|
|
119
100
|
size="lg"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
? {
|
|
123
|
-
type: 'error',
|
|
124
|
-
message: 'Wrong incantation. Try again.',
|
|
125
|
-
}
|
|
126
|
-
: undefined
|
|
127
|
-
}
|
|
101
|
+
onEnter={handleLogin}
|
|
102
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
128
103
|
/>
|
|
129
|
-
{
|
|
104
|
+
{error && (
|
|
130
105
|
<VStack hAlign="end">
|
|
131
106
|
<Link
|
|
132
107
|
href="#/templates/login-card"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
type="supporting">
|
|
136
|
-
Forgot password?
|
|
108
|
+
color="secondary">
|
|
109
|
+
{AUTH_FORGOT_PASSWORD}
|
|
137
110
|
</Link>
|
|
138
111
|
</VStack>
|
|
139
112
|
)}
|
|
@@ -142,7 +115,7 @@ export default function LoginCard() {
|
|
|
142
115
|
|
|
143
116
|
{/* Login button */}
|
|
144
117
|
<Button
|
|
145
|
-
label=
|
|
118
|
+
label={AUTH_PRIMARY_CTA}
|
|
146
119
|
variant="primary"
|
|
147
120
|
size="lg"
|
|
148
121
|
isLoading={isLoading}
|
|
@@ -150,7 +123,7 @@ export default function LoginCard() {
|
|
|
150
123
|
/>
|
|
151
124
|
|
|
152
125
|
{/* Divider */}
|
|
153
|
-
<Divider label=
|
|
126
|
+
<Divider label={AUTH_SSO_DIVIDER} />
|
|
154
127
|
|
|
155
128
|
{/* Social buttons */}
|
|
156
129
|
<VStack gap={3} hAlign="stretch">
|
|
@@ -171,9 +144,9 @@ export default function LoginCard() {
|
|
|
171
144
|
{/* Sign up link */}
|
|
172
145
|
<VStack hAlign="center">
|
|
173
146
|
<Text type="supporting" color="secondary">
|
|
174
|
-
|
|
147
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
175
148
|
<Link href="#/templates/login-card" type="supporting">
|
|
176
|
-
|
|
149
|
+
{AUTH_SIGNUP_LINK}
|
|
177
150
|
</Link>
|
|
178
151
|
</Text>
|
|
179
152
|
</VStack>
|
|
@@ -183,13 +156,13 @@ export default function LoginCard() {
|
|
|
183
156
|
{/* Terms */}
|
|
184
157
|
<VStack hAlign="center" width="100%">
|
|
185
158
|
<Text type="supporting" color="secondary" justify="center">
|
|
186
|
-
|
|
159
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
187
160
|
<Link href="#/templates/login-card" type="supporting">
|
|
188
|
-
|
|
161
|
+
{AUTH_TERMS_SERVICE}
|
|
189
162
|
</Link>{' '}
|
|
190
163
|
and{' '}
|
|
191
164
|
<Link href="#/templates/login-card" type="supporting">
|
|
192
|
-
|
|
165
|
+
{AUTH_TERMS_PRIVACY}
|
|
193
166
|
</Link>
|
|
194
167
|
.
|
|
195
168
|
</Text>
|
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// Ctr > V[g=4] > (Ctr.horizontal > C[p=0] > G[c={min:240} g=8 a=stretch] > (S[p=0] > V[g=4] > (H[g=2 a=center] > Ic + Tx"Castle Dracula"[t=body]) + (V[g=4] > (V[g=1] > Hd"Welcome back to the night"[level=1] + Tx"Sign in to your crypt"[t=body]) + (V[g=2] > TI"Email"[t=email] + (V[g=1] > TI"Password"[t=password] + Lk"Forgot password?")) + B.primary"Enter the night" + D"Or continue with" + (G[c={min:200} g=3] > B.secondary"Login with Apple" + B.secondary"Login with Google")) + Tx"New to the castle?"[t=supporting]) + (C[p=0] > AR)) + (V[a=center] > Tx"By clicking continue, you agree to our Terms of service and Privacy policy"[t=supporting])
|
|
2
4
|
|
|
3
|
-
import {useState, type CSSProperties} from 'react';
|
|
5
|
+
import {useState, useTransition, type CSSProperties} from 'react';
|
|
6
|
+
import {inputAutoComplete} from 'astryx-dracula/shared/auth-chrome-config';
|
|
7
|
+
import {demoLogin} from 'astryx-dracula/shared/login-demo';
|
|
8
|
+
import {AppleIcon, GoogleIcon} from 'astryx-dracula/shared/sso-icons';
|
|
9
|
+
import {
|
|
10
|
+
AUTH_HEADING,
|
|
11
|
+
AUTH_SUBTITLE,
|
|
12
|
+
AUTH_PRIMARY_CTA,
|
|
13
|
+
AUTH_SIGNUP_PROMPT,
|
|
14
|
+
AUTH_SIGNUP_LINK,
|
|
15
|
+
AUTH_SSO_DIVIDER,
|
|
16
|
+
AUTH_FORGOT_PASSWORD,
|
|
17
|
+
AUTH_ERROR_MESSAGE,
|
|
18
|
+
AUTH_EMAIL_PLACEHOLDER,
|
|
19
|
+
AUTH_PASSWORD_PLACEHOLDER,
|
|
20
|
+
AUTH_BRAND_NAME,
|
|
21
|
+
AUTH_TERMS_PREFIX,
|
|
22
|
+
AUTH_TERMS_SERVICE,
|
|
23
|
+
AUTH_TERMS_PRIVACY,
|
|
24
|
+
} from 'astryx-dracula/shared/auth-copy';
|
|
25
|
+
import {SceneCastle} from 'astryx-dracula/shared/scene-castle';
|
|
4
26
|
import {VStack, HStack, StackItem} from '@astryxdesign/core/Layout';
|
|
5
27
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
6
28
|
import {Center} from '@astryxdesign/core/Center';
|
|
@@ -19,77 +41,9 @@ import {Divider} from '@astryxdesign/core/Divider';
|
|
|
19
41
|
// CheckCircleIcon. The SquaresPlusIcon brand mark has no place here.
|
|
20
42
|
import {Moon, CircleCheck} from 'lucide-react';
|
|
21
43
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
viewBox="0 0 24 24"
|
|
26
|
-
fill="currentColor"
|
|
27
|
-
width={16}
|
|
28
|
-
height={16}
|
|
29
|
-
aria-hidden="true"
|
|
30
|
-
{...props}>
|
|
31
|
-
<path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z" />
|
|
32
|
-
</svg>
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
const GoogleIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|
36
|
-
<svg
|
|
37
|
-
viewBox="0 0 24 24"
|
|
38
|
-
fill="currentColor"
|
|
39
|
-
width={16}
|
|
40
|
-
height={16}
|
|
41
|
-
aria-hidden="true"
|
|
42
|
-
{...props}>
|
|
43
|
-
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" />
|
|
44
|
-
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
|
|
45
|
-
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
|
|
46
|
-
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
|
|
47
|
-
</svg>
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
// Cover art is inline Dracula SVG — moonlit rooftops in brand tokens, no
|
|
51
|
-
// external image. Fills the panel via slice, like the placeholder before it.
|
|
52
|
-
const coverArt: CSSProperties = {
|
|
53
|
-
width: '100%',
|
|
54
|
-
height: '100%',
|
|
55
|
-
display: 'block',
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const nightCoverArt = (
|
|
59
|
-
<svg
|
|
60
|
-
viewBox="0 0 400 300"
|
|
61
|
-
preserveAspectRatio="xMidYMid slice"
|
|
62
|
-
style={coverArt}
|
|
63
|
-
role="img"
|
|
64
|
-
aria-label="Harvest moon over the castle rooftops">
|
|
65
|
-
<rect width="400" height="300" fill="var(--dracula-bg)" />
|
|
66
|
-
<circle cx="68" cy="52" r="2" fill="var(--dracula-fg)" />
|
|
67
|
-
<circle cx="140" cy="36" r="1.5" fill="var(--dracula-comment)" />
|
|
68
|
-
<circle cx="330" cy="70" r="2" fill="var(--dracula-fg)" />
|
|
69
|
-
<circle cx="292" cy="34" r="1.5" fill="var(--dracula-comment)" />
|
|
70
|
-
<circle cx="360" cy="140" r="1.5" fill="var(--dracula-comment)" />
|
|
71
|
-
<circle cx="200" cy="118" r="52" fill="var(--dracula-yellow)" />
|
|
72
|
-
<circle cx="182" cy="104" r="44" fill="var(--dracula-bg)" />
|
|
73
|
-
<g
|
|
74
|
-
fill="var(--dracula-current-line)"
|
|
75
|
-
stroke="var(--dracula-comment)"
|
|
76
|
-
strokeWidth="3"
|
|
77
|
-
strokeLinejoin="round">
|
|
78
|
-
<rect x="20" y="220" width="110" height="80" />
|
|
79
|
-
<path d="M20 220 L75 178 L130 220 Z" />
|
|
80
|
-
<rect x="150" y="200" width="100" height="100" />
|
|
81
|
-
<path d="M150 200 L200 162 L250 200 Z" />
|
|
82
|
-
<rect x="270" y="228" width="110" height="72" />
|
|
83
|
-
<path d="M270 228 L325 190 L380 228 Z" />
|
|
84
|
-
</g>
|
|
85
|
-
<g fill="var(--dracula-purple)">
|
|
86
|
-
<rect x="58" y="244" width="14" height="18" />
|
|
87
|
-
<rect x="188" y="224" width="14" height="18" />
|
|
88
|
-
<rect x="312" y="250" width="14" height="18" />
|
|
89
|
-
</g>
|
|
90
|
-
</svg>
|
|
91
|
-
);
|
|
92
|
-
|
|
44
|
+
// Cover art lives in shared/scene-castle (`rooftops` variant:
|
|
45
|
+
// crescent-overlay moon over a rooftop skyline). The transparent Card clips
|
|
46
|
+
// it to rounded corners (overflow:clip + radius), so the art needs no radius.
|
|
93
47
|
// Grid emits minmax(MIN, 1fr) where MIN is a hard floor, so MIN plus the
|
|
94
48
|
// grid inset and page padding must fit the narrowest phone or the column is
|
|
95
49
|
// clipped. 320 − 2×24 (page) − 2×16 (stacked inset) = 240.
|
|
@@ -122,7 +76,7 @@ const LOGIN_SPLIT_CSS = `
|
|
|
122
76
|
.login-split-grid {
|
|
123
77
|
container-type: inline-size;
|
|
124
78
|
container-name: login-split;
|
|
125
|
-
padding: var(--spacing-
|
|
79
|
+
padding: var(--spacing-4);
|
|
126
80
|
}
|
|
127
81
|
.login-split-image {
|
|
128
82
|
width: 100%;
|
|
@@ -130,7 +84,7 @@ const LOGIN_SPLIT_CSS = `
|
|
|
130
84
|
}
|
|
131
85
|
@container login-split (max-width: 511px) {
|
|
132
86
|
.login-split-grid {
|
|
133
|
-
padding: var(--spacing-
|
|
87
|
+
padding: var(--spacing-3);
|
|
134
88
|
}
|
|
135
89
|
.login-split-image {
|
|
136
90
|
order: -1;
|
|
@@ -141,28 +95,27 @@ const LOGIN_SPLIT_CSS = `
|
|
|
141
95
|
export default function LoginSplit() {
|
|
142
96
|
const [email, setEmail] = useState('');
|
|
143
97
|
const [password, setPassword] = useState('');
|
|
144
|
-
const [
|
|
145
|
-
const [isLoading,
|
|
98
|
+
const [error, setError] = useState<string | null>(null);
|
|
99
|
+
const [isLoading, startTransition] = useTransition();
|
|
146
100
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
147
101
|
|
|
148
102
|
const handleLogin = () => {
|
|
149
103
|
if (!email || !password) {
|
|
150
|
-
|
|
104
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
151
105
|
return;
|
|
152
106
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
setIsLoading(false);
|
|
107
|
+
setError(null);
|
|
108
|
+
startTransition(async () => {
|
|
109
|
+
await demoLogin();
|
|
157
110
|
setIsSuccess(true);
|
|
158
|
-
}
|
|
111
|
+
});
|
|
159
112
|
};
|
|
160
113
|
|
|
161
114
|
return (
|
|
162
115
|
<Center axis="both" style={pageStyle}>
|
|
163
116
|
<style>{LOGIN_SPLIT_CSS}</style>
|
|
164
117
|
<VStack gap={4} width="100%">
|
|
165
|
-
<
|
|
118
|
+
<Center axis="horizontal" style={cardWrap}>
|
|
166
119
|
<Card padding={0} width="100%">
|
|
167
120
|
<Grid
|
|
168
121
|
columns={{minWidth: COLUMN_MIN_WIDTH, repeat: 'fit'}}
|
|
@@ -173,9 +126,9 @@ export default function LoginSplit() {
|
|
|
173
126
|
<Section variant="transparent" padding={0} height="100%">
|
|
174
127
|
<VStack gap={4} height="100%">
|
|
175
128
|
<HStack gap={2} vAlign="center">
|
|
176
|
-
<Icon icon={Moon} color="
|
|
177
|
-
<Text type="body" weight="
|
|
178
|
-
|
|
129
|
+
<Icon icon={Moon} color="secondary" size="lg" />
|
|
130
|
+
<Text type="body" weight="semibold">
|
|
131
|
+
{AUTH_BRAND_NAME}
|
|
179
132
|
</Text>
|
|
180
133
|
</HStack>
|
|
181
134
|
|
|
@@ -185,16 +138,22 @@ export default function LoginSplit() {
|
|
|
185
138
|
<EmptyState
|
|
186
139
|
title="You're in for the night"
|
|
187
140
|
description="Drifting to your crypt…"
|
|
188
|
-
icon={
|
|
141
|
+
icon={
|
|
142
|
+
<Icon
|
|
143
|
+
icon={CircleCheck}
|
|
144
|
+
size="lg"
|
|
145
|
+
color="secondary"
|
|
146
|
+
/>
|
|
147
|
+
}
|
|
189
148
|
/>
|
|
190
149
|
) : (
|
|
191
150
|
<VStack gap={4} hAlign="stretch" width="100%">
|
|
192
151
|
<VStack gap={1}>
|
|
193
|
-
<Heading level={2
|
|
194
|
-
|
|
152
|
+
<Heading level={1} type="display-2">
|
|
153
|
+
{AUTH_HEADING}
|
|
195
154
|
</Heading>
|
|
196
155
|
<Text type="body" color="secondary">
|
|
197
|
-
|
|
156
|
+
{AUTH_SUBTITLE}
|
|
198
157
|
</Text>
|
|
199
158
|
</VStack>
|
|
200
159
|
|
|
@@ -203,41 +162,37 @@ export default function LoginSplit() {
|
|
|
203
162
|
label="Email"
|
|
204
163
|
isLabelHidden
|
|
205
164
|
type="email"
|
|
206
|
-
|
|
165
|
+
{...inputAutoComplete('email')}
|
|
166
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
207
167
|
value={email}
|
|
208
|
-
onChange={
|
|
168
|
+
onChange={(v: string) => {
|
|
169
|
+
setEmail(v);
|
|
170
|
+
setError(null);
|
|
171
|
+
}}
|
|
209
172
|
size="lg"
|
|
173
|
+
onEnter={handleLogin}
|
|
174
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
210
175
|
/>
|
|
211
176
|
<VStack gap={1}>
|
|
212
177
|
<TextInput
|
|
213
178
|
label="Password"
|
|
214
179
|
isLabelHidden
|
|
215
|
-
placeholder=
|
|
180
|
+
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
216
181
|
type="password"
|
|
182
|
+
{...inputAutoComplete('current-password')}
|
|
217
183
|
value={password}
|
|
218
184
|
onChange={(v: string) => {
|
|
219
185
|
setPassword(v);
|
|
220
|
-
|
|
186
|
+
setError(null);
|
|
221
187
|
}}
|
|
222
188
|
size="lg"
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
? {
|
|
226
|
-
type: 'error',
|
|
227
|
-
message:
|
|
228
|
-
'Wrong incantation. Try again.',
|
|
229
|
-
}
|
|
230
|
-
: undefined
|
|
231
|
-
}
|
|
189
|
+
onEnter={handleLogin}
|
|
190
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
232
191
|
/>
|
|
233
|
-
{
|
|
192
|
+
{error && (
|
|
234
193
|
<VStack hAlign="end">
|
|
235
|
-
<Link
|
|
236
|
-
|
|
237
|
-
size="sm"
|
|
238
|
-
color="secondary"
|
|
239
|
-
type="supporting">
|
|
240
|
-
Forgot your password?
|
|
194
|
+
<Link href="#/templates/login-split">
|
|
195
|
+
{AUTH_FORGOT_PASSWORD}
|
|
241
196
|
</Link>
|
|
242
197
|
</VStack>
|
|
243
198
|
)}
|
|
@@ -245,16 +200,22 @@ export default function LoginSplit() {
|
|
|
245
200
|
</VStack>
|
|
246
201
|
|
|
247
202
|
<Button
|
|
248
|
-
label=
|
|
203
|
+
label={AUTH_PRIMARY_CTA}
|
|
249
204
|
variant="primary"
|
|
250
205
|
size="lg"
|
|
251
206
|
isLoading={isLoading}
|
|
252
207
|
onClick={handleLogin}
|
|
253
208
|
/>
|
|
254
209
|
|
|
255
|
-
<Divider label=
|
|
210
|
+
<Divider label={AUTH_SSO_DIVIDER} />
|
|
256
211
|
|
|
257
|
-
|
|
212
|
+
{/* minWidth (not a fixed 2-up) so the pair stacks inside
|
|
213
|
+
the narrow single-column container instead of
|
|
214
|
+
giving each button ~114px for an icon + label. */}
|
|
215
|
+
<Grid
|
|
216
|
+
columns={{minWidth: 200, repeat: 'fit'}}
|
|
217
|
+
gap={3}
|
|
218
|
+
justify="stretch">
|
|
258
219
|
<Button
|
|
259
220
|
label="Login with Apple"
|
|
260
221
|
variant="secondary"
|
|
@@ -275,9 +236,9 @@ export default function LoginSplit() {
|
|
|
275
236
|
|
|
276
237
|
{!isSuccess && (
|
|
277
238
|
<Text type="supporting" color="secondary">
|
|
278
|
-
|
|
239
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
279
240
|
<Link href="#/templates/login-split" type="supporting">
|
|
280
|
-
|
|
241
|
+
{AUTH_SIGNUP_LINK}
|
|
281
242
|
</Link>
|
|
282
243
|
</Text>
|
|
283
244
|
)}
|
|
@@ -286,28 +247,27 @@ export default function LoginSplit() {
|
|
|
286
247
|
|
|
287
248
|
{/* Cover art — the transparent Card clips it to rounded
|
|
288
249
|
corners (overflow:clip + radius), so the art needs no radius. */}
|
|
289
|
-
<
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
</div>
|
|
250
|
+
<Card
|
|
251
|
+
variant="transparent"
|
|
252
|
+
padding={0}
|
|
253
|
+
width="100%"
|
|
254
|
+
height="100%"
|
|
255
|
+
className="login-split-image">
|
|
256
|
+
<SceneCastle variant="rooftops" />
|
|
257
|
+
</Card>
|
|
298
258
|
</Grid>
|
|
299
259
|
</Card>
|
|
300
|
-
</
|
|
260
|
+
</Center>
|
|
301
261
|
|
|
302
262
|
<VStack hAlign="center">
|
|
303
263
|
<Text type="supporting" color="secondary">
|
|
304
|
-
|
|
264
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
305
265
|
<Link href="#/templates/login-split" type="supporting">
|
|
306
|
-
|
|
266
|
+
{AUTH_TERMS_SERVICE}
|
|
307
267
|
</Link>{' '}
|
|
308
268
|
and{' '}
|
|
309
269
|
<Link href="#/templates/login-split" type="supporting">
|
|
310
|
-
|
|
270
|
+
{AUTH_TERMS_PRIVACY}
|
|
311
271
|
</Link>
|
|
312
272
|
.
|
|
313
273
|
</Text>
|