astryx-dracula 0.3.1 → 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/USAGE.md +25 -0
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +384 -3
- package/astryx-dracula.variants.d.ts +2 -14
- package/icons.mjs +125 -0
- package/package.json +21 -9
- 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/revenue-chart.tsx +63 -7
- package/shared/scene-hues.ts +0 -8
- package/shared/scene-tile.tsx +3 -11
- package/shared/settings-data.ts +71 -1
- package/shared/settings-rows.tsx +76 -30
- package/shared/stars.tsx +28 -0
- package/templates/ai-chat-landing.tsx +105 -67
- package/templates/ai-chat.tsx +67 -80
- package/templates/centered-hero.tsx +2 -2
- package/templates/contact-form.tsx +11 -10
- package/templates/dashboard-portfolio.tsx +77 -155
- package/templates/dashboard.tsx +24 -137
- package/templates/detail-page.tsx +25 -22
- package/templates/documentation-design.tsx +39 -478
- package/templates/documentation-technical.tsx +13 -31
- package/templates/editor.tsx +168 -185
- package/templates/file-explorer.tsx +91 -93
- package/templates/form-two-column.tsx +1 -1
- package/templates/ide.tsx +18 -24
- package/templates/incident-console.tsx +67 -42
- package/templates/kanban-board.tsx +36 -85
- package/templates/library.tsx +42 -66
- package/templates/login-card.tsx +33 -123
- package/templates/login-split.tsx +96 -119
- package/templates/login-sso.tsx +69 -74
- package/templates/login.tsx +21 -118
- package/templates/messaging-shell.tsx +162 -168
- package/templates/payment-form.tsx +63 -71
- package/templates/product-detail.tsx +11 -46
- package/templates/product-tour.tsx +38 -60
- package/templates/settings-dialog.tsx +93 -118
- package/templates/settings-sidebar.tsx +122 -143
- 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 +86 -136
- 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 +18 -2
- package/templates/tech-report.tsx +59 -73
- package/templates/theme-showcase.tsx +124 -415
- package/theme.css +211 -119
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
-
// L > (LC > V[g=8] > (V[g=2] > Hd"Getting started with Astryx Dracula"[level=1] + Tx"Last updated March 30, 2026"[t=supporting]) + (C > V[g=3] > (H[j=between] > (H[g=2] > Ic + Tx"AI Assistance"[t=body]) +
|
|
3
|
+
// L > (LC > V[g=8] > (V[g=2] > Hd"Getting started with Astryx Dracula"[level=1] + Tx"Last updated March 30, 2026"[t=supporting]) + (C > V[g=3] > (H[j=between] > (H[g=2] > Ic + Tx"AI Assistance"[t=body]) + B"Copy prompt") + Tx"Prompt description"[t=body]) + (V[g=4] > Hd"Prerequisites"[level=2] + UL) + D + (V[g=4] > Hd"Install the package"[level=2] + Tx"Description"[t=body] + (V[g=2] > Tx"Step"[t=body] + Cd)*3) + D + (V[g=4] > Hd"Configure theming"[level=2] + Tx"Description"[t=body] + Cd + Tx"Note"[t=body]) + D + (V[g=4] > Hd"Next steps"[level=2] + UL)) + (LP > Outline)
|
|
4
4
|
|
|
5
5
|
import {useCallback, useState} from 'react';
|
|
6
6
|
import {Heading, Text} from '@astryxdesign/core/Text';
|
|
7
7
|
import {Button} from '@astryxdesign/core/Button';
|
|
8
8
|
import {Card} from '@astryxdesign/core/Card';
|
|
9
|
-
import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
|
|
10
9
|
import {List, ListItem} from '@astryxdesign/core/List';
|
|
11
10
|
import {CodeBlock} from '@astryxdesign/core/CodeBlock';
|
|
12
11
|
import {Selector} from '@astryxdesign/core/Selector';
|
|
@@ -16,7 +15,7 @@ import {Divider} from '@astryxdesign/core/Divider';
|
|
|
16
15
|
import {Icon} from '@astryxdesign/core/Icon';
|
|
17
16
|
import {Outline, type OutlineItem} from '@astryxdesign/core/Outline';
|
|
18
17
|
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
19
|
-
import {Sparkles, ClipboardCopy
|
|
18
|
+
import {Sparkles, ClipboardCopy} from 'lucide-react';
|
|
20
19
|
import {outlinePanel} from 'astryx-dracula/shared/chaptered-doc-config';
|
|
21
20
|
|
|
22
21
|
// ---------------------------------------------------------------------------
|
|
@@ -30,17 +29,8 @@ import {outlinePanel} from 'astryx-dracula/shared/chaptered-doc-config';
|
|
|
30
29
|
// Same prompt in the copy action and the visible body: one string, so the
|
|
31
30
|
// two can never drift.
|
|
32
31
|
const SETUP_PROMPT =
|
|
33
|
-
'Help me get set up with Astryx Dracula. Based on my project, do the following: 1. Install @astryxdesign/core and the StyleX compiler. 2. Wrap my app in
|
|
32
|
+
'Help me get set up with Astryx Dracula. Based on my project, do the following: 1. Install @astryxdesign/core and the StyleX compiler. 2. Wrap my app in the Theme component. 3. Replace one existing component with an Astryx equivalent. After setup, suggest relevant next steps based on my project.';
|
|
34
33
|
|
|
35
|
-
// Copies the setup prompt in the shape each tool wants to receive it. The
|
|
36
|
-
// menu cannot navigate anywhere from a static template, so the action says
|
|
37
|
-
// what it does.
|
|
38
|
-
const PROMPT_TARGETS = ['v0', 'Claude', 'ChatGPT', 'Cursor'].map(target => ({
|
|
39
|
-
label: `Copy for ${target}`,
|
|
40
|
-
onClick: () => {
|
|
41
|
-
void navigator.clipboard.writeText(SETUP_PROMPT);
|
|
42
|
-
},
|
|
43
|
-
}));
|
|
44
34
|
|
|
45
35
|
const OUTLINE_ITEMS: OutlineItem[] = [
|
|
46
36
|
{id: 'prerequisites', label: 'Prerequisites', level: 2},
|
|
@@ -124,22 +114,12 @@ export default function DocumentationTechnical() {
|
|
|
124
114
|
void navigator.clipboard.writeText(SETUP_PROMPT);
|
|
125
115
|
}}
|
|
126
116
|
/>
|
|
127
|
-
<DropdownMenu
|
|
128
|
-
button={{
|
|
129
|
-
label: 'More options',
|
|
130
|
-
variant: 'ghost',
|
|
131
|
-
size: 'sm',
|
|
132
|
-
isIconOnly: true,
|
|
133
|
-
icon: <Icon icon={ChevronDown} />,
|
|
134
|
-
}}
|
|
135
|
-
items={PROMPT_TARGETS}
|
|
136
|
-
/>
|
|
137
117
|
</HStack>
|
|
138
118
|
<Text type="body" color="secondary">
|
|
139
119
|
Help me get set up with Astryx Dracula. Based on my project,
|
|
140
120
|
do the following: 1. Install @astryxdesign/core and the
|
|
141
|
-
StyleX compiler. 2. Wrap my app in
|
|
142
|
-
one existing component with an Astryx equivalent.
|
|
121
|
+
StyleX compiler. 2. Wrap my app in the Theme component. 3.
|
|
122
|
+
Replace one existing component with an Astryx equivalent.
|
|
143
123
|
</Text>
|
|
144
124
|
</VStack>
|
|
145
125
|
</Card>
|
|
@@ -225,17 +205,19 @@ export default function App() {
|
|
|
225
205
|
</Heading>
|
|
226
206
|
<Text type="body">
|
|
227
207
|
Astryx ships with a default theme that works out of the box. To
|
|
228
|
-
customize colors, typography, and spacing, wrap your app in
|
|
229
|
-
|
|
208
|
+
customize colors, typography, and spacing, wrap your app in the
|
|
209
|
+
Theme component.
|
|
230
210
|
</Text>
|
|
231
211
|
<CodeBlock
|
|
232
|
-
code={`import {
|
|
212
|
+
code={`import { type ReactNode } from 'react';
|
|
213
|
+
import { Theme } from '@astryxdesign/core/theme';
|
|
214
|
+
import { astryxDraculaTheme } from 'astryx-dracula';
|
|
233
215
|
|
|
234
|
-
export default function App({ children }) {
|
|
216
|
+
export default function App({ children }: { children: ReactNode }) {
|
|
235
217
|
return (
|
|
236
|
-
<
|
|
218
|
+
<Theme theme={astryxDraculaTheme} mode="dark">
|
|
237
219
|
{children}
|
|
238
|
-
</
|
|
220
|
+
</Theme>
|
|
239
221
|
);
|
|
240
222
|
}`}
|
|
241
223
|
language="tsx"
|
package/templates/editor.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type CSSProperties,
|
|
10
10
|
type KeyboardEvent,
|
|
11
11
|
type MouseEvent,
|
|
12
|
+
type ReactNode,
|
|
12
13
|
type SVGProps,
|
|
13
14
|
} from 'react';
|
|
14
15
|
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
@@ -158,10 +159,6 @@ const VIEWPORT_MAX: Record<ViewportSize, number> = {
|
|
|
158
159
|
phone: 375,
|
|
159
160
|
};
|
|
160
161
|
|
|
161
|
-
let nextId = 5;
|
|
162
|
-
function uid() {
|
|
163
|
-
return String(nextId++);
|
|
164
|
-
}
|
|
165
162
|
|
|
166
163
|
const DEFAULT_BLOCKS: Block[] = [
|
|
167
164
|
{
|
|
@@ -328,6 +325,36 @@ const iconCircle: CSSProperties = {
|
|
|
328
325
|
// Properties Form
|
|
329
326
|
// ---------------------------------------------------------------------------
|
|
330
327
|
|
|
328
|
+
// Heading/description pair shared by the hero/text/features/cta branches.
|
|
329
|
+
// Hero labels it "Subheading" and adds its own button/alignment controls;
|
|
330
|
+
// the merge keeps the shared pair, not the per-branch extras.
|
|
331
|
+
function HeadingDescriptionGroup({
|
|
332
|
+
props,
|
|
333
|
+
onUpdate,
|
|
334
|
+
descriptionKey,
|
|
335
|
+
descriptionLabel,
|
|
336
|
+
}: {
|
|
337
|
+
props: Record<string, unknown>;
|
|
338
|
+
onUpdate: (key: string, value: unknown) => void;
|
|
339
|
+
descriptionKey: string;
|
|
340
|
+
descriptionLabel: string;
|
|
341
|
+
}) {
|
|
342
|
+
return (
|
|
343
|
+
<>
|
|
344
|
+
<TextInput
|
|
345
|
+
label="Heading"
|
|
346
|
+
value={(props.heading as string) ?? ''}
|
|
347
|
+
onChange={(v: string) => onUpdate('heading', v)}
|
|
348
|
+
/>
|
|
349
|
+
<TextArea
|
|
350
|
+
label={descriptionLabel}
|
|
351
|
+
value={(props[descriptionKey] as string) ?? ''}
|
|
352
|
+
onChange={(v: string) => onUpdate(descriptionKey, v)}
|
|
353
|
+
/>
|
|
354
|
+
</>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
331
358
|
function PropertiesForm({
|
|
332
359
|
block,
|
|
333
360
|
onUpdate,
|
|
@@ -341,15 +368,11 @@ function PropertiesForm({
|
|
|
341
368
|
case 'hero':
|
|
342
369
|
return (
|
|
343
370
|
<VStack gap={4}>
|
|
344
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
<TextArea
|
|
350
|
-
label="Subheading"
|
|
351
|
-
value={(props.subheading as string) ?? ''}
|
|
352
|
-
onChange={(v: string) => onUpdate('subheading', v)}
|
|
371
|
+
<HeadingDescriptionGroup
|
|
372
|
+
props={props}
|
|
373
|
+
onUpdate={onUpdate}
|
|
374
|
+
descriptionKey="subheading"
|
|
375
|
+
descriptionLabel="Subheading"
|
|
353
376
|
/>
|
|
354
377
|
<TextInput
|
|
355
378
|
label="Button Label"
|
|
@@ -372,15 +395,11 @@ function PropertiesForm({
|
|
|
372
395
|
case 'text':
|
|
373
396
|
return (
|
|
374
397
|
<VStack gap={4}>
|
|
375
|
-
<
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
<TextArea
|
|
381
|
-
label="Description"
|
|
382
|
-
value={(props.description as string) ?? ''}
|
|
383
|
-
onChange={(v: string) => onUpdate('description', v)}
|
|
398
|
+
<HeadingDescriptionGroup
|
|
399
|
+
props={props}
|
|
400
|
+
onUpdate={onUpdate}
|
|
401
|
+
descriptionKey="description"
|
|
402
|
+
descriptionLabel="Description"
|
|
384
403
|
/>
|
|
385
404
|
<TextInput
|
|
386
405
|
label="Button Label"
|
|
@@ -389,20 +408,15 @@ function PropertiesForm({
|
|
|
389
408
|
/>
|
|
390
409
|
</VStack>
|
|
391
410
|
);
|
|
392
|
-
|
|
393
411
|
case 'features':
|
|
394
412
|
case 'cta':
|
|
395
413
|
return (
|
|
396
414
|
<VStack gap={4}>
|
|
397
|
-
<
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
<TextArea
|
|
403
|
-
label="Description"
|
|
404
|
-
value={(props.description as string) ?? ''}
|
|
405
|
-
onChange={(v: string) => onUpdate('description', v)}
|
|
415
|
+
<HeadingDescriptionGroup
|
|
416
|
+
props={props}
|
|
417
|
+
onUpdate={onUpdate}
|
|
418
|
+
descriptionKey="description"
|
|
419
|
+
descriptionLabel="Description"
|
|
406
420
|
/>
|
|
407
421
|
</VStack>
|
|
408
422
|
);
|
|
@@ -458,187 +472,156 @@ function BlockPreview({
|
|
|
458
472
|
}) {
|
|
459
473
|
const {type, props} = block;
|
|
460
474
|
|
|
475
|
+
// One SelectableCard wrapper for every block; only the inner content
|
|
476
|
+
// switches. Keeps padding/label/selection in one place.
|
|
477
|
+
let content: ReactNode;
|
|
461
478
|
switch (type) {
|
|
462
479
|
case 'hero':
|
|
463
|
-
|
|
464
|
-
<
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
</Text>
|
|
476
|
-
{(props.buttonLabel as string) && (
|
|
477
|
-
<Button label={props.buttonLabel as string} />
|
|
478
|
-
)}
|
|
479
|
-
</VStack>
|
|
480
|
-
</SelectableCard>
|
|
480
|
+
content = (
|
|
481
|
+
<VStack gap={4}>
|
|
482
|
+
<Heading level={3}>
|
|
483
|
+
{(props.heading as string) || 'Hero Heading'}
|
|
484
|
+
</Heading>
|
|
485
|
+
<Text type="body" color="secondary">
|
|
486
|
+
{(props.subheading as string) || 'A whisper from the crypt…'}
|
|
487
|
+
</Text>
|
|
488
|
+
{(props.buttonLabel as string) && (
|
|
489
|
+
<Button label={props.buttonLabel as string} />
|
|
490
|
+
)}
|
|
491
|
+
</VStack>
|
|
481
492
|
);
|
|
493
|
+
break;
|
|
482
494
|
|
|
483
495
|
case 'text':
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
}
|
|
503
|
-
/>
|
|
504
|
-
</SelectableCard>
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
return (
|
|
508
|
-
<SelectableCard
|
|
509
|
-
padding={4}
|
|
510
|
-
label={block.label}
|
|
511
|
-
isSelected={isSelected}
|
|
512
|
-
onChange={onSelect}>
|
|
513
|
-
<Text type="body">
|
|
514
|
-
{(props.content as string) || 'Ink your midnight thoughts here…'}
|
|
515
|
-
</Text>
|
|
516
|
-
</SelectableCard>
|
|
496
|
+
content = props.heading ? (
|
|
497
|
+
<EmptyState
|
|
498
|
+
title={props.heading as string}
|
|
499
|
+
description={props.description as string}
|
|
500
|
+
icon={<Icon icon={FileText} color="secondary" />}
|
|
501
|
+
actions={
|
|
502
|
+
(props.buttonLabel as string) ? (
|
|
503
|
+
<Button
|
|
504
|
+
label={props.buttonLabel as string}
|
|
505
|
+
variant="secondary"
|
|
506
|
+
/>
|
|
507
|
+
) : undefined
|
|
508
|
+
}
|
|
509
|
+
/>
|
|
510
|
+
) : (
|
|
511
|
+
<Text type="body">
|
|
512
|
+
{(props.content as string) || 'Ink your midnight thoughts here…'}
|
|
513
|
+
</Text>
|
|
517
514
|
);
|
|
518
|
-
|
|
515
|
+
break;
|
|
519
516
|
case 'image':
|
|
520
|
-
|
|
521
|
-
<
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
title="Image Block"
|
|
528
|
-
description="Drop an image or enter a URL"
|
|
529
|
-
icon={<Icon icon={Image} color="secondary" />}
|
|
530
|
-
isCompact
|
|
531
|
-
/>
|
|
532
|
-
</SelectableCard>
|
|
517
|
+
content = (
|
|
518
|
+
<EmptyState
|
|
519
|
+
title="Image Block"
|
|
520
|
+
description="Drop an image or enter a URL"
|
|
521
|
+
icon={<Icon icon={Image} color="secondary" />}
|
|
522
|
+
isCompact
|
|
523
|
+
/>
|
|
533
524
|
);
|
|
525
|
+
break;
|
|
534
526
|
|
|
535
527
|
case 'button':
|
|
536
|
-
|
|
537
|
-
<
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
'primary'
|
|
548
|
-
}
|
|
549
|
-
size={(props.size as 'sm' | 'md' | 'lg') || 'md'}
|
|
550
|
-
/>
|
|
551
|
-
</Center>
|
|
552
|
-
</SelectableCard>
|
|
528
|
+
content = (
|
|
529
|
+
<Center>
|
|
530
|
+
<Button
|
|
531
|
+
label={(props.label as string) || 'Button'}
|
|
532
|
+
variant={
|
|
533
|
+
(props.variant as 'primary' | 'secondary' | 'ghost') ||
|
|
534
|
+
'primary'
|
|
535
|
+
}
|
|
536
|
+
size={(props.size as 'sm' | 'md' | 'lg') || 'md'}
|
|
537
|
+
/>
|
|
538
|
+
</Center>
|
|
553
539
|
);
|
|
540
|
+
break;
|
|
541
|
+
|
|
554
542
|
|
|
555
543
|
case 'features': {
|
|
556
544
|
const items = (props.items as Transaction[]) || [];
|
|
557
|
-
|
|
558
|
-
<
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
idKey="id"
|
|
581
|
-
hasHover
|
|
582
|
-
textOverflow="truncate"
|
|
583
|
-
/>
|
|
584
|
-
</VStack>
|
|
585
|
-
</SelectableCard>
|
|
545
|
+
content = (
|
|
546
|
+
<VStack gap={4}>
|
|
547
|
+
<HStack gap={3} vAlign="start" hAlign="between">
|
|
548
|
+
<VStack gap={1}>
|
|
549
|
+
<Heading level={3}>
|
|
550
|
+
{(props.heading as string) || 'Features'}
|
|
551
|
+
</Heading>
|
|
552
|
+
{(props.description as string) && (
|
|
553
|
+
<Text type="body" color="secondary">
|
|
554
|
+
{props.description as string}
|
|
555
|
+
</Text>
|
|
556
|
+
)}
|
|
557
|
+
</VStack>
|
|
558
|
+
<Button label="View All" variant="secondary" />
|
|
559
|
+
</HStack>
|
|
560
|
+
<Table
|
|
561
|
+
data={items}
|
|
562
|
+
columns={TRANSACTION_COLUMNS}
|
|
563
|
+
idKey="id"
|
|
564
|
+
hasHover
|
|
565
|
+
textOverflow="truncate"
|
|
566
|
+
/>
|
|
567
|
+
</VStack>
|
|
586
568
|
);
|
|
569
|
+
break;
|
|
587
570
|
}
|
|
588
571
|
|
|
589
572
|
case 'cards': {
|
|
590
573
|
const cardItems =
|
|
591
574
|
(props.cards as Array<{title: string; description: string}>) || [];
|
|
592
|
-
|
|
593
|
-
<
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
description={card.description}
|
|
607
|
-
/>
|
|
608
|
-
))}
|
|
609
|
-
</List>
|
|
610
|
-
</VStack>
|
|
611
|
-
</SelectableCard>
|
|
575
|
+
content = (
|
|
576
|
+
<VStack gap={4}>
|
|
577
|
+
<Heading level={3}>Cards</Heading>
|
|
578
|
+
<Divider />
|
|
579
|
+
<List density="balanced" hasDividers={false}>
|
|
580
|
+
{cardItems.map(card => (
|
|
581
|
+
<ListItem
|
|
582
|
+
key={card.title}
|
|
583
|
+
label={card.title}
|
|
584
|
+
description={card.description}
|
|
585
|
+
/>
|
|
586
|
+
))}
|
|
587
|
+
</List>
|
|
588
|
+
</VStack>
|
|
612
589
|
);
|
|
590
|
+
break;
|
|
613
591
|
}
|
|
614
592
|
|
|
615
593
|
case 'cta':
|
|
616
|
-
|
|
617
|
-
<
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
{(props.description as string) ||
|
|
632
|
-
'Words from the crypt gather here…'}
|
|
633
|
-
</Text>
|
|
634
|
-
</VStack>
|
|
635
|
-
</HStack>
|
|
636
|
-
</SelectableCard>
|
|
594
|
+
content = (
|
|
595
|
+
<HStack gap={4} vAlign="start">
|
|
596
|
+
<Center width={40} height={40} style={iconCircle}>
|
|
597
|
+
<Icon icon={Lock} color="secondary" />
|
|
598
|
+
</Center>
|
|
599
|
+
<VStack gap={1}>
|
|
600
|
+
<Heading level={3}>
|
|
601
|
+
{(props.heading as string) || 'Notice'}
|
|
602
|
+
</Heading>
|
|
603
|
+
<Text type="body" color="secondary">
|
|
604
|
+
{(props.description as string) ||
|
|
605
|
+
'Words from the crypt gather here…'}
|
|
606
|
+
</Text>
|
|
607
|
+
</VStack>
|
|
608
|
+
</HStack>
|
|
637
609
|
);
|
|
610
|
+
break;
|
|
638
611
|
|
|
639
612
|
default:
|
|
640
613
|
return null;
|
|
641
614
|
}
|
|
615
|
+
|
|
616
|
+
return (
|
|
617
|
+
<SelectableCard
|
|
618
|
+
padding={4}
|
|
619
|
+
label={block.label}
|
|
620
|
+
isSelected={isSelected}
|
|
621
|
+
onChange={onSelect}>
|
|
622
|
+
{content}
|
|
623
|
+
</SelectableCard>
|
|
624
|
+
);
|
|
642
625
|
}
|
|
643
626
|
|
|
644
627
|
// ---------------------------------------------------------------------------
|
|
@@ -701,7 +684,7 @@ export default function PageEditor() {
|
|
|
701
684
|
);
|
|
702
685
|
|
|
703
686
|
const addBlock = useCallback((type: BlockType) => {
|
|
704
|
-
const id =
|
|
687
|
+
const id = crypto.randomUUID();
|
|
705
688
|
const newBlock: Block = {
|
|
706
689
|
id,
|
|
707
690
|
type,
|