@astryxdesign/cli 0.4.7 → 0.5.0-canary.009bcb3

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 (105) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +53 -51
  3. package/api/docs/docs.doc.mjs +2 -2
  4. package/api/index.d.mts +1 -1
  5. package/api/index.mjs +1 -1
  6. package/api/search/search.mjs +50 -4
  7. package/api/search/search.test.mjs +71 -0
  8. package/api/template/data-token-fallbacks.test.mjs +85 -0
  9. package/api/template/template.doc.mjs +2 -2
  10. package/api/theme/build/build.mjs +43 -44
  11. package/api/theme/targets/targets.d.mts +18 -0
  12. package/api/theme/targets/targets.mjs +87 -0
  13. package/api/theme/targets/targets.test.mjs +66 -0
  14. package/api/theme/theme.d.mts +1 -0
  15. package/api/theme/theme.mjs +3 -1
  16. package/api/theme/theme.type.d.mts +23 -0
  17. package/api/theme/theme.type.mjs +21 -1
  18. package/api/theme/themeTargets.doc.d.mts +11 -0
  19. package/api/theme/themeTargets.doc.mjs +58 -0
  20. package/api/theme/themeTemplate.doc.mjs +3 -3
  21. package/assets/codemods/__tests__/registry.test.mjs +1 -0
  22. package/assets/codemods/registry.mjs +1 -0
  23. package/assets/codemods/transforms/v0.5.0/__tests__/next-codemods.test.mjs +127 -0
  24. package/assets/codemods/transforms/v0.5.0/banner-collapsible-content.mjs +171 -0
  25. package/assets/codemods/transforms/v0.5.0/index.mjs +20 -0
  26. package/assets/docs/README.md +50 -0
  27. package/assets/docs/cli-integrations.doc.mjs +4 -4
  28. package/assets/docs/theme.doc.dense.mjs +1 -1
  29. package/assets/docs/theme.doc.mjs +1 -1
  30. package/assets/docs/typography.doc.mjs +2 -2
  31. package/assets/templates/blocks/components/Banner/BannerCollapsibleContent.doc.mjs +1 -1
  32. package/assets/templates/blocks/components/Banner/BannerCollapsibleContent.tsx +1 -1
  33. package/assets/templates/blocks/components/DateInput/DateInputClearable.tsx +5 -1
  34. package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +6 -1
  35. package/assets/templates/blocks/components/DateInput/DateInputFormats.tsx +5 -1
  36. package/assets/templates/blocks/components/DateInput/DateInputShowcase.tsx +4 -1
  37. package/assets/templates/blocks/components/DateInput/DateInputWithDescription.tsx +5 -1
  38. package/assets/templates/blocks/components/DateInput/DateInputWithValidation.tsx +5 -1
  39. package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.doc.mjs +25 -0
  40. package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.tsx +167 -0
  41. package/assets/templates/blocks/components/Dialog/DialogScrollingContent.tsx +1 -1
  42. package/assets/templates/blocks/components/HoverCard/HoverCardHookUsage.tsx +4 -1
  43. package/assets/templates/blocks/components/Step/StepContent.doc.mjs +14 -0
  44. package/assets/templates/blocks/components/Step/StepContent.tsx +32 -0
  45. package/assets/templates/blocks/components/Step/StepIndicator.doc.mjs +14 -0
  46. package/assets/templates/blocks/components/Step/StepIndicator.tsx +60 -0
  47. package/assets/templates/blocks/components/Step/StepShowcase.doc.mjs +15 -0
  48. package/assets/templates/blocks/components/Step/StepShowcase.tsx +26 -0
  49. package/assets/templates/blocks/components/Step/StepStates.doc.mjs +14 -0
  50. package/assets/templates/blocks/components/Step/StepStates.tsx +46 -0
  51. package/assets/templates/blocks/components/Stepper/StepperCustomContent.doc.mjs +22 -0
  52. package/assets/templates/blocks/components/Stepper/StepperCustomContent.tsx +126 -0
  53. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +1 -1
  54. package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +17 -5
  55. package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.doc.mjs +14 -0
  56. package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.tsx +25 -0
  57. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +2 -2
  58. package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +1 -1
  59. package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +1 -1
  60. package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +6 -7
  61. package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +1 -1
  62. package/assets/templates/pages/dashboard-cohort-funnel/page.tsx +1 -1
  63. package/assets/templates/pages/dashboard-data/page.tsx +1 -1
  64. package/assets/templates/pages/dashboard-portfolio/page.tsx +5 -5
  65. package/assets/templates/pages/dashboard-service-monitoring/page.tsx +1 -1
  66. package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
  67. package/assets/templates/pages/settings-dialog/page.tsx +2543 -757
  68. package/assets/templates/pages/settings-dialog/template.doc.mjs +1 -2
  69. package/assets/templates/pages/table-filter/page.tsx +4093 -0
  70. package/assets/templates/pages/table-filter/template.doc.mjs +12 -0
  71. package/assets/templates/pages/table-grouped/page.tsx +151 -144
  72. package/assets/templates/pages/theme-showcase/page.tsx +36 -17
  73. package/assets/templates/themes/chocolate/chocolateTheme.ts +3 -1
  74. package/assets/templates/themes/matcha/matchaTheme.ts +3 -1
  75. package/assets/templates/themes/neutral/neutralTheme.ts +16 -9
  76. package/assets/templates/themes/stone/stoneTheme.ts +3 -1
  77. package/clients/cli/commands/build-theme.color-scheme.test.mjs +7 -6
  78. package/clients/cli/commands/build-theme.data-tokens.test.mjs +144 -0
  79. package/clients/cli/commands/build-theme.mjs +85 -0
  80. package/clients/cli/commands/build-theme.variants.test.mjs +3 -0
  81. package/clients/cli/commands/dialog-adaptive-template.test.mjs +24 -0
  82. package/clients/cli/commands/theme-targets.behavior.test.mjs +64 -0
  83. package/clients/cli/commands/theme-targets.doc.mjs +38 -0
  84. package/clients/cli/commands/theme-template.doc.mjs +2 -2
  85. package/clients/cli/commands/theme.doc.mjs +4 -2
  86. package/clients/cli/index.mjs +1 -0
  87. package/clients/cli/lib/component-format.mjs +2 -2
  88. package/clients/cli/lib/manifest.mjs +2 -0
  89. package/foundation/discovery/component-discovery.mjs +10 -9
  90. package/foundation/discovery/hook-discovery.mjs +2 -1
  91. package/foundation/discovery/hook-discovery.test.mjs +156 -37
  92. package/foundation/discovery/theming-targets.d.mts +86 -0
  93. package/foundation/discovery/theming-targets.mjs +202 -0
  94. package/foundation/discovery/theming-targets.test.mjs +245 -0
  95. package/foundation/fs/paths.d.mts +16 -0
  96. package/foundation/fs/paths.mjs +36 -0
  97. package/foundation/fs/paths.test.mjs +29 -1
  98. package/foundation/response/response-types.doc.mjs +7 -2
  99. package/package.json +9 -9
  100. package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +0 -14
  101. package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +0 -24
  102. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +0 -14
  103. package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +0 -92
  104. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +0 -14
  105. package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +0 -40
