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,74 +1,20 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LH[divider] > H[g2 a=center] > Hd"Midnight Kicks"[level=1] + IB"Filter"[variant=ghost] + IB"Export"[variant=ghost] + B.primary"New order") + (LC[p3] > V[g4] > (V[g3] > C[p=4] + Tx"Daily revenue"[t=supporting] + (H[g2 a=center] > Ic + Tx"Revenue"[t=supporting])) + (T[hover] > (TR > THC"Order" + THC"Product" + THC"Amount" + THC"Customer" + THC"Email" + THC"Status" + THC"Date") + (TR > TC"ORD-1001" + TC"Air Max 90" + TC"$130" + TC"Sarah Chen" + TC"sarah.chen@acme.co" + (TC > Tk.green"Completed") + TC"2025-01-12")*5))
2
4
 
3
- import type {CSSProperties} from 'react';
4
- import {
5
- VStack,
6
- HStack,
7
- StackItem,
8
- Layout,
9
- LayoutContent,
10
- LayoutHeader,
11
- } from '@astryxdesign/core/Layout';
12
- import {Text, Heading} from '@astryxdesign/core/Text';
13
- import {Button} from '@astryxdesign/core/Button';
14
- import {IconButton} from '@astryxdesign/core/IconButton';
15
- import {Icon} from '@astryxdesign/core/Icon';
16
- import {Badge} from '@astryxdesign/core/Badge';
17
- import {Card} from '@astryxdesign/core/Card';
18
- import {Link} from '@astryxdesign/core/Link';
19
- import {Table, proportional, pixel} from '@astryxdesign/core/Table';
20
- import type {TableColumn} from '@astryxdesign/core/Table';
21
- import {Filter, Download, Plus, Square} from 'lucide-react';
5
+ /**
6
+ * Midnight Kicks — sneaker order desk: a daily revenue line over the order log. Frame: page header (title + icon actions) | content column (c (Frame/responsive/container: see XLE header above.)
7
+ */
22
8
 
23
- // ============= ICONS (verified lucide-react exports) =============
24
- // Filter ← FunnelIcon, Download ← ArrowDownTrayIcon, Plus ← PlusIcon.
25
- // Square marks the chart legend swatch.
26
-
27
- const swatchStyle: CSSProperties = {flexShrink: 0};
28
-
29
- // Rounded product swatch: Dracula surface with a per-product accent glyph.
30
- function ProductSwatch({accent, label}: {accent: string; label: string}) {
31
- return (
32
- <svg
33
- viewBox="0 0 36 36"
34
- width={36}
35
- height={36}
36
- style={swatchStyle}
37
- role="img"
38
- aria-label={`${label} swatch`}>
39
- <rect width={36} height={36} rx={5} fill="var(--dracula-bg-light)" />
40
- <rect
41
- x={1}
42
- y={1}
43
- width={34}
44
- height={34}
45
- rx={4}
46
- fill="none"
47
- stroke="var(--color-widget-content-border)"
48
- />
49
- <circle cx={18} cy={18} r={7} fill="none" stroke={accent} strokeWidth={3} />
50
- </svg>
51
- );
52
- }
9
+ import {OrderDesk, type OrderDeskRow} from 'astryx-dracula/shared/order-desk';
10
+ import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
53
11
 
54
12
  // ============= DATA =============
55
13
 
56
14
  type ProductCategory =
57
15
  'Running' | 'Lifestyle' | 'Basketball' | 'Training' | 'Skateboarding';
58
16
 
