astryx-dracula 0.2.1 → 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 +17 -12
- 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 +66 -33
- package/templates/ai-chat.tsx +36 -19
- package/templates/blank.tsx +4 -3
- package/templates/centered-hero.tsx +14 -125
- package/templates/classic-gallery.tsx +20 -66
- package/templates/contact-form.tsx +59 -39
- package/templates/dashboard-portfolio.tsx +143 -226
- package/templates/dashboard.tsx +122 -122
- package/templates/detail-page.tsx +95 -91
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +83 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +29 -19
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +52 -74
- package/templates/editor.tsx +112 -84
- package/templates/file-explorer.tsx +131 -84
- package/templates/form-two-column.tsx +61 -125
- package/templates/gallery-hero.tsx +15 -171
- package/templates/ide.tsx +22 -20
- package/templates/incident-console.tsx +104 -70
- package/templates/kanban-board.tsx +99 -42
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +37 -42
- package/templates/login-card.tsx +58 -87
- package/templates/login-split.tsx +88 -126
- package/templates/login-sso.tsx +105 -64
- package/templates/login.tsx +107 -53
- package/templates/messaging-shell.tsx +171 -144
- package/templates/mixed-gallery.tsx +29 -130
- package/templates/payment-form.tsx +368 -290
- package/templates/product-detail.tsx +39 -58
- package/templates/product-gallery.tsx +30 -41
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +135 -282
- package/templates/settings-sidebar.tsx +178 -268
- package/templates/settings.tsx +41 -14
- package/templates/shell-nav.tsx +31 -24
- package/templates/shell-side-nav.tsx +29 -20
- package/templates/shell-top-nav.tsx +80 -44
- package/templates/side-gallery.tsx +38 -94
- package/templates/table-grouped.tsx +62 -34
- package/templates/table-page-chart.tsx +23 -302
- package/templates/table-page-heatmap-status.tsx +156 -92
- package/templates/table-page-shoe-store-heatmap.tsx +18 -293
- package/templates/table-page.tsx +23 -7
- package/templates/table.tsx +23 -8
- 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 +299 -237
- package/theme.css +76 -14
- package/tokens.css +425 -30
package/templates/login-card.tsx
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// C[
|
|
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])
|
|
4
4
|
|
|
5
|
-
import {useState,
|
|
6
|
-
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';
|
|
7
23
|
import {VStack} from '@astryxdesign/core/Layout';
|
|
8
24
|
import {Center} from '@astryxdesign/core/Center';
|
|
9
25
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
@@ -12,87 +28,43 @@ import {Button} from '@astryxdesign/core/Button';
|
|
|
12
28
|
import {Card} from '@astryxdesign/core/Card';
|
|
13
29
|
import {Link} from '@astryxdesign/core/Link';
|
|
14
30
|
import {Divider} from '@astryxdesign/core/Divider';
|
|
15
|
-
import {Icon} from '@astryxdesign/core/Icon';
|
|
16
31
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<svg
|
|
20
|
-
viewBox="0 0 24 24"
|
|
21
|
-
fill="currentColor"
|
|
22
|
-
width={16}
|
|
23
|
-
height={16}
|
|
24
|
-
aria-hidden="true"
|
|
25
|
-
{...props}>
|
|
26
|
-
<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" />
|
|
27
|
-
</svg>
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
const GoogleIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|
31
|
-
<svg
|
|
32
|
-
viewBox="0 0 24 24"
|
|
33
|
-
fill="currentColor"
|
|
34
|
-
width={16}
|
|
35
|
-
height={16}
|
|
36
|
-
aria-hidden="true"
|
|
37
|
-
{...props}>
|
|
38
|
-
<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" />
|
|
39
|
-
<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" />
|
|
40
|
-
<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" />
|
|
41
|
-
<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" />
|
|
42
|
-
</svg>
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
// Standalone auth page paints its own body background (no host shell).
|
|
46
|
-
const pageStyle: CSSProperties = {
|
|
47
|
-
minHeight: '100%',
|
|
48
|
-
backgroundColor: 'var(--color-background-body)',
|
|
49
|
-
padding: 'var(--spacing-6)',
|
|
50
|
-
};
|
|
51
|
-
// Cap the column at 400px but let it shrink to fit narrow screens (Stack
|
|
52
|
-
// has no maxWidth prop, so it's set here).
|
|
53
|
-
const contentStyle: CSSProperties = {
|
|
54
|
-
width: '100%',
|
|
55
|
-
maxWidth: 400,
|
|
56
|
-
};
|
|
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';
|
|
57
34
|
|
|
58
35
|
export default function LoginCard() {
|
|
59
36
|
const [email, setEmail] = useState('');
|
|
60
37
|
const [password, setPassword] = useState('');
|
|
61
|
-
const [
|
|
62
|
-
const [isLoading,
|
|
38
|
+
const [error, setError] = useState<string | null>(null);
|
|
39
|
+
const [isLoading, startTransition] = useTransition();
|
|
63
40
|
|
|
64
41
|
const handleLogin = () => {
|
|
65
42
|
if (!email || !password) {
|
|
66
|
-
|
|
43
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
67
44
|
return;
|
|
68
45
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}, 2000);
|
|
46
|
+
setError(null);
|
|
47
|
+
startTransition(async () => {
|
|
48
|
+
await demoLogin();
|
|
49
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
50
|
+
});
|
|
75
51
|
};
|
|
76
52
|
|
|
77
53
|
return (
|
|
78
54
|
<Center axis="both" style={pageStyle}>
|
|
79
55
|
<VStack gap={4} hAlign="center" style={contentStyle}>
|
|
80
|
-
|
|
81
|
-
<VStack gap={2} hAlign="center">
|
|
82
|
-
<Icon icon={Moon} size="lg" color="accent" />
|
|
83
|
-
<Text type="body" weight="bold" size="lg">
|
|
84
|
-
Castle Dracula
|
|
85
|
-
</Text>
|
|
86
|
-
</VStack>
|
|
56
|
+
<LoginBrand />
|
|
87
57
|
|
|
88
58
|
{/* Card */}
|
|
89
|
-
<Card padding={
|
|
59
|
+
<Card padding={4} width="100%">
|
|
90
60
|
<VStack gap={4} hAlign="stretch">
|
|
91
61
|
{/* Header */}
|
|
92
62
|
<VStack gap={1} hAlign="center">
|
|
93
|
-
<Heading level={
|
|
63
|
+
<Heading level={1} type="display-2">
|
|
64
|
+
{AUTH_HEADING}
|
|
65
|
+
</Heading>
|
|
94
66
|
<Text type="body" color="secondary">
|
|
95
|
-
|
|
67
|
+
{AUTH_SUBTITLE}
|
|
96
68
|
</Text>
|
|
97
69
|
</VStack>
|
|
98
70
|
|
|
@@ -102,40 +74,39 @@ export default function LoginCard() {
|
|
|
102
74
|
label="Email"
|
|
103
75
|
isLabelHidden
|
|
104
76
|
type="email"
|
|
105
|
-
|
|
77
|
+
{...inputAutoComplete('email')}
|
|
78
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
106
79
|
value={email}
|
|
107
|
-
onChange={
|
|
80
|
+
onChange={(v: string) => {
|
|
81
|
+
setEmail(v);
|
|
82
|
+
setError(null);
|
|
83
|
+
}}
|
|
108
84
|
size="lg"
|
|
85
|
+
onEnter={handleLogin}
|
|
86
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
109
87
|
/>
|
|
110
88
|
<VStack gap={1}>
|
|
111
89
|
<TextInput
|
|
112
90
|
label="Password"
|
|
113
91
|
isLabelHidden
|
|
114
|
-
placeholder=
|
|
92
|
+
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
115
93
|
type="password"
|
|
94
|
+
{...inputAutoComplete('current-password')}
|
|
116
95
|
value={password}
|
|
117
96
|
onChange={(v: string) => {
|
|
118
97
|
setPassword(v);
|
|
119
|
-
|
|
98
|
+
setError(null);
|
|
120
99
|
}}
|
|
121
100
|
size="lg"
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
? {
|
|
125
|
-
type: 'error',
|
|
126
|
-
message: 'Wrong incantation. Try again.',
|
|
127
|
-
}
|
|
128
|
-
: undefined
|
|
129
|
-
}
|
|
101
|
+
onEnter={handleLogin}
|
|
102
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
130
103
|
/>
|
|
131
|
-
{
|
|
104
|
+
{error && (
|
|
132
105
|
<VStack hAlign="end">
|
|
133
106
|
<Link
|
|
134
107
|
href="#/templates/login-card"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
type="supporting">
|
|
138
|
-
Forgot password?
|
|
108
|
+
color="secondary">
|
|
109
|
+
{AUTH_FORGOT_PASSWORD}
|
|
139
110
|
</Link>
|
|
140
111
|
</VStack>
|
|
141
112
|
)}
|
|
@@ -144,7 +115,7 @@ export default function LoginCard() {
|
|
|
144
115
|
|
|
145
116
|
{/* Login button */}
|
|
146
117
|
<Button
|
|
147
|
-
label=
|
|
118
|
+
label={AUTH_PRIMARY_CTA}
|
|
148
119
|
variant="primary"
|
|
149
120
|
size="lg"
|
|
150
121
|
isLoading={isLoading}
|
|
@@ -152,7 +123,7 @@ export default function LoginCard() {
|
|
|
152
123
|
/>
|
|
153
124
|
|
|
154
125
|
{/* Divider */}
|
|
155
|
-
<Divider label=
|
|
126
|
+
<Divider label={AUTH_SSO_DIVIDER} />
|
|
156
127
|
|
|
157
128
|
{/* Social buttons */}
|
|
158
129
|
<VStack gap={3} hAlign="stretch">
|
|
@@ -173,9 +144,9 @@ export default function LoginCard() {
|
|
|
173
144
|
{/* Sign up link */}
|
|
174
145
|
<VStack hAlign="center">
|
|
175
146
|
<Text type="supporting" color="secondary">
|
|
176
|
-
|
|
147
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
177
148
|
<Link href="#/templates/login-card" type="supporting">
|
|
178
|
-
|
|
149
|
+
{AUTH_SIGNUP_LINK}
|
|
179
150
|
</Link>
|
|
180
151
|
</Text>
|
|
181
152
|
</VStack>
|
|
@@ -185,13 +156,13 @@ export default function LoginCard() {
|
|
|
185
156
|
{/* Terms */}
|
|
186
157
|
<VStack hAlign="center" width="100%">
|
|
187
158
|
<Text type="supporting" color="secondary" justify="center">
|
|
188
|
-
|
|
159
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
189
160
|
<Link href="#/templates/login-card" type="supporting">
|
|
190
|
-
|
|
161
|
+
{AUTH_TERMS_SERVICE}
|
|
191
162
|
</Link>{' '}
|
|
192
163
|
and{' '}
|
|
193
164
|
<Link href="#/templates/login-card" type="supporting">
|
|
194
|
-
|
|
165
|
+
{AUTH_TERMS_PRIVACY}
|
|
195
166
|
</Link>
|
|
196
167
|
.
|
|
197
168
|
</Text>
|
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// Ctr > V[
|
|
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])
|
|
4
4
|
|
|
5
|
-
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';
|
|
6
26
|
import {VStack, HStack, StackItem} from '@astryxdesign/core/Layout';
|
|
7
27
|
import {Grid} from '@astryxdesign/core/Grid';
|
|
8
28
|
import {Center} from '@astryxdesign/core/Center';
|
|
@@ -21,77 +41,9 @@ import {Divider} from '@astryxdesign/core/Divider';
|
|
|
21
41
|
// CheckCircleIcon. The SquaresPlusIcon brand mark has no place here.
|
|
22
42
|
import {Moon, CircleCheck} from 'lucide-react';
|
|
23
43
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
viewBox="0 0 24 24"
|
|
28
|
-
fill="currentColor"
|
|
29
|
-
width={16}
|
|
30
|
-
height={16}
|
|
31
|
-
aria-hidden="true"
|
|
32
|
-
{...props}>
|
|
33
|
-
<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" />
|
|
34
|
-
</svg>
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const GoogleIcon = (props: React.SVGProps<SVGSVGElement>) => (
|
|
38
|
-
<svg
|
|
39
|
-
viewBox="0 0 24 24"
|
|
40
|
-
fill="currentColor"
|
|
41
|
-
width={16}
|
|
42
|
-
height={16}
|
|
43
|
-
aria-hidden="true"
|
|
44
|
-
{...props}>
|
|
45
|
-
<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" />
|
|
46
|
-
<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" />
|
|
47
|
-
<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" />
|
|
48
|
-
<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" />
|
|
49
|
-
</svg>
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Cover art is inline Dracula SVG — moonlit rooftops in brand tokens, no
|
|
53
|
-
// external image. Fills the panel via slice, like the placeholder before it.
|
|
54
|
-
const coverArt: CSSProperties = {
|
|
55
|
-
width: '100%',
|
|
56
|
-
height: '100%',
|
|
57
|
-
display: 'block',
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const nightCoverArt = (
|
|
61
|
-
<svg
|
|
62
|
-
viewBox="0 0 400 300"
|
|
63
|
-
preserveAspectRatio="xMidYMid slice"
|
|
64
|
-
style={coverArt}
|
|
65
|
-
role="img"
|
|
66
|
-
aria-label="Harvest moon over the castle rooftops">
|
|
67
|
-
<rect width="400" height="300" fill="var(--dracula-bg)" />
|
|
68
|
-
<circle cx="68" cy="52" r="2" fill="var(--dracula-fg)" />
|
|
69
|
-
<circle cx="140" cy="36" r="1.5" fill="var(--dracula-comment)" />
|
|
70
|
-
<circle cx="330" cy="70" r="2" fill="var(--dracula-fg)" />
|
|
71
|
-
<circle cx="292" cy="34" r="1.5" fill="var(--dracula-comment)" />
|
|
72
|
-
<circle cx="360" cy="140" r="1.5" fill="var(--dracula-comment)" />
|
|
73
|
-
<circle cx="200" cy="118" r="52" fill="var(--dracula-yellow)" />
|
|
74
|
-
<circle cx="182" cy="104" r="44" fill="var(--dracula-bg)" />
|
|
75
|
-
<g
|
|
76
|
-
fill="var(--dracula-current-line)"
|
|
77
|
-
stroke="var(--dracula-comment)"
|
|
78
|
-
strokeWidth="3"
|
|
79
|
-
strokeLinejoin="round">
|
|
80
|
-
<rect x="20" y="220" width="110" height="80" />
|
|
81
|
-
<path d="M20 220 L75 178 L130 220 Z" />
|
|
82
|
-
<rect x="150" y="200" width="100" height="100" />
|
|
83
|
-
<path d="M150 200 L200 162 L250 200 Z" />
|
|
84
|
-
<rect x="270" y="228" width="110" height="72" />
|
|
85
|
-
<path d="M270 228 L325 190 L380 228 Z" />
|
|
86
|
-
</g>
|
|
87
|
-
<g fill="var(--dracula-purple)">
|
|
88
|
-
<rect x="58" y="244" width="14" height="18" />
|
|
89
|
-
<rect x="188" y="224" width="14" height="18" />
|
|
90
|
-
<rect x="312" y="250" width="14" height="18" />
|
|
91
|
-
</g>
|
|
92
|
-
</svg>
|
|
93
|
-
);
|
|
94
|
-
|
|
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.
|
|
95
47
|
// Grid emits minmax(MIN, 1fr) where MIN is a hard floor, so MIN plus the
|
|
96
48
|
// grid inset and page padding must fit the narrowest phone or the column is
|
|
97
49
|
// clipped. 320 − 2×24 (page) − 2×16 (stacked inset) = 240.
|
|
@@ -124,7 +76,7 @@ const LOGIN_SPLIT_CSS = `
|
|
|
124
76
|
.login-split-grid {
|
|
125
77
|
container-type: inline-size;
|
|
126
78
|
container-name: login-split;
|
|
127
|
-
padding: var(--spacing-
|
|
79
|
+
padding: var(--spacing-4);
|
|
128
80
|
}
|
|
129
81
|
.login-split-image {
|
|
130
82
|
width: 100%;
|
|
@@ -132,7 +84,7 @@ const LOGIN_SPLIT_CSS = `
|
|
|
132
84
|
}
|
|
133
85
|
@container login-split (max-width: 511px) {
|
|
134
86
|
.login-split-grid {
|
|
135
|
-
padding: var(--spacing-
|
|
87
|
+
padding: var(--spacing-3);
|
|
136
88
|
}
|
|
137
89
|
.login-split-image {
|
|
138
90
|
order: -1;
|
|
@@ -143,28 +95,27 @@ const LOGIN_SPLIT_CSS = `
|
|
|
143
95
|
export default function LoginSplit() {
|
|
144
96
|
const [email, setEmail] = useState('');
|
|
145
97
|
const [password, setPassword] = useState('');
|
|
146
|
-
const [
|
|
147
|
-
const [isLoading,
|
|
98
|
+
const [error, setError] = useState<string | null>(null);
|
|
99
|
+
const [isLoading, startTransition] = useTransition();
|
|
148
100
|
const [isSuccess, setIsSuccess] = useState(false);
|
|
149
101
|
|
|
150
102
|
const handleLogin = () => {
|
|
151
103
|
if (!email || !password) {
|
|
152
|
-
|
|
104
|
+
setError(AUTH_ERROR_MESSAGE);
|
|
153
105
|
return;
|
|
154
106
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
setIsLoading(false);
|
|
107
|
+
setError(null);
|
|
108
|
+
startTransition(async () => {
|
|
109
|
+
await demoLogin();
|
|
159
110
|
setIsSuccess(true);
|
|
160
|
-
}
|
|
111
|
+
});
|
|
161
112
|
};
|
|
162
113
|
|
|
163
114
|
return (
|
|
164
115
|
<Center axis="both" style={pageStyle}>
|
|
165
116
|
<style>{LOGIN_SPLIT_CSS}</style>
|
|
166
117
|
<VStack gap={4} width="100%">
|
|
167
|
-
<
|
|
118
|
+
<Center axis="horizontal" style={cardWrap}>
|
|
168
119
|
<Card padding={0} width="100%">
|
|
169
120
|
<Grid
|
|
170
121
|
columns={{minWidth: COLUMN_MIN_WIDTH, repeat: 'fit'}}
|
|
@@ -175,9 +126,9 @@ export default function LoginSplit() {
|
|
|
175
126
|
<Section variant="transparent" padding={0} height="100%">
|
|
176
127
|
<VStack gap={4} height="100%">
|
|
177
128
|
<HStack gap={2} vAlign="center">
|
|
178
|
-
<Icon icon={Moon} color="
|
|
129
|
+
<Icon icon={Moon} color="secondary" size="lg" />
|
|
179
130
|
<Text type="body" weight="semibold">
|
|
180
|
-
|
|
131
|
+
{AUTH_BRAND_NAME}
|
|
181
132
|
</Text>
|
|
182
133
|
</HStack>
|
|
183
134
|
|
|
@@ -187,16 +138,22 @@ export default function LoginSplit() {
|
|
|
187
138
|
<EmptyState
|
|
188
139
|
title="You're in for the night"
|
|
189
140
|
description="Drifting to your crypt…"
|
|
190
|
-
icon={
|
|
141
|
+
icon={
|
|
142
|
+
<Icon
|
|
143
|
+
icon={CircleCheck}
|
|
144
|
+
size="lg"
|
|
145
|
+
color="secondary"
|
|
146
|
+
/>
|
|
147
|
+
}
|
|
191
148
|
/>
|
|
192
149
|
) : (
|
|
193
150
|
<VStack gap={4} hAlign="stretch" width="100%">
|
|
194
151
|
<VStack gap={1}>
|
|
195
|
-
<Heading level={2
|
|
196
|
-
|
|
152
|
+
<Heading level={1} type="display-2">
|
|
153
|
+
{AUTH_HEADING}
|
|
197
154
|
</Heading>
|
|
198
155
|
<Text type="body" color="secondary">
|
|
199
|
-
|
|
156
|
+
{AUTH_SUBTITLE}
|
|
200
157
|
</Text>
|
|
201
158
|
</VStack>
|
|
202
159
|
|
|
@@ -205,37 +162,37 @@ export default function LoginSplit() {
|
|
|
205
162
|
label="Email"
|
|
206
163
|
isLabelHidden
|
|
207
164
|
type="email"
|
|
208
|
-
|
|
165
|
+
{...inputAutoComplete('email')}
|
|
166
|
+
placeholder={AUTH_EMAIL_PLACEHOLDER}
|
|
209
167
|
value={email}
|
|
210
|
-
onChange={
|
|
168
|
+
onChange={(v: string) => {
|
|
169
|
+
setEmail(v);
|
|
170
|
+
setError(null);
|
|
171
|
+
}}
|
|
211
172
|
size="lg"
|
|
173
|
+
onEnter={handleLogin}
|
|
174
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
212
175
|
/>
|
|
213
176
|
<VStack gap={1}>
|
|
214
177
|
<TextInput
|
|
215
178
|
label="Password"
|
|
216
179
|
isLabelHidden
|
|
217
|
-
placeholder=
|
|
180
|
+
placeholder={AUTH_PASSWORD_PLACEHOLDER}
|
|
218
181
|
type="password"
|
|
182
|
+
{...inputAutoComplete('current-password')}
|
|
219
183
|
value={password}
|
|
220
184
|
onChange={(v: string) => {
|
|
221
185
|
setPassword(v);
|
|
222
|
-
|
|
186
|
+
setError(null);
|
|
223
187
|
}}
|
|
224
188
|
size="lg"
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
? {
|
|
228
|
-
type: 'error',
|
|
229
|
-
message:
|
|
230
|
-
'Wrong incantation. Try again.',
|
|
231
|
-
}
|
|
232
|
-
: undefined
|
|
233
|
-
}
|
|
189
|
+
onEnter={handleLogin}
|
|
190
|
+
status={error ? {type: 'error', message: error} : undefined}
|
|
234
191
|
/>
|
|
235
|
-
{
|
|
192
|
+
{error && (
|
|
236
193
|
<VStack hAlign="end">
|
|
237
194
|
<Link href="#/templates/login-split">
|
|
238
|
-
|
|
195
|
+
{AUTH_FORGOT_PASSWORD}
|
|
239
196
|
</Link>
|
|
240
197
|
</VStack>
|
|
241
198
|
)}
|
|
@@ -243,16 +200,22 @@ export default function LoginSplit() {
|
|
|
243
200
|
</VStack>
|
|
244
201
|
|
|
245
202
|
<Button
|
|
246
|
-
label=
|
|
203
|
+
label={AUTH_PRIMARY_CTA}
|
|
247
204
|
variant="primary"
|
|
248
205
|
size="lg"
|
|
249
206
|
isLoading={isLoading}
|
|
250
207
|
onClick={handleLogin}
|
|
251
208
|
/>
|
|
252
209
|
|
|
253
|
-
<Divider label=
|
|
210
|
+
<Divider label={AUTH_SSO_DIVIDER} />
|
|
254
211
|
|
|
255
|
-
|
|
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">
|
|
256
219
|
<Button
|
|
257
220
|
label="Login with Apple"
|
|
258
221
|
variant="secondary"
|
|
@@ -273,9 +236,9 @@ export default function LoginSplit() {
|
|
|
273
236
|
|
|
274
237
|
{!isSuccess && (
|
|
275
238
|
<Text type="supporting" color="secondary">
|
|
276
|
-
|
|
277
|
-
<Link href="#/templates/login-split">
|
|
278
|
-
|
|
239
|
+
{AUTH_SIGNUP_PROMPT}{' '}
|
|
240
|
+
<Link href="#/templates/login-split" type="supporting">
|
|
241
|
+
{AUTH_SIGNUP_LINK}
|
|
279
242
|
</Link>
|
|
280
243
|
</Text>
|
|
281
244
|
)}
|
|
@@ -284,28 +247,27 @@ export default function LoginSplit() {
|
|
|
284
247
|
|
|
285
248
|
{/* Cover art — the transparent Card clips it to rounded
|
|
286
249
|
corners (overflow:clip + radius), so the art needs no radius. */}
|
|
287
|
-
<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
</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>
|
|
296
258
|
</Grid>
|
|
297
259
|
</Card>
|
|
298
|
-
</
|
|
260
|
+
</Center>
|
|
299
261
|
|
|
300
262
|
<VStack hAlign="center">
|
|
301
263
|
<Text type="supporting" color="secondary">
|
|
302
|
-
|
|
303
|
-
<Link href="#/templates/login-split">
|
|
304
|
-
|
|
264
|
+
{AUTH_TERMS_PREFIX}{' '}
|
|
265
|
+
<Link href="#/templates/login-split" type="supporting">
|
|
266
|
+
{AUTH_TERMS_SERVICE}
|
|
305
267
|
</Link>{' '}
|
|
306
268
|
and{' '}
|
|
307
|
-
<Link href="#/templates/login-split">
|
|
308
|
-
|
|
269
|
+
<Link href="#/templates/login-split" type="supporting">
|
|
270
|
+
{AUTH_TERMS_PRIVACY}
|
|
309
271
|
</Link>
|
|
310
272
|
.
|
|
311
273
|
</Text>
|