@@ -0,0 +1,12 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
4
+ export const doc = {
5
+ type: 'page',
6
+ name: 'Filterable Table',
7
+ displayName: 'Filterable Table',
8
+ description:
9
+ 'Filterable data table with a token filter bar that swaps to PowerSearch, saved views, view options for columns and grouping, and a resizable detail panel.',
10
+ isReady: true,
11
+ category: 'Table - Filtering',
12
+ };
@@ -37,6 +37,7 @@ import {Divider} from '@astryxdesign/core/Divider';
37
37
  import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
38
38
  import {
39
39
  Table,
40
+ TableBody,
40
41
  TableRow,
41
42
  TableCell,
42
43
  proportional,
@@ -963,161 +964,167 @@ export default function DataTableTemplate() {
963
964
  />
964
965
  ))}
965
966
  </colgroup>
966
- {groupKeys.map(key => {
967
- const tasks = grouped.get(key);
968
- if (!tasks || tasks.length === 0) {
969
- return null;
970
- }
971
- const isExpanded = expandedGroups.has(key);
967
+ <TableBody>
968
+ {groupKeys.map(key => {
969
+ const tasks = grouped.get(key);
970
+ if (!tasks || tasks.length === 0) {
971
+ return null;
972
+ }
973
+ const isExpanded = expandedGroups.has(key);
972
974
 
973
- return (
974
- <React.Fragment key={key}>
975
- {groupBy !== 'none' && (
976
- <TableRow
977
- role="button"
978
- tabIndex={0}
979
- onClick={() => toggleGroup(key)}
980
- onKeyDown={e => {
981
- if (e.key === 'Enter' || e.key === ' ') {
982
- e.preventDefault();
983
- toggleGroup(key);
984
- }
985
- }}>
986
- <TableCell colSpan={COL_COUNT} style={groupHeaderCell}>
987
- <HStack gap={2} vAlign="center">
988
- <Icon
989
- icon={
990
- isExpanded ? ChevronDownIcon : ChevronRightIcon
991
- }
992
- size="sm"
993
- color="secondary"
994
- />
995
- <Text type="body" weight="bold">
996
- {getGroupLabel(groupBy, key)}
997
- </Text>
998
- <Badge
999
- variant="neutral"
1000
- label={String(tasks.length)}
1001
- />
1002
- </HStack>
1003
- </TableCell>
1004
- </TableRow>
1005
- )}
1006
- {(groupBy === 'none' || isExpanded) &&
1007
- tasks.map(task => (
975
+ return (
976
+ <React.Fragment key={key}>
977
+ {groupBy !== 'none' && (
1008
978
  <TableRow
1009
- key={task.id}
1010
- onClick={() => setSelectedTask(task)}>
1011
- <TableCell>
1012
- <Center axis="horizontal">
1013
- <StatusDot
1014
- variant={STATUS_DOT_VARIANT[task.status]}
1015
- label={STATUS_LABEL[task.status]}
1016
- />
1017
- </Center>
1018
- </TableCell>
1019
- <TableCell>
1020
- <HStack gap={3} vAlign="center">
979
+ role="button"
980
+ tabIndex={0}
981
+ onClick={() => toggleGroup(key)}
982
+ onKeyDown={e => {
983
+ if (e.key === 'Enter' || e.key === ' ') {
984
+ e.preventDefault();
985
+ toggleGroup(key);
986
+ }
987
+ }}>
988
+ <TableCell
989
+ colSpan={COL_COUNT}
990
+ style={groupHeaderCell}>
991
+ <HStack gap={2} vAlign="center">
1021
992
  <Icon
1022
- icon={ChartBarIcon}
993
+ icon={
994
+ isExpanded
995
+ ? ChevronDownIcon
996
+ : ChevronRightIcon
997
+ }
1023
998
  size="sm"
1024
- color={PRIORITY_COLOR[task.priority]}
999
+ color="secondary"
1025
1000
  />
1026
- <Text type="supporting" color="secondary">
1027
- {task.taskId}
1001
+ <Text type="body" weight="bold">
1002
+ {getGroupLabel(groupBy, key)}
1028
1003
  </Text>
1029
- <Text type="body" maxLines={1}>
1030
- {task.title}
1031
- </Text>
1032
- {task.subtitle && (
1033
- <Text
1034
- type="body"
1035
- color="secondary"
1036
- maxLines={1}>
1037
- › {task.subtitle}
1038
- </Text>
1039
- )}
1004
+ <Badge
1005
+ variant="neutral"
1006
+ label={String(tasks.length)}
1007
+ />
1040
1008
  </HStack>
1041
1009
  </TableCell>
1042
- <TableCell>
1043
- {task.project ? (
1044
- <Text type="body" maxLines={1}>
1045
- {task.project}
1010
+ </TableRow>
1011
+ )}
1012
+ {(groupBy === 'none' || isExpanded) &&
1013
+ tasks.map(task => (
1014
+ <TableRow
1015
+ key={task.id}
1016
+ onClick={() => setSelectedTask(task)}>
1017
+ <TableCell>
1018
+ <Center axis="horizontal">
1019
+ <StatusDot
1020
+ variant={STATUS_DOT_VARIANT[task.status]}
1021
+ label={STATUS_LABEL[task.status]}
1022
+ />
1023
+ </Center>
1024
+ </TableCell>
1025
+ <TableCell>
1026
+ <HStack gap={3} vAlign="center">
1027
+ <Icon
1028
+ icon={ChartBarIcon}
1029
+ size="sm"
1030
+ color={PRIORITY_COLOR[task.priority]}
1031
+ />
1032
+ <Text type="supporting" color="secondary">
1033
+ {task.taskId}
1034
+ </Text>
1035
+ <Text type="body" maxLines={1}>
1036
+ {task.title}
1037
+ </Text>
1038
+ {task.subtitle && (
1039
+ <Text
1040
+ type="body"
1041
+ color="secondary"
1042
+ maxLines={1}>
1043
+ › {task.subtitle}
1044
+ </Text>
1045
+ )}
1046
+ </HStack>
1047
+ </TableCell>
1048
+ <TableCell>
1049
+ {task.project ? (
1050
+ <Text type="body" maxLines={1}>
1051
+ {task.project}
1052
+ </Text>
1053
+ ) : (
1054
+ <Text type="supporting" color="secondary">
1055
+
1056
+ </Text>
1057
+ )}
1058
+ </TableCell>
1059
+ <TableCell>
1060
+ <Text type="supporting" color="secondary">
1061
+ {task.created}
1046
1062
  </Text>
1047
- ) : (
1063
+ </TableCell>
1064
+ <TableCell>
1048
1065
  <Text type="supporting" color="secondary">
1049
-
1066
+ {task.updated}
1050
1067
  </Text>
1051
- )}
1052
- </TableCell>
1053
- <TableCell>
1054
- <Text type="supporting" color="secondary">
1055
- {task.created}
1056
- </Text>
1057
- </TableCell>
1058
- <TableCell>
1059
- <Text type="supporting" color="secondary">
1060
- {task.updated}
1061
- </Text>
1062
- </TableCell>
1063
- <TableCell>
1064
- <Avatar name={task.assignee} size="sm" />
1065
- </TableCell>
1066
- <TableCell>
1067
- <DropdownMenu
1068
- button={{
1069
- label: 'Actions',
1070
- variant: 'ghost',
1071
- size: 'sm',
1072
- icon: (
1073
- <Icon
1074
- icon={EllipsisHorizontalIcon}
1075
- size="sm"
1076
- />
1077
- ),
1078
- isIconOnly: true,
1079
- }}
1080
- hasChevron={false}
1081
- items={[
1082
- {
1083
- label: 'Edit issue',
1084
- icon: PencilIcon,
1085
- onClick: () => {},
1086
- },
1087
- {
1088
- label: 'Assign to...',
1089
- icon: UserIcon,
1090
- onClick: () => {},
1091
- },
1092
- {
1093
- label: 'Add label',
1094
- icon: TagIcon,
1095
- onClick: () => {},
1096
- },
1097
- {
1098
- label: 'Duplicate',
1099
- icon: DocumentDuplicateIcon,
1100
- onClick: () => {},
1101
- },
1102
- {
1103
- label: 'Move to project',
1104
- icon: ArrowRightIcon,
1105
- onClick: () => {},
1106
- },
1107
- {type: 'divider' as const},
1108
- {
1109
- label: 'Delete issue',
1110
- icon: TrashIcon,
1111
- onClick: () => {},
1112
- },
1113
- ]}
1114
- />
1115
- </TableCell>
1116
- </TableRow>
1117
- ))}
1118
- </React.Fragment>
1119
- );
1120
- })}
1068
+ </TableCell>
1069
+ <TableCell>
1070
+ <Avatar name={task.assignee} size="sm" />
1071
+ </TableCell>
1072
+ <TableCell>
1073
+ <DropdownMenu
1074
+ button={{
1075
+ label: 'Actions',
1076
+ variant: 'ghost',
1077
+ size: 'sm',
1078
+ icon: (
1079
+ <Icon
1080
+ icon={EllipsisHorizontalIcon}
1081
+ size="sm"
1082
+ />
1083
+ ),
1084
+ isIconOnly: true,
1085
+ }}
1086
+ hasChevron={false}
1087
+ items={[
1088
+ {
1089
+ label: 'Edit issue',
1090
+ icon: PencilIcon,
1091
+ onClick: () => {},
1092
+ },
1093
+ {
1094
+ label: 'Assign to...',
1095
+ icon: UserIcon,
1096
+ onClick: () => {},
1097
+ },
1098
+ {
1099
+ label: 'Add label',
1100
+ icon: TagIcon,
1101
+ onClick: () => {},
1102
+ },
1103
+ {
1104
+ label: 'Duplicate',
1105
+ icon: DocumentDuplicateIcon,
1106
+ onClick: () => {},
1107
+ },
1108
+ {
1109
+ label: 'Move to project',
1110
+ icon: ArrowRightIcon,
1111
+ onClick: () => {},
1112
+ },
1113
+ {type: 'divider' as const},
1114
+ {
1115
+ label: 'Delete issue',
1116
+ icon: TrashIcon,
1117
+ onClick: () => {},
1118
+ },
1119
+ ]}
1120
+ />
1121
+ </TableCell>
1122
+ </TableRow>
1123
+ ))}
1124
+ </React.Fragment>
1125
+ );
1126
+ })}
1127
+ </TableBody>
1121
1128
  </Table>
