@astryxdesign/cli 0.7.11 → 0.7.12-canary.ccb4a6d

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 (68) hide show
  1. package/api/build/kit/rank.mjs +21 -4
  2. package/api/build/kit/words.d.mts +1 -0
  3. package/api/build/kit/words.mjs +1 -1
  4. package/api/icon/_adapter.d.mts +3 -2
  5. package/api/icon/_adapter.mjs +8 -2
  6. package/api/icon/_authoring.d.mts +1 -0
  7. package/api/icon/_authoring.mjs +586 -66
  8. package/api/icon/_default-look.d.mts +155 -0
  9. package/api/icon/_default-look.mjs +474 -0
  10. package/api/icon/_records.d.mts +1 -1
  11. package/api/icon/_records.mjs +1 -0
  12. package/api/icon/add/add.mjs +224 -38
  13. package/api/icon/icon.type.d.mts +22 -2
  14. package/api/icon/icon.type.mjs +6 -2
  15. package/api/icon/iconAdd.doc.mjs +2 -2
  16. package/api/icon/iconNewDefinition.doc.mjs +3 -2
  17. package/api/icon/iconNewSet.doc.mjs +4 -4
  18. package/api/icon/search/search.mjs +10 -9
  19. package/api/integration/add-helpers.d.mts +11 -1
  20. package/api/integration/add-helpers.mjs +5 -2
  21. package/api/integration/authoring-checks.d.mts +0 -2
  22. package/api/integration/authoring-checks.mjs +1 -6
  23. package/api/logger.mjs +2 -1
  24. package/api/swizzle/_adapter.d.mts +1 -0
  25. package/api/swizzle/_adapter.mjs +4 -1
  26. package/api/swizzle/_moduleGraph.d.mts +171 -0
  27. package/api/swizzle/_moduleGraph.mjs +795 -0
  28. package/api/swizzle/copy/copy.d.mts +2 -43
  29. package/api/swizzle/copy/copy.mjs +195 -314
  30. package/api/swizzle/swizzle.d.mts +0 -2
  31. package/api/swizzle/swizzle.doc.mjs +9 -4
  32. package/api/swizzle/swizzle.mjs +3 -7
  33. package/api/swizzle/swizzle.type.d.mts +2 -1
  34. package/api/swizzle/swizzle.type.mjs +3 -2
  35. package/assets/discover/official-packages.json +20 -0
  36. package/assets/docs/tree/icon-sets.doc.mjs +11 -5
  37. package/assets/templates/blocks/components/Table/TableRowIndexTable.doc.mjs +1 -1
  38. package/assets/templates/blocks/components/Table/TableRowIndexTable.tsx +2 -2
  39. package/assets/templates/pages/course-detail/page.tsx +699 -0
  40. package/assets/templates/pages/course-detail/template.doc.mjs +37 -0
  41. package/assets/templates/pages/file-explorer/page.tsx +103 -79
  42. package/assets/templates/pages/table-grouped/page.tsx +160 -232
  43. package/assets/templates/pages/table-grouped/template.doc.mjs +0 -2
  44. package/assets/templates/pages/theme-showcase/page.tsx +724 -792
  45. package/assets/templates/pages/theme-showcase/template.doc.mjs +4 -5
  46. package/authoring/doctypes/template/type.ts +1 -0
  47. package/clients/cli/commands/experimental/icon.mjs +31 -10
  48. package/clients/cli/commands/experimental-icon-add.doc.mjs +1 -1
  49. package/clients/cli/commands/experimental-icon-new-definition.doc.mjs +2 -1
  50. package/clients/cli/commands/experimental-icon-new-set.doc.mjs +2 -2
  51. package/clients/cli/commands/swizzle.doc.mjs +8 -3
  52. package/clients/cli/commands/swizzle.mjs +21 -9
  53. package/foundation/discovery/component-loader.d.mts +1 -1
  54. package/foundation/discovery/doc-types.mjs +9 -2
  55. package/foundation/discovery/icon-ranking.d.mts +27 -3
  56. package/foundation/discovery/icon-ranking.mjs +125 -8
  57. package/foundation/discovery/icon-set-checks.mjs +44 -35
  58. package/foundation/discovery/icon-set-discovery.d.mts +13 -0
  59. package/foundation/discovery/icon-set-discovery.mjs +25 -0
  60. package/foundation/discovery/icon-set-writer.d.mts +238 -16
  61. package/foundation/discovery/icon-set-writer.mjs +676 -79
  62. package/foundation/discovery/module-exports.d.mts +3 -2
  63. package/foundation/discovery/module-exports.mjs +167 -37
  64. package/foundation/fs/paths.d.mts +2 -1
  65. package/foundation/fs/paths.mjs +3 -4
  66. package/foundation/text/shell-arg.d.mts +14 -0
  67. package/foundation/text/shell-arg.mjs +17 -0
  68. package/package.json +6 -6