59
- interface OrderRow extends Record<string, unknown> {
60
- id: string;
61
- customer: string;
62
- email: string;
63
- product: string;
64
- category: ProductCategory;
65
- imageIndex: number;
66
- amount: number;
67
- status: 'completed' | 'processing' | 'shipped' | 'refunded';
68
- date: string;
69
- }
70
-
71
- const PRODUCTS = [
17
+ const PRODUCTS: Array<{name: string; category: string; accent: SceneHue; price: number}> = [
72
18
  {
73
19
  name: 'Air Max 90',
74
20
  category: 'Lifestyle' as ProductCategory,
@@ -90,7 +36,7 @@ const PRODUCTS = [
90
36
  {
91
37
  name: 'Jordan 1 Retro',
92
38
  category: 'Basketball' as ProductCategory,
93
- accent: 'var(--dracula-purple)',
39
+ accent: 'var(--dracula-red)',
94
40
  price: 180,
95
41
  },
96
42
  {
@@ -107,7 +53,7 @@ const PRODUCTS = [
107
53
  },
108
54
  ];
109
55
 
110
- const orders: OrderRow[] = [
56
+ const orders: OrderDeskRow[] = [
111
57
  // Sun (0) — spread across 9am–5pm, very hot
112
58
  {
113
59
  id: 'ORD-1001',
@@ -806,233 +752,18 @@ const revenueData = [
806
752
  {date: 'Jan 15', revenue: 7100},
807
753
  ];
808
754
 
809
- const statusColor: Record<string, 'green' | 'blue' | 'orange' | 'red'> = {
810
- completed: 'green',
811
- shipped: 'blue',
812
- processing: 'orange',
813
- refunded: 'red',
814
- };
815
-
816
- const columns: TableColumn<OrderRow>[] = [
817
- {
818
- key: 'id',
819
- header: 'Order',
820
- width: pixel(110),
821
- renderCell: (item: OrderRow) => (
822
- <Link href="#/templates/table-page-shoe-store-heatmap" isStandalone>
823
- {item.id}
824
- </Link>
825
- ),
826
- },
827
- {
828
- key: 'product',
829
- header: 'Product',
830
- width: proportional(3),
831
- renderCell: (item: OrderRow) => (
832
- <HStack gap={3} vAlign="center">
833
- <ProductSwatch
834
- accent={PRODUCTS[item.imageIndex].accent}
835
- label={item.product}
836
- />
837
- <VStack gap={0}>
838
- <Text type="body">{item.product}</Text>
839
- <Text type="supporting" color="secondary">
840
- {item.category}
841
- </Text>
842
- </VStack>
843
- </HStack>
844
- ),
845
- },
846
- {
847
- key: 'amount',
848
- header: 'Amount',
849
- width: pixel(90),
850
- renderCell: (item: OrderRow) => (
851
- <Text type="body" hasTabularNumbers>
852
- ${item.amount}
853
- </Text>
854
- ),
855
- },
856
- {
857
- key: 'customer',
858
- header: 'Customer',
859
- width: proportional(2),
860
- renderCell: (item: OrderRow) => <Text type="body">{item.customer}</Text>,
861
- },
862
- {
863
- key: 'email',
864
- header: 'Email',
865
- width: proportional(2),
866
- renderCell: (item: OrderRow) => <Text type="body">{item.email}</Text>,
867
- },
868
- {
869
- key: 'status',
870
- header: 'Status',
871
- width: pixel(120),
872
- renderCell: (item: OrderRow) => (
873
- <Badge
874
- label={item.status.charAt(0).toUpperCase() + item.status.slice(1)}
875
- variant={statusColor[item.status]}
876
- />
877
- ),
878
- },
879
- {
880
- key: 'date',
881
- header: 'Date',
882
- width: pixel(110),
883
- renderCell: (item: OrderRow) => (
884
- <Text type="body" hasTabularNumbers>
885
- {item.date}
886
- </Text>
887
- ),
888
- },
889
- ];
890
-
891
- // ============= REVENUE CHART (hand SVG, Dracula ramp) =============
892
-
893
- const revenueLine = 'var(--dracula-cyan)';
894
-
895
- function RevenueChart() {
896
- const W = 540;
897
- const H = 200;
898
- const padLeft = 44;
899
- const padRight = 12;
900
- const padTop = 12;
901
- const baseline = 164;
902
- const max = 10000;
903
- const plotW = W - padLeft - padRight;
904
- const plotH = baseline - padTop;
905
- const points = revenueData.map((d, i) => ({
906
- ...d,
907
- x: padLeft + (i / (revenueData.length - 1)) * plotW,
908
- y: baseline - (d.revenue / max) * plotH,
909
- }));
910
- const linePath = points
911
- .map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`)
912
- .join(' ');
913
- const areaPath = `${linePath} L${points[points.length - 1].x.toFixed(1)},${baseline} L${points[0].x.toFixed(1)},${baseline} Z`;
914
- const gridTicks = [0, 2000, 4000, 6000, 8000, 10000];
915
- return (
916
- <VStack gap={3}>
917
- <Card
918
- padding={3}
919
- style={{
920
- backgroundColor: 'var(--color-background)',
921
- border: 'var(--border-width) solid var(--color-separator)',
922
- }}>
923
- <svg
924
- viewBox={`0 0 ${W} ${H}`}
925
- width="100%"
926
- role="img"
927
- aria-label="Daily revenue, January 1 to 15">
928
- {gridTicks.map(tick => {
929
- const y = baseline - (tick / max) * plotH;
930
- return (
931
- <g key={tick}>
932
- <line
933
- x1={padLeft}
934
- y1={y}
935
- x2={W - padRight}
936
- y2={y}
937
- stroke="var(--color-separator)"
938
- strokeDasharray={tick === 0 ? undefined : '3 3'}
939
- opacity={tick === 0 ? 1 : 0.5}
940
- />
941
- <text
942
- x={padLeft - 6}
943
- y={y + 3}
944
- textAnchor="end"
945
- fontSize={9}
946
- fill="var(--color-text-paragraph)"
947
- fontFamily="var(--font-family-mono)">
948
- {tick === 0 ? '$0' : `$${tick / 1000}k`}
949
- </text>
950
- </g>
951
- );
952
- })}
953
- <path d={areaPath} fill={revenueLine} opacity={0.25} />
954
- <path
955
- d={linePath}
956
- fill="none"
957
- stroke={revenueLine}
958
- strokeWidth={2}
959
- strokeLinejoin="round"
960
- strokeLinecap="round"
961
- />
962
- {points.map(
963
- (p, i) =>
964
- (i % 3 === 0 || i === points.length - 1) && (
965
- <text
966
- key={p.date}
967
- x={i === points.length - 1 ? W - padRight - 2 : p.x}
968
- y={H - 8}
969
- textAnchor={i === points.length - 1 ? 'end' : 'middle'}
970
- fontSize={9}
971
- fill="var(--color-text-paragraph)"
972
- fontFamily="var(--font-family-mono)">
973
- {p.date}
974
- </text>
975
- ),
976
- )}
977
- </svg>
978
- </Card>
979
- <Text type="supporting" color="secondary">
980
- Daily revenue · Jan 1–15
981
- </Text>
982
- <HStack gap={2} vAlign="center">
983
- <Icon icon={Square} size="xsm" style={{color: revenueLine}} />
984
- <Text type="supporting" color="secondary">
985
- Revenue
986
- </Text>
987
- </HStack>
988
- </VStack>
989
- );
990
- }
991
-
992
- // ============= PAGE =============
755
+ // ============= PAGE (parametric OrderDesk; catalogue + rows + scale only) =============
993
756
 
994
757
  export default function ShoeStoreTable() {
995
758
  return (
996
- <Layout
997
- height="fill"
998
- header={
999
- <LayoutHeader hasDivider>
1000
- <HStack gap={2} vAlign="center">
1001
- <StackItem size="fill">
1002
- <Heading level={1}>Midnight Kicks</Heading>
1003
- </StackItem>
1004
- <IconButton
1005
- label="Filter"
1006
- icon={<Icon icon={Filter} size="sm" />}
1007
- variant="ghost"
1008
- tooltip="Filter"
1009
- />
1010
- <IconButton
1011
- label="Export"
1012
- icon={<Icon icon={Download} size="sm" />}
1013
- variant="ghost"
1014
- tooltip="Export"
1015
- />
1016
- <Button label="New order" icon={<Icon icon={Plus} size="sm" />} />
1017
- </HStack>
1018
- </LayoutHeader>
1019
- }
1020
- content={
1021
- <LayoutContent padding={3}>
1022
- <VStack gap={4}>
1023
- <RevenueChart />
1024
-
1025
- <Table<OrderRow>
1026
- data={orders}
1027
- columns={columns}
1028
- idKey="id"
1029
- density="balanced"
1030
- dividers="rows"
1031
- hasHover
1032
- />
1033
- </VStack>
1034
- </LayoutContent>
1035
- }
759
+ <OrderDesk
760
+ title="Midnight Kicks"
761
+ selfHash="#/templates/table-page-shoe-store-heatmap"
762
+ products={PRODUCTS}
763
+ orders={orders}
764
+ revenueData={revenueData}
765
+ chartMax={10000}
766
+ gridTicks={[0, 2000, 4000, 6000, 8000, 10000]}
1036
767
  />
1037
768
  );
1038
769
  }
@@ -1,4 +1,10 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LH[divider] > H[g=2 a=center] > (SI[fill] > Hd"Familiars"[level=1]) + IB"Filter"[variant=ghost] + IB"Download"[variant=ghost] + B.primary"Add") + (LC[p=3] > V[g=4] > PS + (T[hover] > (TR > THC*3) + (TR > TC*3)*3))
4
+
5
+ /**
6
+ * Familiars — the full roster of the crypt's companions, with a PowerSearch field filter bar above one dense table. (Frame/responsive/container: see XLE header above.)
7
+ */
2
8
 
3
9
  import {useState, useMemo} from 'react';
4
10
  import {
@@ -41,7 +47,7 @@ const allFamiliars: FamiliarRow[] = [
41
47
  name: 'Vlad',
42
48
  kind: 'Vampire Bat',
43
49
  biography:
44
- 'I love brooding in the belfry and hate garlic bread — you might wonder how well those things mix and the answer is "not that well."',
50
+ 'I love brooding in the belfry and hate garlic bread, and you might wonder how well those things mix. The answer is "not that well."',
45
51
  age: 17,
46
52
  },
47
53
  {
@@ -121,7 +127,7 @@ const allFamiliars: FamiliarRow[] = [
121
127
  name: 'Bruno',
122
128
  kind: 'Gargoyle',
123
129
  biography:
124
- "Bruno is a gargoyle. He's a stony fellow — I never needed more than a day to teach him to scowl.",
130
+ "Bruno is a gargoyle. He's a stony fellow. I never needed more than a day to teach him to scowl.",
125
131
  age: 5,
126
132
  },
127
133
  {
@@ -398,22 +404,34 @@ export default function TablePage() {
398
404
  <Layout
399
405
  height="fill"
400
406
  header={
401
- <LayoutHeader hasDivider>
407
+ <LayoutHeader hasDivider padding={6}>
402
408
  <HStack gap={2} vAlign="center">
403
409
  <StackItem size="fill">
404
- <Heading level={1}>Familiars</Heading>
410
+ {/* maxLines={1} for symmetry with the other order-desk headers.
411
+ "Familiars" measures 189px at 35px and fits on one line today,
412
+ so this is a floor against a longer title rather than a fix
413
+ for a visible break — but the row has 129.2px of controls and
414
+ 24px of gaps against a 342px content box, so it has ~0px of
415
+ slack. Tooltip keeps the full value reachable (SC 1.4.10). */}
416
+ <Heading level={1} type="display-2" maxLines={1}>Familiars</Heading>
405
417
  </StackItem>
406
418
  <IconButton
407
419
  label="Filter"
408
420
  icon={<Icon icon={Filter} size="sm" />}
409
421
  variant="ghost"
422
+ tooltip="Filter"
410
423
  />
411
424
  <IconButton
412
425
  label="Download"
413
426
  icon={<Icon icon={Download} size="sm" />}
414
427
  variant="ghost"
428
+ tooltip="Export"
429
+ />
430
+ <Button
431
+ label="Add"
432
+ variant="primary"
433
+ icon={<Icon icon={Plus} size="sm" />}
415
434
  />
416
- <Button label="Add" icon={<Icon icon={Plus} size="sm" />} />
417
435
  </HStack>
418
436
  </LayoutHeader>
419
437
  }
@@ -426,7 +444,7 @@ export default function TablePage() {
426
444
  onChange={newFilters => {
427
445
  setFilters([...newFilters]);
428
446
  }}
429
- placeholder="Search familiars..."
447
+ placeholder="Search familiars…"
430
448
  resultCount={filtered.length}
431
449
  />
432
450
  <Table<FamiliarRow>
@@ -1,11 +1,12 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ // XLE (canonical structure, validated with `bunx astryx layout check`):
3
+ // L > (LH[divider] > H[a=center j=between] > Hd"Artifacts"[level=1] + B.primary"Add artifact") + (LC > T[hover] > (TR > THC"Name" + THC"Status" + THC"Updated" + THC"Actions") + (TR > TC"Blood Vial" + (TC > SD.success + Tx"Active") + TC"2025-01-15" + (TC > B.secondary"Edit"))*3)
2
4
 
3
- import {useState} from 'react';
4
5
  import {Layout, LayoutHeader, LayoutContent, HStack} from '@astryxdesign/core/Layout';
5
6
  import {Text, Heading} from '@astryxdesign/core/Text';
6
7
  import {Button} from '@astryxdesign/core/Button';
7
8
  import {Table} from '@astryxdesign/core/Table';
8
- import {Badge} from '@astryxdesign/core/Badge';
9
+ import {StatusDot} from '@astryxdesign/core/StatusDot';
9
10
  import type {TableColumn} from '@astryxdesign/core/Table';
10
11
 
11
12
  type Relic = {
@@ -15,6 +16,16 @@ type Relic = {
15
16
  updatedAt: string;
16
17
  };
17
18
 
19
+ const STATUS_LABEL: Record<Relic['status'], string> = {
20
+ active: 'Active',
21
+ inactive: 'Inactive',
22
+ };
23
+
24
+ const STATUS_VARIANT: Record<Relic['status'], 'success' | 'neutral'> = {
25
+ active: 'success',
26
+ inactive: 'neutral',
27
+ };
28
+
18
29
  const SAMPLE_DATA: Relic[] = [
19
30
  {id: '1', name: 'Blood Vial', status: 'active', updatedAt: '2025-01-15'},
20
31
  {id: '2', name: 'Garlic Charm', status: 'inactive', updatedAt: '2025-01-14'},
@@ -35,10 +46,15 @@ const columns: TableColumn<Relic>[] = [
35
46
  key: 'status',
36
47
  header: 'Status',
37
48
  renderCell: (item: Relic) => (
38
- <Badge
39
- variant={item.status === 'active' ? 'success' : 'neutral'}
40
- label={item.status.charAt(0).toUpperCase() + item.status.slice(1)}
41
- />
49
+ <HStack gap={1} vAlign="center">
50
+ <StatusDot
51
+ variant={STATUS_VARIANT[item.status]}
52
+ label={STATUS_LABEL[item.status]}
53
+ />
54
+ <Text type="body" color="secondary">
55
+ {STATUS_LABEL[item.status]}
56
+ </Text>
57
+ </HStack>
42
58
  ),
43
59
  },
44
60
  {
@@ -62,10 +78,11 @@ const columns: TableColumn<Relic>[] = [
62
78
  ];
63
79
 
64
80
  export default function SimpleTable() {
65
- const [data] = useState<Relic[]>(SAMPLE_DATA);
81
+ const data = SAMPLE_DATA;
66
82
 
67
83
  return (
68
84
  <Layout
85
+ height="auto"
69
86
  header={
70
87
  <LayoutHeader hasDivider>
71
88
  <HStack vAlign="center" hAlign="between">
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'Technical Report',
4
+ description: 'Chapter rail, paper walkthrough, and outline',
5
+ category: 'docs',
6
+ };