1122
1129
  </LayoutContent>
1123
1130
  }
@@ -153,14 +153,16 @@ const styles: Record<string, CSSProperties> = {
153
153
  flex: 1,
154
154
  },
155
155
  quantityInput: {
156
- // minWidth (not a hard width) so the field grows to fit the digit + the
157
- // theme's input padding. A fixed 40px was too tight on themes with larger
158
- // padding / bigger type scale (e.g. Matcha, Y2K), clipping the value.
159
- minWidth: 64,
160
156
  flexShrink: 0,
161
157
  },
158
+ // `auto` basis so the button wraps to its own line rather than ellipsizing.
162
159
  cartButton: {
163
- flex: 1,
160
+ flex: '1 1 auto',
161
+ minWidth: 0,
162
+ },
163
+ cartRow: {
164
+ width: '100%',
165
+ minWidth: 0,
164
166
  },
165
167
  };
166
168
 
@@ -236,6 +238,18 @@ const inlineStyles: Record<string, CSSProperties> = {
236
238
  display: 'flex',
237
239
  flexDirection: 'column',
238
240
  },
241
+ // Flex bases, not grid tracks: the checkout form needs a width floor, and the
242
+ // chat wraps to its own row rather than squeezing it under one.
243
+ checkoutColumn: {
244
+ flex: '1 1 300px',
245
+ minWidth: 0,
246
+ display: 'grid',
247
+ },
248
+ chatColumn: {
249
+ flex: '2 1 520px',
250
+ minWidth: 0,
251
+ display: 'grid',
252
+ },
239
253
  };