@@ -2,7 +2,7 @@
2
2
 
3
3
  'use client';
4
4
 
5
- import React, {useState, useMemo} from 'react';
5
+ import {useState, useMemo} from 'react';
6
6
  import {useResizable, ResizeHandle} from '@astryxdesign/core/Resizable';
7
7
  import type {ResizableProps} from '@astryxdesign/core/Resizable';
8
8
  import {
@@ -37,17 +37,13 @@ import {Divider} from '@astryxdesign/core/Divider';
37
37
  import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
38
38
  import {
39
39
  Table,
40
- TableBody,
41
- TableRow,
42
- TableCell,
43
40
  proportional,
44
41
  pixel,
45
- resolveColumnWidths,
42
+ useTableGroupedRows,
46
43
  } from '@astryxdesign/core/Table';
47
- import type {TableColumn} from '@astryxdesign/core/Table';
44
+ import type {TableColumn, TablePlugin} from '@astryxdesign/core/Table';
45
+ import {rtlStyles} from '@astryxdesign/core/utils';
48
46
  import {
49
- ChevronRightIcon,
50
- ChevronDownIcon,
51
47
  PencilIcon,
52
48
  DocumentDuplicateIcon,
53
49
  ArrowRightIcon,
@@ -59,16 +55,6 @@ import {
59
55
  import {XMarkIcon} from '@heroicons/react/24/outline';
60
56
  import {ChartBarIcon} from '@heroicons/react/24/solid';
61
57
 
62
- // Plain inline styles using Astryx design-token CSS variables (declared at
63
- // :root by `@astryxdesign/core/astryx.css`). No StyleX compiler required.
64
- // The group-header background + cursor live on the colSpan TableCell (which
65
- // reliably forwards `style`) so they fill the full row width.
66
- const groupHeaderCell: React.CSSProperties = {
67
- cursor: 'pointer',
68
- backgroundColor: 'var(--color-background-muted)',
69
- padding: 'var(--spacing-3) var(--spacing-4)',
70
- };
71
-
72
58
  // Types
73
59
  type TaskStatus = 'in_progress' | 'todo' | 'backlog' | 'done';
74
60
  type TaskPriority = 'urgent' | 'high' | 'medium' | 'low' | 'none';
@@ -571,8 +557,6 @@ const STATUS_LABEL: Record<TaskStatus, string> = {
571
557
  done: 'Done',
572
558
  };
573
559
 
574
- const GROUP_ORDER: TaskStatus[] = ['in_progress', 'todo', 'backlog', 'done'];
575
-
576
560
  type GroupByField = 'status' | 'priority' | 'project' | 'assignee' | 'none';
577
561
 
578
562
  const GROUP_BY_OPTIONS: {value: GroupByField; label: string}[] = [
@@ -583,26 +567,6 @@ const GROUP_BY_OPTIONS: {value: GroupByField; label: string}[] = [
583
567
  {value: 'assignee', label: 'Assignee'},
584
568
  ];
585
569
 
586
- function groupTasks(
587
- tasks: TaskRow[],
588
- groupBy: GroupByField,
589
- ): Map<string, TaskRow[]> {
590
- if (groupBy === 'none') {
591
- return new Map([['All', tasks]]);
592
- }
593
- const map = new Map<string, TaskRow[]>();
594
- for (const task of tasks) {
595
- const key = String(task[groupBy]) || '—';
596
- let group = map.get(key);
597
- if (!group) {
598
- group = [];
599
- map.set(key, group);
600
- }
601
- group.push(task);
602
- }
603
- return map;
604
- }
605
-
606
570
  function getGroupLabel(groupBy: GroupByField, key: string): string {
607
571
  if (groupBy === 'status') {
608
572
  return STATUS_LABEL[key as TaskStatus] ?? key;
@@ -625,36 +589,137 @@ const columns: TableColumn<TaskRow>[] = [
625
589
  key: 'status',
626
590
  header: '',
627
591
  width: pixel(44),
592
+ renderCell: task => (
593
+ <Center axis="horizontal">
594
+ <StatusDot
595
+ variant={STATUS_DOT_VARIANT[task.status]}
596
+ label={STATUS_LABEL[task.status]}
597
+ />
598
+ </Center>
599
+ ),
628
600
  },
629
601
  {
630
602
  key: 'title',
631
603
  header: 'Issue',
632
604
  width: proportional(1),
605
+ renderCell: task => (
606
+ <HStack gap={3} vAlign="center">
607
+ <Icon
608
+ icon={ChartBarIcon}
609
+ size="sm"
610
+ color={PRIORITY_COLOR[task.priority]}
611
+ />
612
+ <Text type="supporting" color="secondary">
613
+ {task.taskId}
614
+ </Text>
615
+ <Text type="body" maxLines={1}>
616
+ {task.title}
617
+ </Text>
618
+ {task.subtitle && (
619
+ <Text type="body" color="secondary" maxLines={1}>
620
+ › {task.subtitle}
621
+ </Text>
622
+ )}
623
+ </HStack>
624
+ ),
633
625
  },
634
626
  {
635
627
  key: 'project',
636
628
  header: 'Project',
637
629
  width: pixel(180),
630
+ renderCell: task =>
631
+ task.project ? (
632
+ <Text type="body" maxLines={1}>
633
+ {task.project}
634
+ </Text>
635
+ ) : (
636
+ <Text type="supporting" color="secondary">
637
+ —
638
+ </Text>
639
+ ),
638
640
  },
639
641
  {
640
642
  key: 'created',
641
643
  header: 'Created',
642
- width: pixel(72),
644
+ width: pixel(88),
645
+ renderCell: task => (
646
+ <Text type="supporting" color="secondary">
647
+ {task.created}
648
+ </Text>
649
+ ),
643
650
  },
644
651
  {
645
652
  key: 'updated',
646
653
  header: 'Updated',
647
- width: pixel(72),
654
+ width: pixel(88),
655
+ renderCell: task => (
656
+ <Text type="supporting" color="secondary">
657
+ {task.updated}
658
+ </Text>
659
+ ),
648
660
  },
649
661
  {
650
662
  key: 'assignee',
651
663
  header: 'Assignee',
652
- width: pixel(52),
664
+ width: pixel(88),
665
+ renderCell: task => <Avatar name={task.assignee} size="sm" />,
653
666
  },
654
667
  {
655
668
  key: 'actions',
656
669
  header: '',
657
670
  width: pixel(56),
671
+ renderCell: () => (
672
+ <DropdownMenu
673
+ button={{
674
+ label: 'Actions',
675
+ variant: 'ghost',
676
+ size: 'sm',
677
+ icon: <Icon icon={EllipsisHorizontalIcon} size="sm" />,
678
+ isIconOnly: true,
679
+ }}
680
+ hasChevron={false}
681
+ items={[
682
+ {
683
+ label: 'Edit issue',
684
+ icon: PencilIcon,
685
+ onClick: () => {},
686
+ },
687
+ {
688
+ label: 'Assign to...',
689
+ icon: UserIcon,
690
+ onClick: () => {},
691
+ },
692
+ {
693
+ label: 'Add label',
694
+ icon: TagIcon,
695
+ onClick: () => {},
696
+ },
697
+ {
698
+ label: 'Duplicate',
699
+ icon: DocumentDuplicateIcon,
700
+ onClick: () => {},
701
+ },
702
+ {
703
+ label: 'Move to project',
704
+ icon: (
705
+ <Icon
706
+ icon={ArrowRightIcon}
707
+ size="sm"
708
+ color="secondary"
709
+ xstyle={rtlStyles.mirror}
710
+ />
711
+ ),
712
+ onClick: () => {},
713
+ },
714
+ {type: 'divider' as const},
715
+ {
716
+ label: 'Delete issue',
717
+ icon: TrashIcon,
718
+ onClick: () => {},
719
+ },
720
+ ]}
721
+ />
722
+ ),
658
723
  },
659
724
  ];
660
725
 
@@ -840,8 +905,8 @@ export default function DataTableTemplate() {
840
905
  ReadonlyArray<PowerSearchFilter>
841
906
  >([]);
842
907
  const [groupBy, setGroupBy] = useState<GroupByField>('status');
843
- const [expandedGroups, setExpandedGroups] = useState<Set<string>>(
844
- () => new Set(GROUP_ORDER as string[]),
908
+ const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(
909
+ () => new Set(),
845
910
  );
846
911
 
847
912
  const filtered = useMemo(() => {
@@ -861,19 +926,8 @@ export default function DataTableTemplate() {
861
926
  return data;
862
927
  }, [search, priorityFilter]);
863
928
 
864
- const grouped = useMemo(
865
- () => groupTasks(filtered, groupBy),
866
- [filtered, groupBy],
867
- );
868
-
869
- const groupKeys = useMemo(() => Array.from(grouped.keys()), [grouped]);
870
-
871
- React.useEffect(() => {
872
- setExpandedGroups(new Set(groupKeys));
873
- }, [groupKeys]);
874
-
875
929
  const toggleGroup = (key: string) => {
876
- setExpandedGroups(prev => {
930
+ setCollapsedGroups(prev => {
877
931
  const next = new Set(prev);
878
932
  if (next.has(key)) {
879
933
  next.delete(key);
@@ -884,15 +938,48 @@ export default function DataTableTemplate() {
884
938
  });
885
939
  };
886
940
 
941
+ // Grouping by "None" shows the rows as one plain list, with no headers.
942
+ const grouped = useTableGroupedRows<TaskRow>({
943
+ data: filtered,
944
+ // Groups are keyed by their visible label, which also names each
945
+ // group's toggle button.
946
+ groupBy: task =>
947
+ groupBy === 'none'
948
+ ? ''
949
+ : getGroupLabel(groupBy, String(task[groupBy]) || '—'),
950
+ collapsedGroups,
951
+ onToggleGroup: toggleGroup,
952
+ getRowKey: task => task.id,
953
+ renderGroupHeader: (key, count) => (
954
+ <HStack gap={2} vAlign="center">
955
+ <Text type="body" weight="bold">
956
+ {key}
957
+ </Text>
958
+ <Badge variant="neutral" label={String(count)} />
959
+ </HStack>
960
+ ),
961
+ });
962
+
963
+ // Clicking an issue row opens its details. Group header rows keep their
964
+ // own toggle.
965
+ const openTaskPlugin: TablePlugin<TaskRow> = {
966
+ transformBodyRow(props, task) {
967
+ if (task.id === '') {
968
+ return props;
969
+ }
970
+ return {
971
+ ...props,
972
+ htmlProps: {...props.htmlProps, onClick: () => setSelectedTask(task)},
973
+ };
974
+ },
975
+ };
976
+
887
977
  const detailPanel = useResizable({
888
978
  defaultSize: 360,
889
979
  minSize: 280,
890
980
  maxSize: 500,
891
981
  });
892
982
 
893
- const COL_COUNT = columns.length;
894
- const resolvedWidths = resolveColumnWidths(columns);
895
-
896
983
  return (
897
984
  <>
898
985
  <Layout
@@ -931,7 +1018,10 @@ export default function DataTableTemplate() {
931
1018
  <RadioList
932
1019
  label="Group by"
933
1020
  value={groupBy}
934
- onChange={v => setGroupBy(v as GroupByField)}>
1021
+ onChange={v => {
1022
+ setGroupBy(v as GroupByField);
1023
+ setCollapsedGroups(new Set());
1024
+ }}>
935
1025
  {GROUP_BY_OPTIONS.map(opt => (
936
1026
  <RadioListItem
937
1027
  key={opt.value}
@@ -951,181 +1041,19 @@ export default function DataTableTemplate() {
951
1041
  content={
952
1042
  <LayoutContent role="main" padding={0}>
953
1043
  <Table
1044
+ data={groupBy === 'none' ? filtered : grouped.data}
954
1045
  columns={columns}
1046
+ idKey={groupBy === 'none' ? 'id' : grouped.idKey}
955
1047
  density="balanced"
956
1048
  dividers="rows"
957
1049
  textOverflow="truncate"
958
- hasHover>
959
- <colgroup>
960
- {columns.map(col => (
961
- <col
962
- key={col.key}
963
- style={resolvedWidths.columns.get(col.key)?.style}
964
- />
965
- ))}
966
- </colgroup>
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);
974
-
975
- return (
976
- <React.Fragment key={key}>
977
- {groupBy !== 'none' && (
978
- <TableRow
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">
992
- <Icon
993
- icon={
994
- isExpanded
995
- ? ChevronDownIcon
996
- : ChevronRightIcon
997
- }
998
- size="sm"
999
- color="secondary"
1000
- />
1001
- <Text type="body" weight="bold">
1002
- {getGroupLabel(groupBy, key)}
1003
- </Text>
1004
- <Badge
1005
- variant="neutral"
1006
- label={String(tasks.length)}
1007
- />
1008
- </HStack>
1009
- </TableCell>
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}
1062
- </Text>
1063
- </TableCell>
1064
- <TableCell>
1065
- <Text type="supporting" color="secondary">
1066
- {task.updated}
1067
- </Text>
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>
1128
- </Table>
1050
+ hasHover
1051
+ plugins={
1052
+ groupBy === 'none'
1053
+ ? {openTask: openTaskPlugin}
1054
+ : {grouped: grouped.plugin, openTask: openTaskPlugin}
1055
+ }
1056
+ />
1129
1057
  </LayoutContent>
1130
1058
  }
1131
1059
  end={
@@ -25,8 +25,6 @@ export const doc = {
25
25
  icons: {
26
26
  ArrowRightIcon: 'arrowRight',
27
27
  ChartBarIcon: 'chart',
28
- ChevronDownIcon: 'chevronDown',
29
- ChevronRightIcon: 'chevronRight',
30
28
  DocumentDuplicateIcon: 'copy',
31
29
  EllipsisHorizontalIcon: 'moreHorizontal',
32
30
  PencilIcon: 'edit',