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.
Files changed (83) hide show
  1. package/BRAND.md +30 -18
  2. package/README.md +22 -11
  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 +67 -32
  31. package/templates/ai-chat.tsx +41 -20
  32. package/templates/blank.tsx +5 -2
  33. package/templates/centered-hero.tsx +15 -124
  34. package/templates/classic-gallery.tsx +21 -65
  35. package/templates/contact-form.tsx +62 -40
  36. package/templates/dashboard-portfolio.tsx +146 -227
  37. package/templates/dashboard.tsx +123 -121
  38. package/templates/detail-page.tsx +101 -95
  39. package/templates/documentation-design.template.mjs +1 -1
  40. package/templates/documentation-design.tsx +85 -96
  41. package/templates/documentation-technical.template.mjs +1 -1
  42. package/templates/documentation-technical.tsx +33 -21
  43. package/templates/documentation.template.mjs +1 -1
  44. package/templates/documentation.tsx +54 -74
  45. package/templates/editor.tsx +119 -89
  46. package/templates/file-explorer.tsx +133 -84
  47. package/templates/form-two-column.tsx +62 -124
  48. package/templates/gallery-hero.tsx +17 -171
  49. package/templates/ide.tsx +27 -21
  50. package/templates/incident-console.tsx +105 -69
  51. package/templates/kanban-board.tsx +100 -41
  52. package/templates/library.template.mjs +2 -2
  53. package/templates/library.tsx +38 -41
  54. package/templates/login-card.tsx +59 -86
  55. package/templates/login-split.tsx +88 -128
  56. package/templates/login-sso.tsx +107 -68
  57. package/templates/login.tsx +108 -52
  58. package/templates/messaging-shell.tsx +172 -143
  59. package/templates/mixed-gallery.tsx +31 -130
  60. package/templates/payment-form.tsx +369 -289
  61. package/templates/product-detail.tsx +40 -57
  62. package/templates/product-gallery.tsx +31 -40
  63. package/templates/product-tour.template.mjs +6 -0
  64. package/templates/product-tour.tsx +916 -0
  65. package/templates/settings-dialog.tsx +145 -286
  66. package/templates/settings-sidebar.tsx +179 -267
  67. package/templates/settings.tsx +42 -13
  68. package/templates/shell-nav.tsx +32 -23
  69. package/templates/shell-side-nav.tsx +30 -19
  70. package/templates/shell-top-nav.tsx +81 -43
  71. package/templates/side-gallery.tsx +39 -93
  72. package/templates/table-grouped.tsx +63 -33
  73. package/templates/table-page-chart.tsx +24 -297
  74. package/templates/table-page-heatmap-status.tsx +157 -91
  75. package/templates/table-page-shoe-store-heatmap.tsx +19 -288
  76. package/templates/table-page.tsx +24 -6
  77. package/templates/table.tsx +24 -7
  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 +313 -249
  82. package/theme.css +76 -14
  83. package/tokens.css +425 -30
@@ -1,9 +1,19 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
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=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)
2
4
 
3
- 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';
4
14
  import {useMediaQuery} from '@astryxdesign/core/hooks';
5
15
  import {Button} from '@astryxdesign/core/Button';
6
- import {Card} from '@astryxdesign/core/Card';
16
+ import {SelectableCard} from '@astryxdesign/core/SelectableCard';
7
17
  import {Center} from '@astryxdesign/core/Center';
8
18
  import {Dialog} from '@astryxdesign/core/Dialog';
9
19
  import {Divider} from '@astryxdesign/core/Divider';
@@ -18,7 +28,7 @@ import {
18
28
  } from '@astryxdesign/core/Layout';
19
29
  import {Icon} from '@astryxdesign/core/Icon';
20
30
  import {List, ListItem} from '@astryxdesign/core/List';
21
- import {Table, proportional, pixel} from '@astryxdesign/core/Table';
31
+ import {Table, proportional} from '@astryxdesign/core/Table';
22
32
  import type {TableColumn} from '@astryxdesign/core/Table';
23
33
  import {Section} from '@astryxdesign/core/Section';
