mac-human-design 0.1.21 → 0.1.23
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
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
This file tracks changes between published npm versions of `mac-human-design`.
|
|
4
4
|
|
|
5
|
+
## 0.1.23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `grid`/`table` semantics, row and column metadata, busy state, selected
|
|
10
|
+
row state, keyboard row activation, and optional accessible labels to
|
|
11
|
+
`MacDataTable`.
|
|
12
|
+
- Added focus-visible styling for interactive `MacDataTable` rows.
|
|
13
|
+
- Extended the macOS design verifier to protect interactive table/grid
|
|
14
|
+
accessibility invariants.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Interactive table rows can now be selected with Space and opened/activated
|
|
19
|
+
with Enter when row handlers are provided.
|
|
20
|
+
- Bumped the package to `0.1.23`.
|
|
21
|
+
|
|
22
|
+
## 0.1.22
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Added a centered loading spinner overlay for `SymbolIconButton` that shares
|
|
27
|
+
the icon button grid cell and respects reduced-motion preferences.
|
|
28
|
+
- Added `aria-busy` to loading `SymbolIconButton` instances.
|
|
29
|
+
- Extended the macOS design verifier to protect the loading spinner's centered
|
|
30
|
+
geometry, animation keyframes, reduced-motion behavior, and `aria-busy`
|
|
31
|
+
loading state.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Kept the underlying symbol visible at reduced opacity while loading so the
|
|
36
|
+
button remains spatially stable.
|
|
37
|
+
- Bumped the package to `0.1.22`.
|
|
38
|
+
|
|
5
39
|
## 0.1.21
|
|
6
40
|
|
|
7
41
|
### Added
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@ const packageJsonPath = path.join(repoRoot, "package.json");
|
|
|
8
8
|
const wrapperPath = path.join(repoRoot, "src", "components", "MacBaseUI.tsx");
|
|
9
9
|
const symbolIconButtonPath = path.join(repoRoot, "src", "components", "SymbolIconButton.tsx");
|
|
10
10
|
const galleryPath = path.join(repoRoot, "src", "components", "MacBaseUIGallery.tsx");
|
|
11
|
+
const dataTablePath = path.join(repoRoot, "src", "components", "MacDataTable.tsx");
|
|
11
12
|
const symbolServicePath = path.join(repoRoot, "src", "symbols", "systemSymbolService.ts");
|
|
12
13
|
|
|
13
14
|
function readText(filePath) {
|
|
@@ -48,6 +49,7 @@ const packageJson = JSON.parse(readText(packageJsonPath));
|
|
|
48
49
|
const wrapperSource = readText(wrapperPath);
|
|
49
50
|
const symbolIconButtonSource = readText(symbolIconButtonPath);
|
|
50
51
|
const gallerySource = readText(galleryPath);
|
|
52
|
+
const dataTableSource = readText(dataTablePath);
|
|
51
53
|
const symbolServiceSource = readText(symbolServicePath);
|
|
52
54
|
|
|
53
55
|
function getCssRuleBody(selector) {
|
|
@@ -138,6 +140,10 @@ requireCssDeclarations(".hd-mac-symbol-icon-button", [
|
|
|
138
140
|
["fixed 30px min-height", /min-height\s*:\s*30px\s*;/],
|
|
139
141
|
]);
|
|
140
142
|
|
|
143
|
+
requireCssDeclarations(".hd-mac-symbol-icon-button::after", [
|
|
144
|
+
["loading overlay grid placement", /grid-area\s*:\s*1\s*\/\s*1\s*;/],
|
|
145
|
+
]);
|
|
146
|
+
|
|
141
147
|
requireCssDeclarations(".hd-mac-symbol-icon", [
|
|
142
148
|
["single grid cell placement", /grid-area\s*:\s*1\s*\/\s*1\s*;/],
|
|
143
149
|
["flex centering display", /display\s*:\s*inline-flex\s*;/],
|
|
@@ -184,6 +190,7 @@ const symbolIconRequirements = [
|
|
|
184
190
|
["SymbolIconButton imports MacIconButton", /import\s+\{\s*MacIconButton,\s*MacTooltip\s*\}\s+from\s+["']\.\/MacBaseUI["']/],
|
|
185
191
|
["SymbolIconButton render element is MacIconButton", /render=\{\s*<MacIconButton\b/],
|
|
186
192
|
["SymbolIconButton applies hd-mac-symbol-icon-button class", /className=["']hd-mac-symbol-icon-button["']/],
|
|
193
|
+
["SymbolIconButton exposes aria-busy loading state", /aria-busy=\{isLoading \|\| undefined\}/],
|
|
187
194
|
["masked symbol class path", /className=["']hd-mac-symbol-icon hd-mac-symbol-icon-mask["']/],
|
|
188
195
|
["fallback symbol class path", /className=["']hd-mac-symbol-icon hd-mac-symbol-icon-fallback["']/],
|
|
189
196
|
["mask centering", /maskPosition:\s*["']center["']/],
|
|
@@ -213,6 +220,63 @@ if (missingGalleryStateCoverage.length > 0) {
|
|
|
213
220
|
);
|
|
214
221
|
}
|
|
215
222
|
|
|
223
|
+
const loadingIconRule = getCssRuleBody(".hd-mac-symbol-icon-button[data-loading]::after");
|
|
224
|
+
const missingLoadingIconDeclarations = [
|
|
225
|
+
["centered loading spinner content", /content\s*:\s*""\s*;/],
|
|
226
|
+
["spinner width", /width\s*:\s*15px\s*;/],
|
|
227
|
+
["spinner height", /height\s*:\s*15px\s*;/],
|
|
228
|
+
["spinner automatic centering margin", /margin\s*:\s*auto\s*;/],
|
|
229
|
+
["spinner center transform origin", /transform-origin\s*:\s*center\s*;/],
|
|
230
|
+
["spinner animation", /animation\s*:\s*mac-symbol-icon-spin\s+720ms\s+linear\s+infinite\s*;/],
|
|
231
|
+
].filter(([, pattern]) => !pattern.test(loadingIconRule));
|
|
232
|
+
|
|
233
|
+
if (missingLoadingIconDeclarations.length > 0) {
|
|
234
|
+
fail(
|
|
235
|
+
"Missing centered loading affordance declarations for SymbolIconButton.",
|
|
236
|
+
missingLoadingIconDeclarations.map(([label]) => label),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!/@keyframes\s+mac-symbol-icon-spin/.test(stylesSource)) {
|
|
241
|
+
fail("Missing SymbolIconButton loading spinner keyframes.", ["hd-mac-symbol-icon-spin"]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (
|
|
245
|
+
!/@media\s*\(prefers-reduced-motion:\s*reduce\)[\s\S]*?\.hd-mac-symbol-icon-button\[data-loading\]::after[\s\S]*?animation-name\s*:\s*none\s*!important/.test(
|
|
246
|
+
stylesSource,
|
|
247
|
+
)
|
|
248
|
+
) {
|
|
249
|
+
fail("Missing reduced-motion handling for SymbolIconButton loading spinner.", [
|
|
250
|
+
".hd-mac-symbol-icon-button[data-loading]::after",
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const missingDataTableRequirements = [
|
|
255
|
+
["interactive grid role", /const tableRole = hasInteractiveRows \? "grid" : "table"/],
|
|
256
|
+
["aria busy state", /aria-busy=\{loading \|\| undefined\}/],
|
|
257
|
+
["row count metadata", /aria-rowcount=\{loading \? undefined : rows\.length \+ 1\}/],
|
|
258
|
+
["column count metadata", /aria-colcount=\{columns\.length\}/],
|
|
259
|
+
["header row index", /aria-rowindex=\{1\}/],
|
|
260
|
+
["cell column index", /aria-colindex=\{columnIndex \+ 1\}/],
|
|
261
|
+
["interactive row selected state", /aria-selected=\{hasInteractiveRows \? selected : undefined\}/],
|
|
262
|
+
["interactive row tab stop", /tabIndex=\{hasInteractiveRows \? 0 : undefined\}/],
|
|
263
|
+
["keyboard row handler", /handleRowKeyDown/],
|
|
264
|
+
["enter key activation", /event\.key === "Enter"/],
|
|
265
|
+
["space key activation", /event\.key === " "/],
|
|
266
|
+
].filter(([, pattern]) => !pattern.test(dataTableSource));
|
|
267
|
+
|
|
268
|
+
if (missingDataTableRequirements.length > 0) {
|
|
269
|
+
fail(
|
|
270
|
+
"Missing MacDataTable interactive grid accessibility invariants.",
|
|
271
|
+
missingDataTableRequirements.map(([label]) => label),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
requireCssDeclarations(".hd-mac-data-table-row-interactive:focus-visible", [
|
|
276
|
+
["focus outline reset", /outline\s*:\s*none\s*;/],
|
|
277
|
+
["focus ring", /var\(--hd-mac-focus-ring\)/],
|
|
278
|
+
]);
|
|
279
|
+
|
|
216
280
|
const missingSymbolServiceRequirements = [
|
|
217
281
|
["transparent symbol padding trim", /trimTransparentSymbolPadding/],
|
|
218
282
|
["alpha threshold trim", /TRANSPARENT_ALPHA_THRESHOLD/],
|
|
@@ -17,6 +17,8 @@ export type MacDataTableProps<Row> = {
|
|
|
17
17
|
columns: readonly MacDataTableColumn<Row>[];
|
|
18
18
|
rows: readonly Row[];
|
|
19
19
|
getRowKey: (row: Row, index: number) => React.Key;
|
|
20
|
+
ariaLabel?: string;
|
|
21
|
+
ariaLabelledBy?: string;
|
|
20
22
|
isRowSelected?: (row: Row, index: number) => boolean;
|
|
21
23
|
onRowClick?: (row: Row, index: number) => void;
|
|
22
24
|
onRowDoubleClick?: (row: Row, index: number) => void;
|
|
@@ -53,6 +55,8 @@ export function MacDataTable<Row>({
|
|
|
53
55
|
columns,
|
|
54
56
|
rows,
|
|
55
57
|
getRowKey,
|
|
58
|
+
ariaLabel,
|
|
59
|
+
ariaLabelledBy,
|
|
56
60
|
isRowSelected,
|
|
57
61
|
onRowClick,
|
|
58
62
|
onRowDoubleClick,
|
|
@@ -67,6 +71,30 @@ export function MacDataTable<Row>({
|
|
|
67
71
|
}: MacDataTableProps<Row>) {
|
|
68
72
|
const gridTemplateColumns = columns.map((column) => column.width).join(" ");
|
|
69
73
|
const hasInteractiveRows = Boolean(onRowClick || onRowDoubleClick);
|
|
74
|
+
const tableRole = hasInteractiveRows ? "grid" : "table";
|
|
75
|
+
|
|
76
|
+
const handleRowKeyDown = React.useCallback(
|
|
77
|
+
(event: React.KeyboardEvent, row: Row, index: number) => {
|
|
78
|
+
if (!hasInteractiveRows) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (event.key === "Enter") {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
if (onRowDoubleClick) {
|
|
85
|
+
onRowDoubleClick(row, index);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
onRowClick?.(row, index);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (event.key === " ") {
|
|
92
|
+
event.preventDefault();
|
|
93
|
+
onRowClick?.(row, index);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[hasInteractiveRows, onRowClick, onRowDoubleClick],
|
|
97
|
+
);
|
|
70
98
|
|
|
71
99
|
return (
|
|
72
100
|
<motion.div
|
|
@@ -74,12 +102,19 @@ export function MacDataTable<Row>({
|
|
|
74
102
|
animate={{ opacity: 1 }}
|
|
75
103
|
transition={tableTransition}
|
|
76
104
|
className={joinClasses("hd-mac-data-table", bordered && "hd-mac-data-table-bordered", className)}
|
|
105
|
+
role={tableRole}
|
|
106
|
+
aria-label={ariaLabel}
|
|
107
|
+
aria-labelledby={ariaLabelledBy}
|
|
108
|
+
aria-busy={loading || undefined}
|
|
109
|
+
aria-rowcount={loading ? undefined : rows.length + 1}
|
|
110
|
+
aria-colcount={columns.length}
|
|
77
111
|
style={style}
|
|
78
112
|
>
|
|
79
113
|
<motion.div
|
|
80
114
|
layout
|
|
81
115
|
className="hd-mac-data-table-header"
|
|
82
116
|
role="row"
|
|
117
|
+
aria-rowindex={1}
|
|
83
118
|
style={{ gridTemplateColumns }}
|
|
84
119
|
transition={tableTransition}
|
|
85
120
|
>
|
|
@@ -92,6 +127,7 @@ export function MacDataTable<Row>({
|
|
|
92
127
|
column.headerClassName,
|
|
93
128
|
)}
|
|
94
129
|
role="columnheader"
|
|
130
|
+
aria-colindex={columns.indexOf(column) + 1}
|
|
95
131
|
style={column.headerStyle}
|
|
96
132
|
>
|
|
97
133
|
{column.header}
|
|
@@ -139,10 +175,14 @@ export function MacDataTable<Row>({
|
|
|
139
175
|
data-selected={selected || undefined}
|
|
140
176
|
onClick={onRowClick ? () => onRowClick(row, index) : undefined}
|
|
141
177
|
onDoubleClick={onRowDoubleClick ? () => onRowDoubleClick(row, index) : undefined}
|
|
178
|
+
onKeyDown={hasInteractiveRows ? (event) => handleRowKeyDown(event, row, index) : undefined}
|
|
142
179
|
role="row"
|
|
180
|
+
aria-rowindex={index + 2}
|
|
181
|
+
aria-selected={hasInteractiveRows ? selected : undefined}
|
|
182
|
+
tabIndex={hasInteractiveRows ? 0 : undefined}
|
|
143
183
|
style={{ gridTemplateColumns, ...rowStyle?.(row, index) }}
|
|
144
184
|
>
|
|
145
|
-
{columns.map((column) => (
|
|
185
|
+
{columns.map((column, columnIndex) => (
|
|
146
186
|
<div
|
|
147
187
|
key={column.id}
|
|
148
188
|
className={joinClasses(
|
|
@@ -151,6 +191,7 @@ export function MacDataTable<Row>({
|
|
|
151
191
|
column.className,
|
|
152
192
|
)}
|
|
153
193
|
role="cell"
|
|
194
|
+
aria-colindex={columnIndex + 1}
|
|
154
195
|
style={column.style}
|
|
155
196
|
>
|
|
156
197
|
{column.render(row, index)}
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
.hd-mac-avatar,
|
|
77
77
|
.hd-mac-scroll-thumb,
|
|
78
78
|
.hd-mac-status-message,
|
|
79
|
+
.hd-mac-symbol-icon-button[data-loading]::after,
|
|
79
80
|
.hd-mac-window-toolbar,
|
|
80
81
|
.hd-mac-data-table,
|
|
81
82
|
.hd-mac-data-table-row {
|
|
@@ -84,6 +85,10 @@
|
|
|
84
85
|
animation-iteration-count: 1 !important;
|
|
85
86
|
scroll-behavior: auto !important;
|
|
86
87
|
}
|
|
88
|
+
|
|
89
|
+
.hd-mac-symbol-icon-button[data-loading]::after {
|
|
90
|
+
animation-name: none !important;
|
|
91
|
+
}
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
.hd-mac,
|
|
@@ -279,6 +284,10 @@
|
|
|
279
284
|
border-radius: 7px;
|
|
280
285
|
}
|
|
281
286
|
|
|
287
|
+
.hd-mac-symbol-icon-button::after {
|
|
288
|
+
grid-area: 1 / 1;
|
|
289
|
+
}
|
|
290
|
+
|
|
282
291
|
.hd-mac-symbol-icon {
|
|
283
292
|
grid-area: 1 / 1;
|
|
284
293
|
display: inline-flex;
|
|
@@ -955,7 +964,31 @@
|
|
|
955
964
|
}
|
|
956
965
|
|
|
957
966
|
.hd-mac-symbol-icon-button[data-loading] {
|
|
958
|
-
opacity:
|
|
967
|
+
opacity: 1;
|
|
968
|
+
color: var(--hd-mac-blue);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
.hd-mac-symbol-icon-button[data-loading] .hd-mac-symbol-icon {
|
|
972
|
+
opacity: 0.22;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.hd-mac-symbol-icon-button[data-loading]::after {
|
|
976
|
+
content: "";
|
|
977
|
+
width: 15px;
|
|
978
|
+
height: 15px;
|
|
979
|
+
border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent);
|
|
980
|
+
border-top-color: currentColor;
|
|
981
|
+
border-radius: 999px;
|
|
982
|
+
box-sizing: border-box;
|
|
983
|
+
margin: auto;
|
|
984
|
+
transform-origin: center;
|
|
985
|
+
animation: mac-symbol-icon-spin 720ms linear infinite;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
@keyframes mac-symbol-icon-spin {
|
|
989
|
+
to {
|
|
990
|
+
transform: rotate(360deg);
|
|
991
|
+
}
|
|
959
992
|
}
|
|
960
993
|
|
|
961
994
|
.hd-mac-toggle[data-pressed] {
|
|
@@ -1448,6 +1481,13 @@
|
|
|
1448
1481
|
cursor: default;
|
|
1449
1482
|
}
|
|
1450
1483
|
|
|
1484
|
+
.hd-mac-data-table-row-interactive:focus-visible {
|
|
1485
|
+
outline: none;
|
|
1486
|
+
box-shadow:
|
|
1487
|
+
var(--hd-mac-focus-ring),
|
|
1488
|
+
inset 0 0 0 1px color-mix(in srgb, var(--hd-mac-blue) 24%, transparent);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1451
1491
|
.hd-mac-data-table-row-interactive:hover {
|
|
1452
1492
|
background: var(--hd-mac-fill-hover);
|
|
1453
1493
|
}
|
|
@@ -1461,6 +1501,12 @@
|
|
|
1461
1501
|
box-shadow: inset 3px 0 0 var(--hd-mac-blue);
|
|
1462
1502
|
}
|
|
1463
1503
|
|
|
1504
|
+
.hd-mac-data-table-row[data-selected]:focus-visible {
|
|
1505
|
+
box-shadow:
|
|
1506
|
+
var(--hd-mac-focus-ring),
|
|
1507
|
+
inset 3px 0 0 var(--hd-mac-blue);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1464
1510
|
.hd-mac-data-table-row[data-selected]:hover {
|
|
1465
1511
|
background: color-mix(in srgb, var(--hd-mac-blue) 24%, transparent);
|
|
1466
1512
|
}
|