240
254
 
241
255
  const PRODUCT_IMAGE_KEYS = ['watch', 'headphones', 'backpack'];
@@ -284,8 +298,7 @@ const DEFAULT_PRODUCTS: ProductSpec[] = [
284
298
  // scaffolded template renders real imagery without needing local public assets.
285
299
  const DEFAULT_IMAGES: Record<string, string> = {
286
300
  watch: '/template-assets/Neutral-Watch.png',
287
- headphones:
288
- '/template-assets/Neutral-Headphones.png',
301
+ headphones: '/template-assets/Neutral-Headphones.png',
289
302
  backpack: '/template-assets/Neutral-Backpack.png',
290
303
  wallet: '/template-assets/Neutral-Wallet.png',
291
304
  tumbler: '/template-assets/Neutral-Tumbler.png',
@@ -350,14 +363,14 @@ function CardShowcase({
350
363
 
351
364
  return (
352
365
  <VStack gap={8}>
353
- <Grid columns={columns} gap={4}>
354
- <GridSpan columns={1}>
366
+ <HStack gap={4} wrap="wrap">
367
+ <div style={inlineStyles.checkoutColumn}>
355
368
  <CheckoutCard isMobile={isMobile} />
356
- </GridSpan>
357
- <GridSpan columns={isMobile ? 1 : 2}>
369
+ </div>
370
+ <div style={inlineStyles.chatColumn}>
358
371
  <ChatCard />
359
- </GridSpan>
360
- </Grid>
372
+ </div>
373
+ </HStack>
361
374
  <Grid columns={columns} gap={4}>
362
375
  <GridSpan columns={isMobile ? 1 : 3}>
363
376
  <InventoryCard images={images} inventory={inventory} />
@@ -472,7 +485,12 @@ function StorePreview({
472
485
  }}>
473
486
  {p.description}
474
487
  </Text>
475
- <HStack gap={2} vAlign="center" hAlign="center">
488
+ <HStack
489
+ gap={2}
490
+ vAlign="center"
491
+ hAlign="center"
492
+ wrap="wrap"
493
+ style={styles.cartRow}>
476
494
  <NumberInput
477
495
  label="Quantity"
478
496
  isLabelHidden
@@ -481,6 +499,7 @@ function StorePreview({
481
499
  min={1}
482
500
  max={99}
483
501
  size="sm"
502
+ width={72}
484
503
  style={styles.quantityInput}
485
504
  />
486
505
  <Button
@@ -506,7 +525,7 @@ function StorePreview({
506
525
 
507
526
  function CheckoutCard({isMobile}: {isMobile: boolean}) {
508
527
  return (
509
- <Card padding={5} style={styles.card}>
528
+ <Card padding={isMobile ? 4 : 5} style={styles.card}>
510
529
  <VStack gap={4} style={styles.checkoutStack}>
511
530
  <Heading level={2}>Checkout</Heading>
512
531
 
@@ -560,7 +579,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
560
579
  <Text type="supporting" weight="bold">
561
580
  Payment method
562
581
  </Text>
563
- <Grid columns={isMobile ? 1 : {minWidth: 70, max: 3}} gap={2}>
582
+ <Grid columns={isMobile ? 1 : {minWidth: 80, max: 3}} gap={2}>
564
583
  <SelectableCard
565
584
  label="Pay with card"
566
585
  isSelected={true}
@@ -614,7 +633,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
614
633
  placeholder="1234 1234 1234 1234"
615
634
  value=""
616
635
  onChange={() => {}}
617
- startIcon={<CreditCard size={16} />}
636
+ startIcon={isMobile ? undefined : <CreditCard size={16} />}
618
637
  size="lg"
619
638
  />
620
639
 
@@ -27,7 +27,9 @@ const chocolateSyntax = defineSyntaxTheme({
27
27
  tag: ['#8c3a3a', '#d47a7a'],
28
28
  attribute: ['#8C5927', '#d4a06a'],
29
29
  property: ['#3a7c6b', '#70c4b0'],
30
- punctuation: ['#B88859', '#6b5540'],
30
+ // #B88859/#6b5540 failed WCAG AA against the syntax background: 3.06:1
31
+ // light, 2.56:1 dark. #5386.
32
+ punctuation: ['#9e622e', '#cb884d'], // 4.84:1 / 6.12:1
31
33
  background: ['#FFFCF7', '#1c1610'],
32
34
  },
33
35
  });
@@ -27,7 +27,9 @@ const matchaSyntax = defineSyntaxTheme({
27
27
  tag: ['#8c3a3a', '#d47a7a'],
28
28
  attribute: ['#7c5e3a', '#c4a882'],
29
29
  property: ['#3a7c6b', '#70c4b0'],
30
- punctuation: ['#707E46', '#5a6440'],
30
+ // #707E46/#5a6440 failed WCAG AA against the syntax background: 3.83:1
31
+ // light, 2.73:1 dark. #5386.
32
+ punctuation: ['#566a39', '#92af6a'], // 5.19:1 / 7.02:1
31
33
  background: ['#F0F0E0', '#1a1c14'],
32
34
  },
33
35
  });
@@ -24,7 +24,8 @@
24
24
  * icon = T30 / T80
25
25
  * text = T30 / T80
26
26
  *
27
- * All 9 saturated badge values pass WCAG AA (5.6–9.6 contrast range).
27
+ * All 9 saturated badge values pass WCAG AA against their label (>= 4.5:1);
28
+ * `scripts/check-badge-contrast.test.mjs` holds every theme to that.
28
29
  *
29
30
  * Only overrides tokens that differ from the defaults.
30
31
  */
@@ -51,7 +52,9 @@ const neutralSyntax = defineSyntaxTheme({
51
52
  tag: ['#89001a', '#ffaeaa'], // red
52
53
  attribute: ['#584400', '#eec12f'], // yellow
53
54
  property: ['#005348', '#83dac9'], // teal
54
- punctuation: ['#a3a3a3', '#525252'], // neutral
55
+ // #a3a3a3/#525252 (this pair's own disabled-text tone) failed WCAG AA
56
+ // against the syntax background: 2.42:1 light, 2.53:1 dark. #5386.
57
+ punctuation: ['#6e6e6e', '#a0a0a0'], // neutral, 4.89:1 / 7.57:1
55
58
  background: ['#fafafa', '#0a0a0a'],
56
59
  },
57
60
  });
@@ -432,10 +435,14 @@ export const neutralTheme = defineTheme({
432
435
  color: '#171717',
433
436
  },
434
437
  'variant:error': {
435
- // Light: T55 #e33f4a (palette saturated stop)
438
+ // Light: T58 #c9303a. The T55 stop #e33f4a pairs with white at only
439
+ // 4.14:1 — the label is 12px/500, so AA wants 4.5, not the 3:1
440
+ // large-text allowance. One tonal step down holds the hue
441
+ // (OKLCH H 21.9 -> 22.8, C 0.200 -> 0.189) and reaches 5.29:1.
436
442
  // Dark : T60 stop from dark-mode tonal palette of Tailwind red-600
437
- // source #dc2626 (kept on H=27 alarm-red rather than coral)
438
- backgroundColor: 'light-dark(#e33f4a, #ff705d)',
443
+ // source #dc2626 (kept on H=27 alarm-red rather than coral).
444
+ // Dark text on it is 6.60:1 and unchanged.
445
+ backgroundColor: 'light-dark(#c9303a, #ff705d)',
439
446
  color: 'light-dark(#ffffff, #171717)',
440
447
  },
441
448
 
@@ -500,7 +507,7 @@ export const neutralTheme = defineTheme({
500
507
  //
501
508
  // success → badge success bg (green T45 / dark-ramp T60)
502
509
  // warning → badge warning bg (yellow T85, same hex both modes)
503
- // error → badge error bg (red T55 / dark-ramp T60)
510
+ // error → badge error bg (red T58 / dark-ramp T60)
504
511
  // accent → badge info bg (blue T50 / dark-ramp T60) — the
505
512
  // StatusDot "accent" is the info/attention color, so it
506
513
  // pairs with the info badge rather than --color-accent
@@ -515,7 +522,7 @@ export const neutralTheme = defineTheme({
515
522
  statusdot: {
516
523
  'variant:success': {backgroundColor: 'light-dark(#198100, #64af4c)'},
517
524
  'variant:warning': {backgroundColor: '#ffce2f'},
518
- 'variant:error': {backgroundColor: 'light-dark(#e33f4a, #ff705d)'},
525
+ 'variant:error': {backgroundColor: 'light-dark(#c9303a, #ff705d)'},
519
526
  'variant:accent': {backgroundColor: 'light-dark(#0074e2, #6d9cfe)'},
520
527
  },
521
528
 
@@ -611,8 +618,8 @@ export const neutralTheme = defineTheme({
611
618
  '--color-warning': '#ffce2f',
612
619
  },
613
620
  'variant:error': {
614
- // Red T55 saturated stop (= variant:error badge bg)
615
- '--color-error': '#e33f4a',
621
+ // Red T58 saturated stop (= variant:error badge bg)
622
+ '--color-error': '#c9303a',
616
623
  },
617
624
  },
618
625
 
@@ -105,7 +105,9 @@ export const stoneTheme = defineTheme({
105
105
 
106
106
  // Text — H=291
107
107
  '--color-text-primary': ['#25252a', '#f3f3f5'], // light: Stone Neutral T15
108
- '--color-text-secondary': ['#83838a', '#9d9da3'], // T55 C=4 / T65 C=3
108
+ // T40/T70 keeps normal secondary text above AA on every stone surface,
109
+ // including the muted and tinted fills that components pair it with.
110
+ '--color-text-secondary': ['#5e5e63', '#ababb0'], // Stone Neutral T40 / T70
109
111
  '--color-text-disabled': ['#d7d7da', '#5e5e61'], // T86 C=1.6 / T40 C=2
110
112
  '--color-text-accent': ['#25252a', '#f3f3f5'], // light: Stone Neutral T15
111
113
  '--color-on-dark': '#FFFFFF',
@@ -17,7 +17,9 @@
17
17
  * html[data-theme="dark"] { color-scheme: dark; }
18
18
  *
19
19
  * Themes that never use `light-dark()` have no `color-scheme` ambiguity to
20
- * resolve, so none of the three rules should be emitted for them.
20
+ * resolve, so none of the three rules should be emitted for them — the
21
+ * generated CSS still carries the `--color-data-*` defaults, which are
22
+ * `light-dark()` pairs, so the decision reads the theme's own values.
21
23
  *
22
24
  * Building `astryx theme build` requires a compiled @astryxdesign/core (there is no in-CLI
23
25
  * fallback generator), so this suite builds core once in beforeAll via the
@@ -67,10 +69,9 @@ describe('theme build color-scheme output', () => {
67
69
  const project = path.join(tmpDir, 'project');
68
70
  const themesDir = path.join(project, 'themes');
69
71
  // A raw light-dark() token value is what triggers the color-scheme
70
- // injection in build-theme.mjs (it checks the generated CSS for the
71
- // literal substring). `defineTheme.test.ts` already covers the separate
72
- // [light, dark] tuple -> light-dark() conversion; this fixture writes
73
- // the string directly so this test stays focused on the CLI's own
72
+ // injection in build-theme.mjs. `defineTheme.test.ts` already covers the
73
+ // separate [light, dark] tuple -> light-dark() conversion; this fixture
74
+ // writes the string directly so this test stays focused on the CLI's own
74
75
  // color-scheme decision, not on that conversion.
75
76
  const themeFile = writeTheme(themesDir, 'with-light-dark', {
76
77
  '--color-accent': 'light-dark(#0077B6, #48CAE4)',
@@ -123,7 +124,7 @@ describe('theme build color-scheme output', () => {
123
124
  expect(fs.existsSync(cssPath)).toBe(true);
124
125
  const css = fs.readFileSync(cssPath, 'utf-8');
125
126
 
126
- expect(css).not.toContain('light-dark(');
127
+ expect(css).toContain('--color-accent: #0077B6');
127
128
  expect(css).not.toContain(COLOR_SCHEME_ROOT_DECL);
128
129
  expect(css).not.toContain(COLOR_SCHEME_LIGHT_DECL);
129
130
  expect(css).not.toContain(COLOR_SCHEME_DARK_DECL);