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.
Files changed (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +17 -12
  3. package/USAGE.md +29 -10
  4. package/astryx-dracula.js +19 -13
  5. package/astryx-dracula.variants.d.ts +15 -0
  6. package/package.json +59 -17
  7. package/shared/auth-chrome-config.ts +18 -0
  8. package/shared/auth-chrome.tsx +25 -0
  9. package/shared/auth-copy.ts +22 -0
  10. package/shared/chaptered-doc-config.ts +92 -0
  11. package/shared/chaptered-doc.tsx +332 -0
  12. package/shared/chart-hues.ts +27 -0
  13. package/shared/chart-labels.tsx +44 -0
  14. package/shared/chart-legend.tsx +100 -0
  15. package/shared/chart-panel-style.ts +13 -0
  16. package/shared/data-bar.tsx +160 -0
  17. package/shared/gallery-image.ts +20 -0
  18. package/shared/login-demo.ts +7 -0
  19. package/shared/metric-delta.tsx +36 -0
  20. package/shared/order-desk.tsx +273 -0
  21. package/shared/revenue-chart.tsx +174 -0
  22. package/shared/scene-castle.tsx +322 -0
  23. package/shared/scene-frame.tsx +88 -0
  24. package/shared/scene-hues.ts +49 -0
  25. package/shared/scene-tile.tsx +128 -0
  26. package/shared/settings-data.ts +125 -0
  27. package/shared/settings-rows.tsx +179 -0
  28. package/shared/sparkline.tsx +94 -0
  29. package/shared/sso-icons.tsx +30 -0
  30. package/templates/ai-chat-landing.tsx +66 -33
  31. package/templates/ai-chat.tsx +36 -19
  32. package/templates/blank.tsx +4 -3
  33. package/templates/centered-hero.tsx +14 -125
  34. package/templates/classic-gallery.tsx +20 -66
  35. package/templates/contact-form.tsx +59 -39
  36. package/templates/dashboard-portfolio.tsx +143 -226
  37. package/templates/dashboard.tsx +122 -122
  38. package/templates/detail-page.tsx +95 -91
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +83 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +29 -19
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +52 -74
  45. package/templates/editor.tsx +112 -84
  46. package/templates/file-explorer.tsx +131 -84
  47. package/templates/form-two-column.tsx +61 -125
  48. package/templates/gallery-hero.tsx +15 -171
  49. package/templates/ide.tsx +22 -20
  50. package/templates/incident-console.tsx +104 -70
  51. package/templates/kanban-board.tsx +99 -42
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +37 -42
  54. package/templates/login-card.tsx +58 -87
  55. package/templates/login-split.tsx +88 -126
  56. package/templates/login-sso.tsx +105 -64
  57. package/templates/login.tsx +107 -53
  58. package/templates/messaging-shell.tsx +171 -144
  59. package/templates/mixed-gallery.tsx +29 -130
  60. package/templates/payment-form.tsx +368 -290
  61. package/templates/product-detail.tsx +39 -58
  62. package/templates/product-gallery.tsx +30 -41
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +135 -282
  66. package/templates/settings-sidebar.tsx +178 -268
  67. package/templates/settings.tsx +41 -14
  68. package/templates/shell-nav.tsx +31 -24
  69. package/templates/shell-side-nav.tsx +29 -20
  70. package/templates/shell-top-nav.tsx +80 -44
  71. package/templates/side-gallery.tsx +38 -94
  72. package/templates/table-grouped.tsx +62 -34
  73. package/templates/table-page-chart.tsx +23 -302
  74. package/templates/table-page-heatmap-status.tsx +156 -92
  75. package/templates/table-page-shoe-store-heatmap.tsx +18 -293
  76. package/templates/table-page.tsx +23 -7
  77. package/templates/table.tsx +23 -8
  78. package/templates/tech-report.template.mjs +6 -0
  79. package/templates/tech-report.tsx +1486 -0
  80. package/templates/theme-showcase.template.mjs +1 -1
  81. package/templates/theme-showcase.tsx +299 -237
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -1,11 +1,19 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
  // XLE (canonical structure, validated with `bunx astryx layout check`):
3
- // L > (LP[w=320] > V[g=4] > (S[p=4] > V[g=4] > Hd"Page Editor"[level=2] + Tbar) + (V[g=4] > (TL > Tab"Blocks"! + Tab"Properties") + D + (S[p=4] > V[g=2] > Hd"Add Block"[level=3] + UL + Hd"Layers"[level=3] + UL))) + (LC > V[g=4] > C*3)
3
+ // L > (LP[w=320] > V[g=4] > (S[p=4] > V[g=4] > Hd"Page Editor"[level=1] + Tbar) + (V[g=4] > (TL > Tab"Blocks"! + Tab"Properties") + D + (S[p=4] > V[g=2] > Hd"Add Block"[level=3] + UL + Hd"Layers"[level=3] + UL))) + (LC > V[g=4] > C*3)
4
4
 
5
- import {useState, useCallback, type CSSProperties} from 'react';
5
+ import {
6
+ useState,
7
+ useCallback,
8
+ type ComponentType,
9
+ type CSSProperties,
10
+ type KeyboardEvent,
11
+ type MouseEvent,
12
+ type SVGProps,
13
+ } from 'react';
6
14
  import {useMediaQuery} from '@astryxdesign/core/hooks';
7
15
  import {Button} from '@astryxdesign/core/Button';
8
- import {Card} from '@astryxdesign/core/Card';
16
+ import {SelectableCard} from '@astryxdesign/core/SelectableCard';
9
17
  import {Center} from '@astryxdesign/core/Center';
10
18
  import {Dialog} from '@astryxdesign/core/Dialog';
11
19
  import {Divider} from '@astryxdesign/core/Divider';
@@ -20,7 +28,7 @@ import {
20
28
  } from '@astryxdesign/core/Layout';
21
29
  import {Icon} from '@astryxdesign/core/Icon';
22
30
  import {List, ListItem} from '@astryxdesign/core/List';
23
- import {Table, proportional, pixel} from '@astryxdesign/core/Table';
31
+ import {Table, proportional} from '@astryxdesign/core/Table';
24
32
  import type {TableColumn} from '@astryxdesign/core/Table';
25
33
  import {Section} from '@astryxdesign/core/Section';
26
34
  import {
@@ -56,10 +64,8 @@ import {
56
64
  Banknote,
57
65
  CarFront,
58
66
  CirclePlay,
59
- Ellipsis,
60
67
  Lock,
61
68
  } from 'lucide-react';
62
- import {Spinner} from '@astryxdesign/core/Spinner';
63
69
 
64
70
  type BlockType =
65
71
  'hero' | 'text' | 'image' | 'button' | 'cards' | 'features' | 'cta';
@@ -73,7 +79,7 @@ interface Block {
73
79
 
74
80
  type ViewportSize = 'desktop' | 'tablet' | 'phone';
75
81
  type SidebarTab = 'blocks' | 'properties';
76
- type IconComponent = React.ComponentType<React.SVGProps<SVGSVGElement>>;
82
+ type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
77
83
 
78
84
  const BLOCK_META: Record<BlockType, {label: string; icon: IconComponent}> = {
79
85
  hero: {label: 'Hero', icon: LayoutGrid},
@@ -102,11 +108,14 @@ const CATEGORY_ICONS: Record<string, IconComponent> = {
102
108
  Entertainment: CirclePlay,
103
109
  };
104
110
 
111
+ // The minimums clear the 375px phone canvas once the preview card's 24px
112
+ // padding is taken out; the widest column carries the slack and every cell
113
+ // truncates rather than pushing the table past the card.
105
114
  const TRANSACTION_COLUMNS: TableColumn<Transaction>[] = [
106
115
  {
107
116
  key: 'name',
108
117
  header: 'Transaction',
109
- width: proportional(2, {minWidth: 170}),
118
+ width: proportional(2, {minWidth: 104}),
110
119
  renderCell: (item: Transaction) => (
111
120
  <HStack gap={3} vAlign="center">
112
121
  <Icon icon={CATEGORY_ICONS[item.category] || Sparkles} />
@@ -124,7 +133,7 @@ const TRANSACTION_COLUMNS: TableColumn<Transaction>[] = [
124
133
  {
125
134
  key: 'date',
126
135
  header: 'Date',
127
- width: proportional(1, {minWidth: 100}),
136
+ width: proportional(1, {minWidth: 64}),
128
137
  renderCell: (item: Transaction) => (
129
138
  <Text type="body" color="secondary" hasTabularNumbers>
130
139
  {item.date}
@@ -134,27 +143,13 @@ const TRANSACTION_COLUMNS: TableColumn<Transaction>[] = [
134
143
  {
135
144
  key: 'amount',
136
145
  header: 'Amount',
137
- width: proportional(1, {minWidth: 90}),
146
+ width: proportional(1, {minWidth: 88}),
138
147
  renderCell: (item: Transaction) => (
139
148
  <Text type="body" weight="semibold" hasTabularNumbers>
140
149
  {item.amount}
141
150
  </Text>
142
151
  ),
143
152
  },
144
- {
145
- key: 'actions',
146
- header: '',
147
- width: pixel(44),
148
- renderCell: () => (
149
- <Button
150
- label="More"
151
- icon={<Icon icon={Ellipsis} size="sm" />}
152
- variant="ghost"
153
- size="sm"
154
- isIconOnly
155
- />
156
- ),
157
- },
158
153
  ];
159
154
 
160
155
  const VIEWPORT_MAX: Record<ViewportSize, number> = {
@@ -243,9 +238,9 @@ function defaultProps(type: BlockType): Record<string, unknown> {
243
238
  switch (type) {
244
239
  case 'hero':
245
240
  return {
246
- heading: 'New Hero',
247
- subheading: 'Subtitle goes here',
248
- buttonLabel: 'Click Me',
241
+ heading: 'Midnight at the Castle Gates',
242
+ subheading: 'Gather the coven — the night is young and the candles are lit.',
243
+ buttonLabel: 'Enter the night',
249
244
  alignment: 'center',
250
245
  };
251
246
  case 'text':
@@ -283,10 +278,11 @@ function defaultProps(type: BlockType): Record<string, unknown> {
283
278
  };
284
279
  case 'cta':
285
280
  return {
286
- heading: 'Call to Action',
287
- description: 'Description text',
288
- primaryLabel: 'Primary',
289
- secondaryLabel: 'Secondary',
281
+ heading: 'Join the Midnight Coven',
282
+ description:
283
+ 'Claim your seat at the table — letters from the crypt, once a moon.',
284
+ primaryLabel: 'Join the coven',
285
+ secondaryLabel: 'Read the lore',
290
286
  };
291
287
  }
292
288
  }
@@ -310,19 +306,21 @@ const canvasStyle = (maxWidth: number): CSSProperties => ({
310
306
  width: '100%',
311
307
  marginInline: 'auto',
312
308
  });
313
- const clickable: CSSProperties = {
314
- cursor: 'pointer',
315
- };
316
- // Selection ring on the active block — Card has no `isSelected` state.
317
- const selectedCard: CSSProperties = {
318
- outline: '2px solid',
319
- outlineColor: 'var(--color-accent)',
320
- outlineOffset: -2,
321
- };
322
- // Circular muted chip behind the CTA icon — Center handles the centering
309
+ // `clickable` and `selectedCard` are gone: the eight block cards are core's
310
+ // `SelectableCard` now, which supplies the cursor, the hover overlay
311
+ // (`SelectableCard.tsx:90` reads `--color-overlay-hover`) and the selected ring
312
+ // from the same tokens this file was hand-rolling. Worth recording why the old
313
+ // pair was wrong twice over: it was reachable only as an inline `style` spread,
314
+ // and a spread of a lookup is not a measurement — those values were never
315
+ // checked against what `Card` actually renders. The 1.4.11 non-claim on the
316
+ // ring colour is unchanged and still stands: Comment #6272A4 cannot reach
317
+ // 3:1 on #343746 at any alpha, and core's own selected ring has that ceiling.
318
+ // The sidebar keeps its width when the canvas is wider than the window.
319
+ const panelShrink: CSSProperties = {flexShrink: 0};
320
+ // Square muted chip behind the CTA icon — Center handles the centering
323
321
  // and sizing; only the surface (radius + fill) needs custom CSS.
324
322
  const iconCircle: CSSProperties = {
325
- borderRadius: '50%',
323
+ borderRadius: 'var(--radius-element)',
326
324
  backgroundColor: 'var(--color-selection)',
327
325
  };
328
326
 
@@ -459,37 +457,41 @@ function BlockPreview({
459
457
  onSelect: () => void;
460
458
  }) {
461
459
  const {type, props} = block;
462
- const cardStyle: CSSProperties = {
463
- ...clickable,
464
- ...(isSelected ? selectedCard : null),
465
- };
466
460
 
467
461
  switch (type) {
468
462
  case 'hero':
469
463
  return (
470
- <Card padding={6} style={cardStyle} onClick={onSelect}>
464
+ <SelectableCard
465
+ padding={4}
466
+ label={block.label}
467
+ isSelected={isSelected}
468
+ onChange={onSelect}>
471
469
  <VStack gap={4}>
472
- <Heading level={2}>
470
+ <Heading level={3}>
473
471
  {(props.heading as string) || 'Hero Heading'}
474
472
  </Heading>
475
473
  <Text type="body" color="secondary">
476
- {(props.subheading as string) || 'Subtitle text goes here'}
474
+ {(props.subheading as string) || 'A whisper from the crypt…'}
477
475
  </Text>
478
476
  {(props.buttonLabel as string) && (
479
477
  <Button label={props.buttonLabel as string} />
480
478
  )}
481
479
  </VStack>
482
- </Card>
480
+ </SelectableCard>
483
481
  );
484
482
 
485
483
  case 'text':
486
484
  if (props.heading) {
487
485
  return (
488
- <Card padding={6} style={cardStyle} onClick={onSelect}>
486
+ <SelectableCard
487
+ padding={4}
488
+ label={block.label}
489
+ isSelected={isSelected}
490
+ onChange={onSelect}>
489
491
  <EmptyState
490
492
  title={props.heading as string}
491
493
  description={props.description as string}
492
- icon={<Spinner />}
494
+ icon={<Icon icon={FileText} color="secondary" />}
493
495
  actions={
494
496
  (props.buttonLabel as string) ? (
495
497
  <Button
@@ -499,32 +501,44 @@ function BlockPreview({
499
501
  ) : undefined
500
502
  }
501
503
  />
502
- </Card>
504
+ </SelectableCard>
503
505
  );
504
506
  }
505
507
  return (
506
- <Card style={cardStyle} onClick={onSelect}>
508
+ <SelectableCard
509
+ padding={4}
510
+ label={block.label}
511
+ isSelected={isSelected}
512
+ onChange={onSelect}>
507
513
  <Text type="body">
508
- {(props.content as string) || 'Text content goes here'}
514
+ {(props.content as string) || 'Ink your midnight thoughts here…'}
509
515
  </Text>
510
- </Card>
516
+ </SelectableCard>
511
517
  );
512
518
 
513
519
  case 'image':
514
520
  return (
515
- <Card style={cardStyle} onClick={onSelect}>
521
+ <SelectableCard
522
+ padding={4}
523
+ label={block.label}
524
+ isSelected={isSelected}
525
+ onChange={onSelect}>
516
526
  <EmptyState
517
527
  title="Image Block"
518
528
  description="Drop an image or enter a URL"
519
- icon={<Icon icon={Image} />}
529
+ icon={<Icon icon={Image} color="secondary" />}
520
530
  isCompact
521
531
  />
522
- </Card>
532
+ </SelectableCard>
523
533
  );
524
534
 
525
535
  case 'button':
526
536
  return (
527
- <Card padding={6} style={cardStyle} onClick={onSelect}>
537
+ <SelectableCard
538
+ padding={4}
539
+ label={block.label}
540
+ isSelected={isSelected}
541
+ onChange={onSelect}>
528
542
  <Center>
529
543
  <Button
530
544
  label={(props.label as string) || 'Button'}
@@ -535,13 +549,17 @@ function BlockPreview({
535
549
  size={(props.size as 'sm' | 'md' | 'lg') || 'md'}
536
550
  />
537
551
  </Center>
538
- </Card>
552
+ </SelectableCard>
539
553
  );
540
554
 
541
555
  case 'features': {
542
556
  const items = (props.items as Transaction[]) || [];
543
557
  return (
544
- <Card padding={6} style={cardStyle} onClick={onSelect}>
558
+ <SelectableCard
559
+ padding={4}
560
+ label={block.label}
561
+ isSelected={isSelected}
562
+ onChange={onSelect}>
545
563
  <VStack gap={4}>
546
564
  <HStack gap={3} vAlign="start" hAlign="between">
547
565
  <VStack gap={1}>
@@ -549,12 +567,12 @@ function BlockPreview({
549
567
  {(props.heading as string) || 'Features'}
550
568
  </Heading>
551
569
  {(props.description as string) && (
552
- <Text type="supporting" color="secondary">
570
+ <Text type="body" color="secondary">
553
571
  {props.description as string}
554
572
  </Text>
555
573
  )}
556
574
  </VStack>
557
- <Button label="View All" variant="secondary" size="sm" />
575
+ <Button label="View All" variant="secondary" />
558
576
  </HStack>
559
577
  <Table
560
578
  data={items}
@@ -564,7 +582,7 @@ function BlockPreview({
564
582
  textOverflow="truncate"
565
583
  />
566
584
  </VStack>
567
- </Card>
585
+ </SelectableCard>
568
586
  );
569
587
  }
570
588
 
@@ -572,41 +590,50 @@ function BlockPreview({
572
590
  const cardItems =
573
591
  (props.cards as Array<{title: string; description: string}>) || [];
574
592
  return (
575
- <Card style={cardStyle} onClick={onSelect}>
593
+ <SelectableCard
594
+ padding={4}
595
+ label={block.label}
596
+ isSelected={isSelected}
597
+ onChange={onSelect}>
576
598
  <VStack gap={4}>
577
599
  <Heading level={3}>Cards</Heading>
578
600
  <Divider />
579
601
  <List density="balanced" hasDividers={false}>
580
- {cardItems.map((card, i) => (
602
+ {cardItems.map((card) => (
581
603
  <ListItem
582
- key={i}
604
+ key={card.title}
583
605
  label={card.title}
584
606
  description={card.description}
585
607
  />
586
608
  ))}
587
609
  </List>
588
610
  </VStack>
589
- </Card>
611
+ </SelectableCard>
590
612
  );
591
613
  }
592
614
 
593
615
  case 'cta':
594
616
  return (
595
- <Card padding={6} style={cardStyle} onClick={onSelect}>
617
+ <SelectableCard
618
+ padding={4}
619
+ label={block.label}
620
+ isSelected={isSelected}
621
+ onChange={onSelect}>
596
622
  <HStack gap={4} vAlign="start">
597
623
  <Center width={40} height={40} style={iconCircle}>
598
624
  <Icon icon={Lock} color="secondary" />
599
625
  </Center>
600
626
  <VStack gap={1}>
601
- <Heading level={4}>
627
+ <Heading level={3}>
602
628
  {(props.heading as string) || 'Notice'}
603
629
  </Heading>
604
630
  <Text type="body" color="secondary">
605
- {(props.description as string) || 'Description text'}
631
+ {(props.description as string) ||
632
+ 'Words from the crypt gather here…'}
606
633
  </Text>
607
634
  </VStack>
608
635
  </HStack>
609
- </Card>
636
+ </SelectableCard>
610
637
  );
611
638
 
612
639
  default:
@@ -703,7 +730,7 @@ export default function PageEditor() {
703
730
  const blocksTabContent = (
704
731
  <VStack gap={2}>
705
732
  <VStack gap={1}>
706
- <Heading level={4}>Add Block</Heading>
733
+ <Heading level={3}>Add Block</Heading>
707
734
  <List density="balanced" hasDividers={false}>
708
735
  {(Object.keys(BLOCK_META) as BlockType[]).map(type => (
709
736
  <ListItem
@@ -719,7 +746,7 @@ export default function PageEditor() {
719
746
  </VStack>
720
747
 
721
748
  <VStack gap={1}>
722
- <Heading level={4}>Layers</Heading>
749
+ <Heading level={3}>Layers</Heading>
723
750
  <List density="balanced" hasDividers={false}>
724
751
  {blocks.map(block => (
725
752
  <ListItem
@@ -737,7 +764,7 @@ export default function PageEditor() {
737
764
  icon={<Icon icon={ChevronUp} size="sm" />}
738
765
  variant="ghost"
739
766
  size="sm"
740
- onClick={(e: React.MouseEvent) => {
767
+ onClick={(e: MouseEvent) => {
741
768
  e.stopPropagation();
742
769
  moveBlock(block.id, -1);
743
770
  }}
@@ -748,7 +775,7 @@ export default function PageEditor() {
748
775
  icon={<Icon icon={ChevronDown} size="sm" />}
749
776
  variant="ghost"
750
777
  size="sm"
751
- onClick={(e: React.MouseEvent) => {
778
+ onClick={(e: MouseEvent) => {
752
779
  e.stopPropagation();
753
780
  moveBlock(block.id, 1);
754
781
  }}
@@ -759,7 +786,7 @@ export default function PageEditor() {
759
786
  icon={<Icon icon={Trash2} size="sm" />}
760
787
  variant="ghost"
761
788
  size="sm"
762
- onClick={(e: React.MouseEvent) => {
789
+ onClick={(e: MouseEvent) => {
763
790
  e.stopPropagation();
764
791
  deleteBlock(block.id);
765
792
  }}
@@ -811,15 +838,16 @@ export default function PageEditor() {
811
838
  <LayoutPanel
812
839
  hasDivider={!isMobile}
813
840
  padding={0}
814
- style={{width: isMobile ? '100%' : 320, flexShrink: 0}}>
841
+ width={isMobile ? '100%' : 320}
842
+ style={panelShrink}>
815
843
  <VStack gap={4}>
816
- {/* Panel Header */}
844
+ {/* Panel Header — mobile/desktop branches are exclusive: exactly one h1. */}
817
845
  <Section variant="transparent" padding={4}>
818
846
  {isMobile ? (
819
847
  // Mobile: the title, an Edit button that opens the customizations
820
848
  // dialog, and the primary action.
821
849
  <HStack gap={3} vAlign="center" hAlign="between">
822
- <Heading level={2}>{pageTitle}</Heading>
850
+ <Heading level={1} type="display-2">{pageTitle}</Heading>
823
851
  <HStack gap={2} vAlign="center">
824
852
  <Button
825
853
  label="Edit"
@@ -839,7 +867,7 @@ export default function PageEditor() {
839
867
  isLabelHidden
840
868
  value={pageTitle}
841
869
  onChange={setPageTitle}
842
- onKeyDown={(e: React.KeyboardEvent) => {
870
+ onKeyDown={(e: KeyboardEvent) => {
843
871
  if (e.key === 'Enter') {
844
872
  setIsEditingTitle(false);
845
873
  }
@@ -848,7 +876,7 @@ export default function PageEditor() {
848
876
  onBlur={() => setIsEditingTitle(false)}
849
877
  />
850
878
  ) : (
851
- <Heading level={2}>{pageTitle}</Heading>
879
+ <Heading level={1} type="display-2">{pageTitle}</Heading>
852
880
  )}
853
881
 
854
882
  <Toolbar
@@ -922,7 +950,7 @@ export default function PageEditor() {
922
950
  <EmptyState
923
951
  title="No blocks yet"
924
952
  description="Add blocks from the sidebar to start building your page"
925
- icon={<Icon icon={CirclePlus} />}
953
+ icon={<Icon icon={CirclePlus} color="secondary" />}
926
954
  />
927
955
  )}
928
956
  </VStack>