@astryxdesign/cli 0.4.7 → 0.5.0-canary.32a62fd
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/CHANGELOG.md +64 -0
- package/README.md +53 -51
- package/api/docs/docs.doc.mjs +2 -2
- package/api/index.d.mts +1 -1
- package/api/index.mjs +1 -1
- package/api/search/search.mjs +50 -4
- package/api/search/search.test.mjs +71 -0
- package/api/template/template.doc.mjs +2 -2
- package/api/theme/build/build.mjs +12 -42
- package/api/theme/targets/targets.d.mts +18 -0
- package/api/theme/targets/targets.mjs +87 -0
- package/api/theme/targets/targets.test.mjs +66 -0
- package/api/theme/theme.d.mts +1 -0
- package/api/theme/theme.mjs +3 -1
- package/api/theme/theme.type.d.mts +23 -0
- package/api/theme/theme.type.mjs +21 -1
- package/api/theme/themeTargets.doc.d.mts +11 -0
- package/api/theme/themeTargets.doc.mjs +58 -0
- package/api/theme/themeTemplate.doc.mjs +3 -3
- package/assets/codemods/__tests__/registry.test.mjs +1 -0
- package/assets/codemods/registry.mjs +1 -0
- package/assets/codemods/transforms/v0.5.0/__tests__/next-codemods.test.mjs +127 -0
- package/assets/codemods/transforms/v0.5.0/banner-collapsible-content.mjs +171 -0
- package/assets/codemods/transforms/v0.5.0/index.mjs +20 -0
- package/assets/docs/README.md +50 -0
- package/assets/docs/cli-integrations.doc.mjs +4 -4
- package/assets/docs/theme.doc.dense.mjs +1 -1
- package/assets/docs/theme.doc.mjs +1 -1
- package/assets/docs/typography.doc.mjs +2 -2
- package/assets/templates/blocks/components/Banner/BannerCollapsibleContent.doc.mjs +1 -1
- package/assets/templates/blocks/components/Banner/BannerCollapsibleContent.tsx +1 -1
- package/assets/templates/blocks/components/DateInput/DateInputClearable.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputDateRange.tsx +6 -1
- package/assets/templates/blocks/components/DateInput/DateInputFormats.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputShowcase.tsx +4 -1
- package/assets/templates/blocks/components/DateInput/DateInputWithDescription.tsx +5 -1
- package/assets/templates/blocks/components/DateInput/DateInputWithValidation.tsx +5 -1
- package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.doc.mjs +25 -0
- package/assets/templates/blocks/components/Dialog/DialogAdaptivePresentation.tsx +167 -0
- package/assets/templates/blocks/components/Dialog/DialogScrollingContent.tsx +1 -1
- package/assets/templates/blocks/components/HoverCard/HoverCardHookUsage.tsx +4 -1
- package/assets/templates/blocks/components/Step/StepContent.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepContent.tsx +32 -0
- package/assets/templates/blocks/components/Step/StepIndicator.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepIndicator.tsx +60 -0
- package/assets/templates/blocks/components/Step/StepShowcase.doc.mjs +15 -0
- package/assets/templates/blocks/components/Step/StepShowcase.tsx +26 -0
- package/assets/templates/blocks/components/Step/StepStates.doc.mjs +14 -0
- package/assets/templates/blocks/components/Step/StepStates.tsx +46 -0
- package/assets/templates/blocks/components/Stepper/StepperCustomContent.doc.mjs +22 -0
- package/assets/templates/blocks/components/Stepper/StepperCustomContent.tsx +126 -0
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.doc.mjs +1 -1
- package/assets/templates/blocks/components/Stepper/StepperIndicatorModes.tsx +17 -5
- package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.doc.mjs +14 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackHorizontal.tsx +25 -0
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.doc.mjs +2 -2
- package/assets/templates/blocks/components/Stepper/StepperOnTrackVertical.tsx +1 -1
- package/assets/templates/blocks/components/Stepper/StepperShowcase.doc.mjs +1 -1
- package/assets/templates/blocks/components/Stepper/StepperShowcase.tsx +6 -7
- package/assets/templates/blocks/components/Stepper/StepperStatus.tsx +1 -1
- package/assets/templates/pages/mixed-gallery/page.tsx +12 -3
- package/assets/templates/pages/table-grouped/page.tsx +151 -144
- package/assets/templates/themes/chocolate/chocolateTheme.ts +3 -1
- package/assets/templates/themes/matcha/matchaTheme.ts +3 -1
- package/assets/templates/themes/neutral/neutralTheme.ts +16 -9
- package/clients/cli/commands/build-theme.mjs +85 -0
- package/clients/cli/commands/dialog-adaptive-template.test.mjs +24 -0
- package/clients/cli/commands/theme-targets.behavior.test.mjs +64 -0
- package/clients/cli/commands/theme-targets.doc.mjs +38 -0
- package/clients/cli/commands/theme-template.doc.mjs +2 -2
- package/clients/cli/commands/theme.doc.mjs +4 -2
- package/clients/cli/index.mjs +1 -0
- package/clients/cli/lib/component-format.mjs +2 -2
- package/clients/cli/lib/manifest.mjs +2 -0
- package/foundation/discovery/theming-targets.d.mts +86 -0
- package/foundation/discovery/theming-targets.mjs +202 -0
- package/foundation/discovery/theming-targets.test.mjs +245 -0
- package/foundation/response/response-types.doc.mjs +7 -2
- package/package.json +9 -9
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperHorizontal.tsx +0 -24
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperMultiStepForm.tsx +0 -92
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.doc.mjs +0 -14
- package/assets/templates/blocks/components/Stepper/StepperVerticalOnboarding.tsx +0 -40
|
@@ -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
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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={
|
|
993
|
+
icon={
|
|
994
|
+
isExpanded
|
|
995
|
+
? ChevronDownIcon
|
|
996
|
+
: ChevronRightIcon
|
|
997
|
+
}
|
|
1023
998
|
size="sm"
|
|
1024
|
-
color=
|
|
999
|
+
color="secondary"
|
|
1025
1000
|
/>
|
|
1026
|
-
<Text type="
|
|
1027
|
-
{
|
|
1001
|
+
<Text type="body" weight="bold">
|
|
1002
|
+
{getGroupLabel(groupBy, key)}
|
|
1028
1003
|
</Text>
|
|
1029
|
-
<
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
}
|
|
@@ -27,7 +27,9 @@ const chocolateSyntax = defineSyntaxTheme({
|
|
|
27
27
|
tag: ['#8c3a3a', '#d47a7a'],
|
|
28
28
|
attribute: ['#8C5927', '#d4a06a'],
|
|
29
29
|
property: ['#3a7c6b', '#70c4b0'],
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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(#
|
|
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
|
|
615
|
-
'--color-error': '#
|
|
621
|
+
// Red T58 saturated stop (= variant:error badge bg)
|
|
622
|
+
'--color-error': '#c9303a',
|
|
616
623
|
},
|
|
617
624
|
},
|
|
618
625
|
|
|
@@ -38,6 +38,7 @@ import {ERROR_CODES} from '../../../foundation/response/error-codes.mjs';
|
|
|
38
38
|
import {themeAdd} from '../../../api/theme/add/add.mjs';
|
|
39
39
|
import {themeTemplate} from '../../../api/theme/template/template.mjs';
|
|
40
40
|
import {themeList} from '../../../api/theme/list/list.mjs';
|
|
41
|
+
import {themeTargets} from '../../../api/theme/targets/targets.mjs';
|
|
41
42
|
import {themeBuild, importSpecifier} from '../../../api/theme/build/build.mjs';
|
|
42
43
|
import {defineCommand} from '../lib/define-command.mjs';
|
|
43
44
|
import {doc as themeGroup} from './theme.doc.mjs';
|
|
@@ -45,10 +46,12 @@ import {doc as themeBuildCommand} from './theme-build.doc.mjs';
|
|
|
45
46
|
import {doc as themeListCommand} from './theme-list.doc.mjs';
|
|
46
47
|
import {doc as themeAddCommand} from './theme-add.doc.mjs';
|
|
47
48
|
import {doc as themeTemplateCommand} from './theme-template.doc.mjs';
|
|
49
|
+
import {doc as themeTargetsCommand} from './theme-targets.doc.mjs';
|
|
48
50
|
import {doc as themeBuildFn} from '../../../api/theme/themeBuild.doc.mjs';
|
|
49
51
|
import {doc as themeListFn} from '../../../api/theme/themeList.doc.mjs';
|
|
50
52
|
import {doc as themeAddFn} from '../../../api/theme/themeAdd.doc.mjs';
|
|
51
53
|
import {doc as themeTemplateFn} from '../../../api/theme/themeTemplate.doc.mjs';
|
|
54
|
+
import {doc as themeTargetsFn} from '../../../api/theme/themeTargets.doc.mjs';
|
|
52
55
|
|
|
53
56
|
/**
|
|
54
57
|
* Path to this CLI's real entry (clients/cli/bin/astryx.mjs), resolved from
|
|
@@ -194,6 +197,36 @@ function printThemeList(themes) {
|
|
|
194
197
|
);
|
|
195
198
|
}
|
|
196
199
|
|
|
200
|
+
/**
|
|
201
|
+
* Render the targets as one greppable line each, under an aligned header. A
|
|
202
|
+
* `records()` block would be five lines per target — over a thousand for the
|
|
203
|
+
* full surface, which is the view this command exists to make readable.
|
|
204
|
+
* @param {import('../../../api/theme/theme.type.mjs').ThemeTargetEntry[]} targets
|
|
205
|
+
* @returns {string}
|
|
206
|
+
*/
|
|
207
|
+
function formatTargetsTable(targets) {
|
|
208
|
+
const rows = targets.map(t => ({
|
|
209
|
+
key: t.key,
|
|
210
|
+
component: t.component,
|
|
211
|
+
props: t.props.join(', ') || '-',
|
|
212
|
+
states: t.states.join(', ') || '-',
|
|
213
|
+
}));
|
|
214
|
+
const head = {key: 'key', component: 'component', props: 'props', states: 'states'};
|
|
215
|
+
const width = (/** @type {'key'|'component'|'props'} */ field) =>
|
|
216
|
+
[head, ...rows].reduce((max, r) => Math.max(max, r[field].length), 0);
|
|
217
|
+
const w = {key: width('key'), component: width('component'), props: width('props')};
|
|
218
|
+
const line = (/** @type {typeof head} */ r) =>
|
|
219
|
+
[
|
|
220
|
+
r.key.padEnd(w.key),
|
|
221
|
+
r.component.padEnd(w.component),
|
|
222
|
+
r.props.padEnd(w.props),
|
|
223
|
+
r.states,
|
|
224
|
+
]
|
|
225
|
+
.join(' ')
|
|
226
|
+
.trimEnd();
|
|
227
|
+
return [line(head), ...rows.map(line)].join('\n');
|
|
228
|
+
}
|
|
229
|
+
|
|
197
230
|
/**
|
|
198
231
|
* @param {import('commander').Command} program
|
|
199
232
|
*/
|
|
@@ -225,6 +258,18 @@ export function registerTheme(program) {
|
|
|
225
258
|
},
|
|
226
259
|
});
|
|
227
260
|
|
|
261
|
+
// Theming questions are asked at `theme`, but per-component overrides live
|
|
262
|
+
// under `component`. Without this pointer the group reads as a build-tool
|
|
263
|
+
// menu, and the component targets are unreachable from the noun the user
|
|
264
|
+
// started at.
|
|
265
|
+
theme.addHelpText(
|
|
266
|
+
'after',
|
|
267
|
+
`\nComponent style overrides:\n` +
|
|
268
|
+
` ${getCliInvocation()} theme targets Every themeable class, with its props and states\n` +
|
|
269
|
+
` ${getCliInvocation()} component <Name> One component's theming table\n` +
|
|
270
|
+
` ${getCliInvocation()} docs theme How component overrides work\n`,
|
|
271
|
+
);
|
|
272
|
+
|
|
228
273
|
defineCommand(theme, themeBuildCommand, {
|
|
229
274
|
fn: themeBuildFn,
|
|
230
275
|
action: async (
|
|
@@ -502,4 +547,44 @@ export function registerTheme(program) {
|
|
|
502
547
|
);
|
|
503
548
|
},
|
|
504
549
|
});
|
|
550
|
+
|
|
551
|
+
defineCommand(theme, themeTargetsCommand, {
|
|
552
|
+
fn: themeTargetsFn,
|
|
553
|
+
action: async (/** @type {string | undefined} */ filter) => {
|
|
554
|
+
const json = program.opts().json || false;
|
|
555
|
+
|
|
556
|
+
/** @type {import('../../../api/theme/theme.type.mjs').ThemeTargetsResponse} */
|
|
557
|
+
let result;
|
|
558
|
+
try {
|
|
559
|
+
result = await themeTargets(filter, {cwd: process.cwd()});
|
|
560
|
+
} catch (e) {
|
|
561
|
+
const err =
|
|
562
|
+
/** @type {import('../../../api/error.mjs').AstryxError} */ (e);
|
|
563
|
+
cliError(err.message, {
|
|
564
|
+
suggestions: err.suggestions || [],
|
|
565
|
+
code: err.code,
|
|
566
|
+
});
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (json) return jsonOut(result);
|
|
571
|
+
|
|
572
|
+
const run = getCliInvocation();
|
|
573
|
+
const {targets, componentCount} = result.data;
|
|
574
|
+
emit(
|
|
575
|
+
section(
|
|
576
|
+
'Theming targets',
|
|
577
|
+
`${targets.length} across ${componentCount} component${componentCount === 1 ? '' : 's'}`,
|
|
578
|
+
),
|
|
579
|
+
text(formatTargetsTable(targets)),
|
|
580
|
+
text(
|
|
581
|
+
[
|
|
582
|
+
`Each key goes under \`components\` in defineTheme; it paints \`.astryx-<key>\`.`,
|
|
583
|
+
`Props take a value (\`variant:secondary\`); states are written bare (\`checked\`).`,
|
|
584
|
+
`One component in full: ${run} component <Name>`,
|
|
585
|
+
].join('\n'),
|
|
586
|
+
),
|
|
587
|
+
);
|
|
588
|
+
},
|
|
589
|
+
});
|
|
505
590
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it} from 'vitest';
|
|
4
|
+
import {component} from '../../../api/component/component.mjs';
|
|
5
|
+
|
|
6
|
+
const CWD = {cwd: '.'};
|
|
7
|
+
|
|
8
|
+
// Resolves built-in block templates by walking the source tree.
|
|
9
|
+
const SCAN_TIMEOUT = 30_000;
|
|
10
|
+
|
|
11
|
+
describe(
|
|
12
|
+
'Dialog adaptive presentation template',
|
|
13
|
+
() => {
|
|
14
|
+
it('is listed as a Dialog example block', async () => {
|
|
15
|
+
const result = await component('Dialog', {
|
|
16
|
+
...CWD,
|
|
17
|
+
blocks: true,
|
|
18
|
+
});
|
|
19
|
+
const exampleNames = result.data.examples.map(b => b.name);
|
|
20
|
+
expect(exampleNames).toContain('DialogAdaptivePresentation');
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
SCAN_TIMEOUT,
|
|
24
|
+
);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file CLI behavior for `astryx theme targets`.
|
|
5
|
+
*
|
|
6
|
+
* The API leaf is covered by api/theme/targets/targets.test.mjs; what is only
|
|
7
|
+
* reachable here is the terminal binding — the table a human reads, the JSON
|
|
8
|
+
* envelope a lint script reads, and the route into component overrides from
|
|
9
|
+
* `theme --help`, which is where the question gets asked.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect} from 'vitest';
|
|
13
|
+
import {runCli} from '../../../test-utils/run-cli.mjs';
|
|
14
|
+
|
|
15
|
+
describe('astryx theme targets', () => {
|
|
16
|
+
it('prints one greppable line per target, with props and states', async () => {
|
|
17
|
+
const {status, stdout} = await runCli(['theme', 'targets', 'Switch']);
|
|
18
|
+
|
|
19
|
+
expect(status).toBe(0);
|
|
20
|
+
expect(stdout).toMatch(/^switch\s+Switch\s+size\s+checked, disabled$/m);
|
|
21
|
+
expect(stdout).toMatch(/^switch-thumb\s+Switch\s+size\s+checked$/m);
|
|
22
|
+
expect(stdout).toMatch(/4 across 1 component/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('lists the whole surface when unfiltered', async () => {
|
|
26
|
+
const {status, stdout} = await runCli(['theme', 'targets']);
|
|
27
|
+
|
|
28
|
+
expect(status).toBe(0);
|
|
29
|
+
const rows = stdout.split('\n').filter(l => /^[a-z][a-z0-9-]*\s{2,}/.test(l));
|
|
30
|
+
expect(rows.length).toBeGreaterThan(100);
|
|
31
|
+
expect(stdout).toMatch(/^button\s/m);
|
|
32
|
+
expect(stdout).toMatch(/^switch-thumb\s/m);
|
|
33
|
+
}, 30_000);
|
|
34
|
+
|
|
35
|
+
it('returns a theme.targets envelope under --json', async () => {
|
|
36
|
+
const {status, stdout} = await runCli(['--json', 'theme', 'targets', 'Switch']);
|
|
37
|
+
|
|
38
|
+
expect(status).toBe(0);
|
|
39
|
+
const payload = JSON.parse(stdout);
|
|
40
|
+
expect(payload.type).toBe('theme.targets');
|
|
41
|
+
expect(payload.data.targets).toContainEqual({
|
|
42
|
+
key: 'switch-thumb',
|
|
43
|
+
className: 'astryx-switch-thumb',
|
|
44
|
+
component: 'Switch',
|
|
45
|
+
props: ['size'],
|
|
46
|
+
states: ['checked'],
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('fails a filter that matches nothing', async () => {
|
|
51
|
+
const {status, stderr} = await runCli(['theme', 'targets', 'nosuchthing']);
|
|
52
|
+
|
|
53
|
+
expect(status).toBe(1);
|
|
54
|
+
expect(stderr).toMatch(/No theming target matches "nosuchthing"/);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('routes a theming question from `theme --help` to component overrides', async () => {
|
|
58
|
+
const {stdout} = await runCli(['theme', '--help']);
|
|
59
|
+
|
|
60
|
+
expect(stdout).toMatch(/Component style overrides:/);
|
|
61
|
+
expect(stdout).toMatch(/theme targets/);
|
|
62
|
+
expect(stdout).toMatch(/component <Name>/);
|
|
63
|
+
});
|
|
64
|
+
});
|