24
34
  import {
@@ -54,10 +64,8 @@ import {
54
64
  Banknote,
55
65
  CarFront,
56
66
  CirclePlay,
57
- Ellipsis,
58
67
  Lock,
59
68
  } from 'lucide-react';
60
- import {Spinner} from '@astryxdesign/core/Spinner';
61
69
 
62
70
  type BlockType =
63
71
  'hero' | 'text' | 'image' | 'button' | 'cards' | 'features' | 'cta';
@@ -71,7 +79,7 @@ interface Block {
71
79
 
72
80
  type ViewportSize = 'desktop' | 'tablet' | 'phone';
73
81
  type SidebarTab = 'blocks' | 'properties';
74
- type IconComponent = React.ComponentType<React.SVGProps<SVGSVGElement>>;
82
+ type IconComponent = ComponentType<SVGProps<SVGSVGElement>>;
75
83
 
76
84
  const BLOCK_META: Record<BlockType, {label: string; icon: IconComponent}> = {
77
85
  hero: {label: 'Hero', icon: LayoutGrid},
@@ -100,16 +108,19 @@ const CATEGORY_ICONS: Record<string, IconComponent> = {
100
108
  Entertainment: CirclePlay,
101
109
  };
102
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.
103
114
  const TRANSACTION_COLUMNS: TableColumn<Transaction>[] = [
104
115
  {
105
116
  key: 'name',
106
117
  header: 'Transaction',
107
- width: proportional(2, {minWidth: 170}),
118
+ width: proportional(2, {minWidth: 104}),
108
119
  renderCell: (item: Transaction) => (
109
120
  <HStack gap={3} vAlign="center">
110
121
  <Icon icon={CATEGORY_ICONS[item.category] || Sparkles} />
111
122
  <VStack gap={0}>
112
- <Text type="label" weight="semibold">
123
+ <Text type="body" weight="semibold">
113
124
  {item.name}
114
125
  </Text>
115
126
  <Text type="supporting" color="secondary">
@@ -122,7 +133,7 @@ const TRANSACTION_COLUMNS: TableColumn<Transaction>[] = [
122
133
  {
123
134
  key: 'date',
124
135
  header: 'Date',
125
- width: proportional(1, {minWidth: 100}),
136
+ width: proportional(1, {minWidth: 64}),
126
137
  renderCell: (item: Transaction) => (
127
138
  <Text type="body" color="secondary" hasTabularNumbers>
128
139
  {item.date}
@@ -132,27 +143,13 @@ const TRANSACTION_COLUMNS: TableColumn<Transaction>[] = [
132
143
  {
133
144
  key: 'amount',
134
145
  header: 'Amount',
135
- width: proportional(1, {minWidth: 90}),
146
+ width: proportional(1, {minWidth: 88}),
136
147
  renderCell: (item: Transaction) => (
137
- <Text type="label" weight="semibold" hasTabularNumbers>
148
+ <Text type="body" weight="semibold" hasTabularNumbers>
138
149
  {item.amount}
139
150
  </Text>
140
151
  ),
141
152
  },
142
- {
143
- key: 'actions',
144
- header: '',
145
- width: pixel(44),
146
- renderCell: () => (
147
- <Button
148
- label="More"
149
- icon={<Icon icon={Ellipsis} size="sm" />}
150
- variant="ghost"
151
- size="sm"
152
- isIconOnly
153
- />
154
- ),
155
- },
156
153
  ];
157
154
 
158
155
  const VIEWPORT_MAX: Record<ViewportSize, number> = {
@@ -241,9 +238,9 @@ function defaultProps(type: BlockType): Record<string, unknown> {
241
238
  switch (type) {
242
239
  case 'hero':
243
240
  return {
244
- heading: 'New Hero',
245
- subheading: 'Subtitle goes here',
246
- 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',
247
244
  alignment: 'center',
248
245
  };
249
246
  case 'text':
@@ -281,10 +278,11 @@ function defaultProps(type: BlockType): Record<string, unknown> {
281
278
  };
282
279
  case 'cta':
283
280
  return {
284
- heading: 'Call to Action',
285
- description: 'Description text',
286
- primaryLabel: 'Primary',
287
- 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',
288
286
  };
289
287
  }
290
288
  }
@@ -308,20 +306,22 @@ const canvasStyle = (maxWidth: number): CSSProperties => ({
308
306
  width: '100%',
309
307
  marginInline: 'auto',
310
308
  });
311
- const clickable: CSSProperties = {
312
- cursor: 'pointer',
313
- };
314
- // Selection ring on the active block — Card has no `isSelected` state.
315
- const selectedCard: CSSProperties = {
316
- outline: '2px solid',
317
- outlineColor: 'var(--color-border-blue)',
318
- outlineOffset: -2,
319
- };
320
- // 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
321
321
  // and sizing; only the surface (radius + fill) needs custom CSS.
322
322
  const iconCircle: CSSProperties = {
323
- borderRadius: '50%',
324
- backgroundColor: 'var(--color-background-muted)',
323
+ borderRadius: 'var(--radius-element)',
324
+ backgroundColor: 'var(--color-selection)',
325
325
  };
326
326
 
327
327
  // ---------------------------------------------------------------------------
@@ -457,37 +457,41 @@ function BlockPreview({
457
457
  onSelect: () => void;
458
458
  }) {
459
459
  const {type, props} = block;
460
- const cardStyle: CSSProperties = {
461
- ...clickable,
462
- ...(isSelected ? selectedCard : null),
463
- };
464
460
 
465
461
  switch (type) {
466
462
  case 'hero':
467
463
  return (
468
- <Card padding={6} style={cardStyle} onClick={onSelect}>
464
+ <SelectableCard
465
+ padding={4}
466
+ label={block.label}
467
+ isSelected={isSelected}
468
+ onChange={onSelect}>
469
469
  <VStack gap={4}>
470
- <Heading level={2}>
470
+ <Heading level={3}>
471
471
  {(props.heading as string) || 'Hero Heading'}
472
472
  </Heading>
473
- <Text type="supporting" color="secondary">
474
- {(props.subheading as string) || 'Subtitle text goes here'}
473
+ <Text type="body" color="secondary">
474
+ {(props.subheading as string) || 'A whisper from the crypt…'}
475
475
  </Text>
476
476
  {(props.buttonLabel as string) && (
477
477
  <Button label={props.buttonLabel as string} />
478
478
  )}
479
479
  </VStack>
480
- </Card>
480
+ </SelectableCard>
481
481
  );
482
482
 
483
483
  case 'text':
484
484
  if (props.heading) {
485
485
  return (
486
- <Card padding={6} style={cardStyle} onClick={onSelect}>
486
+ <SelectableCard
487
+ padding={4}
488
+ label={block.label}
489
+ isSelected={isSelected}
490
+ onChange={onSelect}>
487
491
  <EmptyState
488
492
  title={props.heading as string}
489
493
  description={props.description as string}
490
- icon={<Spinner />}
494
+ icon={<Icon icon={FileText} color="secondary" />}
491
495
  actions={
492
496
  (props.buttonLabel as string) ? (
493
497
  <Button
@@ -497,32 +501,44 @@ function BlockPreview({
497
501
  ) : undefined
498
502
  }
499
503
  />
500
- </Card>
504
+ </SelectableCard>
501
505
  );
502
506
  }
503
507
  return (
504
- <Card style={cardStyle} onClick={onSelect}>
508
+ <SelectableCard
509
+ padding={4}
510
+ label={block.label}
511
+ isSelected={isSelected}
512
+ onChange={onSelect}>
505
513
  <Text type="body">
506
- {(props.content as string) || 'Text content goes here'}
514
+ {(props.content as string) || 'Ink your midnight thoughts here…'}
507
515
  </Text>
508
- </Card>
516
+ </SelectableCard>
509
517
  );
510
518
 
511
519
  case 'image':
512
520
  return (
513
- <Card style={cardStyle} onClick={onSelect}>
521
+ <SelectableCard
522
+ padding={4}
523
+ label={block.label}
524
+ isSelected={isSelected}
525
+ onChange={onSelect}>
514
526
  <EmptyState
515
527
  title="Image Block"
516
528
  description="Drop an image or enter a URL"
517
- icon={<Icon icon={Image} />}
529
+ icon={<Icon icon={Image} color="secondary" />}
518
530
  isCompact
519
531
  />
520
- </Card>
532
+ </SelectableCard>
521
533
  );
522
534
 
523
535
  case 'button':
524
536
  return (
525
- <Card padding={6} style={cardStyle} onClick={onSelect}>
537
+ <SelectableCard
538
+ padding={4}
539
+ label={block.label}
540
+ isSelected={isSelected}
541
+ onChange={onSelect}>
526
542
  <Center>
527
543
  <Button
528
544
  label={(props.label as string) || 'Button'}
@@ -533,13 +549,17 @@ function BlockPreview({
533
549
  size={(props.size as 'sm' | 'md' | 'lg') || 'md'}
534
550
  />
535
551
  </Center>
536
- </Card>
552
+ </SelectableCard>
537
553
  );
538
554
 
539
555
  case 'features': {
540
556
  const items = (props.items as Transaction[]) || [];
541
557
  return (
542
- <Card padding={6} style={cardStyle} onClick={onSelect}>
558
+ <SelectableCard
559
+ padding={4}
560
+ label={block.label}
561
+ isSelected={isSelected}
562
+ onChange={onSelect}>
543
563
  <VStack gap={4}>
544
564
  <HStack gap={3} vAlign="start" hAlign="between">
545
565
  <VStack gap={1}>
@@ -547,12 +567,12 @@ function BlockPreview({
547
567
  {(props.heading as string) || 'Features'}
548
568
  </Heading>
549
569
  {(props.description as string) && (
550
- <Text type="supporting" color="secondary">
570
+ <Text type="body" color="secondary">
551
571
  {props.description as string}
552
572
  </Text>
553
573
  )}
554
574
  </VStack>
555
- <Button label="View All" variant="secondary" size="sm" />
575
+ <Button label="View All" variant="secondary" />
556
576
  </HStack>
557
577
  <Table
558
578
  data={items}
@@ -562,7 +582,7 @@ function BlockPreview({
562
582
  textOverflow="truncate"
563
583
  />
564
584
  </VStack>
565
- </Card>
585
+ </SelectableCard>
566
586
  );
567
587
  }
568
588
 
@@ -570,41 +590,50 @@ function BlockPreview({
570
590
  const cardItems =
571
591
  (props.cards as Array<{title: string; description: string}>) || [];
572
592
  return (
573
- <Card style={cardStyle} onClick={onSelect}>
593
+ <SelectableCard
594
+ padding={4}
595
+ label={block.label}
596
+ isSelected={isSelected}
597
+ onChange={onSelect}>
574
598
  <VStack gap={4}>
575
599
  <Heading level={3}>Cards</Heading>
576
600
  <Divider />
577
601
  <List density="balanced" hasDividers={false}>
578
- {cardItems.map((card, i) => (
602
+ {cardItems.map((card) => (
579
603
  <ListItem
580
- key={i}
604
+ key={card.title}
581
605
  label={card.title}
582
606
  description={card.description}
583
607
  />
584
608
  ))}
585
609
  </List>
586
610
  </VStack>
587
- </Card>
611
+ </SelectableCard>
588
612
  );
589
613
  }
590
614
 
591
615
  case 'cta':
592
616
  return (
593
- <Card padding={6} style={cardStyle} onClick={onSelect}>
617
+ <SelectableCard
618
+ padding={4}
619
+ label={block.label}
620
+ isSelected={isSelected}
621
+ onChange={onSelect}>
594
622
  <HStack gap={4} vAlign="start">
595
623
  <Center width={40} height={40} style={iconCircle}>
596
624
  <Icon icon={Lock} color="secondary" />
597
625
  </Center>
598
626
  <VStack gap={1}>
599
- <Text type="label" weight="semibold">
627
+ <Heading level={3}>
600
628
  {(props.heading as string) || 'Notice'}
601
- </Text>
602
- <Text type="supporting" color="secondary">
603
- {(props.description as string) || 'Description text'}
629
+ </Heading>
630
+ <Text type="body" color="secondary">
631
+ {(props.description as string) ||
632
+ 'Words from the crypt gather here…'}
604
633
  </Text>
605
634
  </VStack>
606
635
  </HStack>
607
- </Card>
636
+ </SelectableCard>
608
637
  );
609
638
 
610
639
  default:
@@ -701,7 +730,7 @@ export default function PageEditor() {
701
730
  const blocksTabContent = (
702
731
  <VStack gap={2}>
703
732
  <VStack gap={1}>
704
- <Heading level={4}>Add Block</Heading>
733
+ <Heading level={3}>Add Block</Heading>
705
734
  <List density="balanced" hasDividers={false}>
706
735
  {(Object.keys(BLOCK_META) as BlockType[]).map(type => (
707
736
  <ListItem
@@ -717,7 +746,7 @@ export default function PageEditor() {
717
746
  </VStack>
718
747
 
719
748
  <VStack gap={1}>
720
- <Heading level={4}>Layers</Heading>
749
+ <Heading level={3}>Layers</Heading>
721
750
  <List density="balanced" hasDividers={false}>
722
751
  {blocks.map(block => (
723
752
  <ListItem
@@ -735,7 +764,7 @@ export default function PageEditor() {
735
764
  icon={<Icon icon={ChevronUp} size="sm" />}
736
765
  variant="ghost"
737
766
  size="sm"
738
- onClick={(e: React.MouseEvent) => {
767
+ onClick={(e: MouseEvent) => {
739
768
  e.stopPropagation();
740
769
  moveBlock(block.id, -1);
741
770
  }}
@@ -746,7 +775,7 @@ export default function PageEditor() {
746
775
  icon={<Icon icon={ChevronDown} size="sm" />}
747
776
  variant="ghost"
748
777
  size="sm"
749
- onClick={(e: React.MouseEvent) => {
778
+ onClick={(e: MouseEvent) => {
750
779
  e.stopPropagation();
751
780
  moveBlock(block.id, 1);
752
781
  }}
@@ -757,7 +786,7 @@ export default function PageEditor() {
757
786
  icon={<Icon icon={Trash2} size="sm" />}
758
787
  variant="ghost"
759
788
  size="sm"
760
- onClick={(e: React.MouseEvent) => {
789
+ onClick={(e: MouseEvent) => {
761
790
  e.stopPropagation();
762
791
  deleteBlock(block.id);
763
792
  }}
@@ -809,15 +838,16 @@ export default function PageEditor() {
809
838
  <LayoutPanel
810
839
  hasDivider={!isMobile}
811
840
  padding={0}
812
- style={{width: isMobile ? '100%' : 320, flexShrink: 0}}>
841
+ width={isMobile ? '100%' : 320}
842
+ style={panelShrink}>
813
843
  <VStack gap={4}>
814
- {/* Panel Header */}
844
+ {/* Panel Header — mobile/desktop branches are exclusive: exactly one h1. */}
815
845
  <Section variant="transparent" padding={4}>
816
846
  {isMobile ? (
817
847
  // Mobile: the title, an Edit button that opens the customizations
818
848
  // dialog, and the primary action.
819
849
  <HStack gap={3} vAlign="center" hAlign="between">
820
- <Heading level={2}>{pageTitle}</Heading>
850
+ <Heading level={1} type="display-2">{pageTitle}</Heading>
821
851
  <HStack gap={2} vAlign="center">
822
852
  <Button
823
853
  label="Edit"
@@ -837,7 +867,7 @@ export default function PageEditor() {
837
867
  isLabelHidden
838
868
  value={pageTitle}
839
869
  onChange={setPageTitle}
840
- onKeyDown={(e: React.KeyboardEvent) => {
870
+ onKeyDown={(e: KeyboardEvent) => {
841
871
  if (e.key === 'Enter') {
842
872
  setIsEditingTitle(false);
843
873
  }
@@ -846,7 +876,7 @@ export default function PageEditor() {
846
876
  onBlur={() => setIsEditingTitle(false)}
847
877
  />
848
878
  ) : (
849
- <Heading level={2}>{pageTitle}</Heading>
879
+ <Heading level={1} type="display-2">{pageTitle}</Heading>
850
880
  )}
851
881
 
852
882
  <Toolbar
@@ -920,7 +950,7 @@ export default function PageEditor() {
920
950
  <EmptyState
921
951
  title="No blocks yet"
922
952
  description="Add blocks from the sidebar to start building your page"
923
- icon={<Icon icon={CirclePlus} />}
953
+ icon={<Icon icon={CirclePlus} color="secondary" />}
924
954
  />
925
955
  )}
926
956
  </VStack>