mac-human-design 0.1.28 → 0.1.30-preview.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.
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.30-preview.0
6
+
7
+ ### Added
8
+
9
+ - Added a centered `SymbolIconButton` state layer for hover, focus, active, and
10
+ loading feedback that sits under the glyph and spinner without changing
11
+ button geometry.
12
+ - Added an emphasis motion token for slightly richer shared control feedback.
13
+ - Added verifier coverage for the icon button state layer, glyph stacking, and
14
+ emphasis motion token.
15
+
16
+ ### Changed
17
+
18
+ - Improved icon-only button interaction feedback so highlighted toolbar buttons
19
+ feel more deliberate while keeping SF Symbols optically centered.
20
+ - Bumped the package to `0.1.30-preview.0` and publish it under the `preview`
21
+ dist-tag while this UI/UX iteration is still being verified.
22
+
23
+ ## 0.1.29
24
+
25
+ ### Added
26
+
27
+ - Added `SymbolIconButton` optical centering support through default
28
+ per-symbol offsets and an `opticalOffset` override prop.
29
+ - Added verifier coverage for symbol optical offset props, default offsets, and
30
+ CSS variable-based glyph translation.
31
+
32
+ ### Changed
33
+
34
+ - Tuned common toolbar SF Symbols such as refresh, upload, download, back,
35
+ trash, and folder-plus so their visual weight sits closer to the center of
36
+ the fixed macOS icon button.
37
+ - Bumped the package to `0.1.29`.
38
+
5
39
  ## 0.1.28
6
40
 
7
41
  ### Added
@@ -94,16 +94,17 @@ be reimplemented in consuming apps:
94
94
  | Finder-style grid/table primitive | `MacDataTable` | Uses `table` semantics for static rows, upgrades to `grid` semantics for interactive rows, exposes row/column metadata, busy state, keyboard activation, selected row state, and focus-visible row styling. |
95
95
  | Tauri window toolbar | `MacWindowToolbar` | Provides a labelled `toolbar` landmark, labelled leading/content/trailing control groups, a root drag region, and no-drag regions around interactive controls. |
96
96
  | Segmented control | `MacSegmentedControl` | Wraps the shared macOS segmented toggle styling with accessible labels, stable class names, full-width layout support, and class/style pass-through. |
97
- | Centered SF Symbols icon button | `SymbolIconButton` | Renders through `MacIconButton`, defaults to `type="button"`, preserves fixed geometry while allowing style/class pass-through, supports tooltip offsets, and exposes loading state with `aria-busy`. |
97
+ | Centered SF Symbols icon button | `SymbolIconButton` | Renders through `MacIconButton`, defaults to `type="button"`, preserves fixed geometry while allowing style/class pass-through, supports tooltip offsets and per-symbol optical offsets, and exposes loading state with `aria-busy`. |
98
98
 
99
99
  ## Icon Button Alignment Guarantees
100
100
 
101
101
  Icon-only buttons use fixed-size grid containers with zero padding, zero
102
102
  line-height, centered grid content, middle inline alignment, and center transform
103
103
  origins. `SymbolIconButton` keeps the SF Symbol and loading spinner in the same
104
- grid cell, centers generated symbol masks, trims transparent symbol padding in
105
- the native symbol service, and avoids vertical Motion offsets for icon-only
106
- controls.
104
+ grid cell, renders hover/focus/loading feedback as a centered state layer under
105
+ the glyph, centers generated symbol masks, trims transparent symbol padding in
106
+ the native symbol service, applies per-symbol optical offsets for asymmetric SF
107
+ Symbols, and avoids vertical Motion offsets for icon-only controls.
107
108
 
108
109
  The gallery at `examples/macos-base-ui-gallery` renders these wrappers across
109
110
  normal, selected, disabled, mixed, popup, dialog, and narrow-width states.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mac-human-design",
3
- "version": "0.1.28",
3
+ "version": "0.1.30-preview.0",
4
4
  "description": "Reusable macOS-oriented UI primitives and SF Symbols bridge for Tauri apps.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -39,6 +39,7 @@
39
39
  "check": "npm run typecheck && npm run verify:base-ui && npm run verify:design && npm run verify:gallery",
40
40
  "dev:gallery": "vite --host 127.0.0.1 examples/macos-base-ui-gallery",
41
41
  "publish": "npm publish --access public --ignore-scripts",
42
+ "publish:preview": "npm publish --access public --ignore-scripts --tag preview",
42
43
  "typecheck": "tsc --noEmit",
43
44
  "verify:base-ui": "node scripts/verify-base-ui-coverage.mjs",
44
45
  "verify:design": "node scripts/verify-macos-design.mjs",
@@ -86,6 +86,7 @@ const requiredStyleFeatures = [
86
86
  ["reduced motion support", /@media\s*\(prefers-reduced-motion:\s*reduce\)/],
87
87
  ["keyboard focus-visible support", /:focus-visible/],
88
88
  ["macOS blue accent token", /--hd-mac-blue:\s*#0a84ff/i],
89
+ ["emphasis motion token", /--hd-mac-motion-emphasis:\s*220ms\s+cubic-bezier\(0\.16,\s*1,\s*0\.3,\s*1\)/],
89
90
  ];
90
91
 
91
92
  const missingFeatures = requiredStyleFeatures
@@ -146,6 +147,25 @@ requireCssDeclarations(".hd-mac-symbol-icon-button", [
146
147
  ["fixed 30px height", /height\s*:\s*30px\s*;/],
147
148
  ["fixed 30px min-width", /min-width\s*:\s*30px\s*;/],
148
149
  ["fixed 30px min-height", /min-height\s*:\s*30px\s*;/],
150
+ ["stable state layer positioning", /position\s*:\s*relative\s*;/],
151
+ ["state layer isolation", /isolation\s*:\s*isolate\s*;/],
152
+ ["state layer clipping", /overflow\s*:\s*hidden\s*;/],
153
+ ]);
154
+
155
+ requireCssDeclarations(".hd-mac-symbol-icon-button::before", [
156
+ ["state layer content", /content\s*:\s*""\s*;/],
157
+ ["state layer behind glyph", /z-index\s*:\s*0\s*;/],
158
+ ["state layer full width", /width\s*:\s*100%\s*;/],
159
+ ["state layer full height", /height\s*:\s*100%\s*;/],
160
+ ["state layer hidden by default", /opacity\s*:\s*0\s*;/],
161
+ ["state layer scale starts centered", /transform\s*:\s*scale\(0\.82\)\s*;/],
162
+ ["state layer opacity transition", /opacity\s+var\(--hd-mac-motion-standard\)/],
163
+ ["state layer emphasis transition", /transform\s+var\(--hd-mac-motion-emphasis\)/],
164
+ ]);
165
+
166
+ requireCssDeclarations(".hd-mac-symbol-icon-button:hover::before", [
167
+ ["hover state layer visible", /opacity\s*:\s*1\s*;/],
168
+ ["hover state layer full scale", /transform\s*:\s*scale\(1\)\s*;/],
149
169
  ]);
150
170
 
151
171
  requireCssDeclarations(".hd-mac-symbol-icon-button::after", [
@@ -154,11 +174,17 @@ requireCssDeclarations(".hd-mac-symbol-icon-button::after", [
154
174
 
155
175
  requireCssDeclarations(".hd-mac-symbol-icon", [
156
176
  ["single grid cell placement", /grid-area\s*:\s*1\s*\/\s*1\s*;/],
177
+ ["glyph above state layer", /z-index\s*:\s*1\s*;/],
157
178
  ["flex centering display", /display\s*:\s*inline-flex\s*;/],
158
179
  ["vertical glyph centering", /align-items\s*:\s*center\s*;/],
159
180
  ["horizontal glyph centering", /justify-content\s*:\s*center\s*;/],
160
181
  ["automatic optical centering margin", /margin\s*:\s*auto\s*;/],
161
182
  ["center transform origin", /transform-origin\s*:\s*center\s*;/],
183
+ [
184
+ "symbol optical centering translate",
185
+ /translate\s*:\s*var\(--hd-mac-symbol-offset-x,\s*0px\)\s*var\(--hd-mac-symbol-offset-y,\s*0px\)\s*;/,
186
+ ],
187
+ ["symbol opacity transition", /opacity\s+var\(--hd-mac-motion-standard\)/],
162
188
  ]);
163
189
 
164
190
  const sharedIconButtonRule = stylesSource.match(
@@ -199,6 +225,9 @@ const symbolIconRequirements = [
199
225
  ["SymbolIconButton render element is MacIconButton", /render=\{\s*<MacIconButton\b/],
200
226
  ["SymbolIconButton applies hd-mac-symbol-icon-button class", /joinClasses\("hd-mac-symbol-icon-button", className\)/],
201
227
  ["SymbolIconButton defaults to button type", /type=["']button["']/],
228
+ ["SymbolIconButton supports optical offset prop", /opticalOffset\?: SymbolIconOpticalOffset/],
229
+ ["SymbolIconButton includes default optical offsets", /DEFAULT_SYMBOL_OPTICAL_OFFSETS/],
230
+ ["SymbolIconButton sets symbol offset CSS variable", /"--hd-mac-symbol-offset-x": offsetToCssValue/],
202
231
  ["SymbolIconButton supports className pass-through", /\bclassName,\s*\n\s*style,/],
203
232
  ["SymbolIconButton supports style pass-through after fixed geometry", /\.\.\.style/],
204
233
  ["SymbolIconButton supports tooltip offset", /tooltipSideOffset = 5/],
@@ -10,6 +10,7 @@ type SymbolIconButtonProps = {
10
10
  disabled?: boolean;
11
11
  isLoading?: boolean;
12
12
  iconSizePx?: number;
13
+ opticalOffset?: SymbolIconOpticalOffset;
13
14
  symbolEnabled?: boolean;
14
15
  className?: string;
15
16
  style?: CSSProperties;
@@ -19,10 +20,31 @@ type SymbolIconButtonProps = {
19
20
  const ICON_BUTTON_SIZE = "30px";
20
21
  const ICON_BUTTON_RADIUS = "7px";
21
22
 
23
+ type SymbolIconOpticalOffset = {
24
+ x?: number | string;
25
+ y?: number | string;
26
+ };
27
+
28
+ const DEFAULT_SYMBOL_OPTICAL_OFFSETS: Record<string, SymbolIconOpticalOffset> = {
29
+ "arrow.clockwise": { y: 0.5 },
30
+ "square.and.arrow.up": { y: 0.35 },
31
+ "square.and.arrow.down": { y: 0.35 },
32
+ "folder.badge.plus": { x: -0.65 },
33
+ "chevron.left": { x: 0.6 },
34
+ trash: { y: 0.25 },
35
+ };
36
+
22
37
  function joinClasses(...classes: Array<string | undefined | false>) {
23
38
  return classes.filter(Boolean).join(" ");
24
39
  }
25
40
 
41
+ function offsetToCssValue(value: number | string | undefined) {
42
+ if (typeof value === "number") {
43
+ return `${value}px`;
44
+ }
45
+ return value ?? "0px";
46
+ }
47
+
26
48
  export function SymbolIconButton({
27
49
  label,
28
50
  symbolName,
@@ -31,6 +53,7 @@ export function SymbolIconButton({
31
53
  disabled,
32
54
  isLoading,
33
55
  iconSizePx = 18,
56
+ opticalOffset,
34
57
  symbolEnabled = true,
35
58
  className,
36
59
  style,
@@ -42,6 +65,13 @@ export function SymbolIconButton({
42
65
  enabled: symbolEnabled,
43
66
  });
44
67
  const glyphPx = `${iconSizePx}px`;
68
+ const resolvedOpticalOffset = opticalOffset ?? DEFAULT_SYMBOL_OPTICAL_OFFSETS[symbolName];
69
+ const iconStyle = {
70
+ width: glyphPx,
71
+ height: glyphPx,
72
+ "--hd-mac-symbol-offset-x": offsetToCssValue(resolvedOpticalOffset?.x),
73
+ "--hd-mac-symbol-offset-y": offsetToCssValue(resolvedOpticalOffset?.y),
74
+ } as CSSProperties;
45
75
 
46
76
  return (
47
77
  <MacTooltip.Root>
@@ -70,8 +100,7 @@ export function SymbolIconButton({
70
100
  aria-hidden
71
101
  className="hd-mac-symbol-icon hd-mac-symbol-icon-mask"
72
102
  style={{
73
- width: glyphPx,
74
- height: glyphPx,
103
+ ...iconStyle,
75
104
  color: "currentColor",
76
105
  backgroundColor: "currentColor",
77
106
  WebkitMaskImage: `url("${symbolDataUrl}")`,
@@ -89,8 +118,7 @@ export function SymbolIconButton({
89
118
  aria-hidden
90
119
  className="hd-mac-symbol-icon hd-mac-symbol-icon-fallback"
91
120
  style={{
92
- width: glyphPx,
93
- height: glyphPx,
121
+ ...iconStyle,
94
122
  fontSize: glyphPx,
95
123
  }}
96
124
  >
@@ -28,6 +28,7 @@
28
28
  --hd-mac-motion-standard: 140ms cubic-bezier(0.2, 0.8, 0.2, 1);
29
29
  --hd-mac-motion-surface: 180ms cubic-bezier(0.16, 1, 0.3, 1);
30
30
  --hd-mac-motion-row: 120ms cubic-bezier(0.2, 0.8, 0.2, 1);
31
+ --hd-mac-motion-emphasis: 220ms cubic-bezier(0.16, 1, 0.3, 1);
31
32
  }
32
33
 
33
34
  @media (prefers-color-scheme: dark) {
@@ -76,6 +77,7 @@
76
77
  .hd-mac-avatar,
77
78
  .hd-mac-scroll-thumb,
78
79
  .hd-mac-status-message,
80
+ .hd-mac-symbol-icon-button::before,
79
81
  .hd-mac-symbol-icon-button[data-loading]::after,
80
82
  .hd-mac-window-toolbar,
81
83
  .hd-mac-data-table,
@@ -282,13 +284,41 @@
282
284
  height: 30px;
283
285
  min-height: 30px;
284
286
  border-radius: 7px;
287
+ position: relative;
288
+ isolation: isolate;
289
+ overflow: hidden;
290
+ }
291
+
292
+ .hd-mac-symbol-icon-button::before,
293
+ .hd-mac-symbol-icon-button::after {
294
+ grid-area: 1 / 1;
295
+ pointer-events: none;
285
296
  }
286
297
 
287
298
  .hd-mac-symbol-icon-button::after {
288
299
  grid-area: 1 / 1;
289
300
  }
290
301
 
302
+ .hd-mac-symbol-icon-button::before {
303
+ content: "";
304
+ z-index: 0;
305
+ width: 100%;
306
+ height: 100%;
307
+ border-radius: inherit;
308
+ background:
309
+ radial-gradient(circle at 50% 35%, rgba(255, 255, 255, 0.28), transparent 54%),
310
+ var(--hd-mac-fill-hover);
311
+ opacity: 0;
312
+ transform: scale(0.82);
313
+ transition:
314
+ opacity var(--hd-mac-motion-standard),
315
+ transform var(--hd-mac-motion-emphasis),
316
+ background-color var(--hd-mac-motion-standard);
317
+ }
318
+
291
319
  .hd-mac-symbol-icon {
320
+ position: relative;
321
+ z-index: 1;
292
322
  grid-area: 1 / 1;
293
323
  display: inline-flex;
294
324
  align-items: center;
@@ -297,6 +327,10 @@
297
327
  flex-shrink: 0;
298
328
  margin: auto;
299
329
  transform-origin: center;
330
+ translate: var(--hd-mac-symbol-offset-x, 0px) var(--hd-mac-symbol-offset-y, 0px);
331
+ transition:
332
+ opacity var(--hd-mac-motion-standard),
333
+ color var(--hd-mac-motion-standard);
300
334
  }
301
335
 
302
336
  .hd-mac-symbol-icon-mask {
@@ -324,6 +358,29 @@
324
358
  font: 700 12px/18px var(--hd-mac-font);
325
359
  }
326
360
 
361
+ .hd-mac-symbol-icon-button:hover::before {
362
+ opacity: 1;
363
+ transform: scale(1);
364
+ }
365
+
366
+ .hd-mac-symbol-icon-button:focus-visible::before {
367
+ opacity: 1;
368
+ transform: scale(1);
369
+ }
370
+
371
+ .hd-mac-symbol-icon-button:active::before,
372
+ .hd-mac-symbol-icon-button[data-loading]::before {
373
+ opacity: 1;
374
+ transform: scale(0.92);
375
+ background:
376
+ radial-gradient(circle at 50% 35%, rgba(255, 255, 255, 0.18), transparent 54%),
377
+ var(--hd-mac-fill-pressed);
378
+ }
379
+
380
+ .hd-mac-symbol-icon-button[disabled]::before {
381
+ opacity: 0;
382
+ }
383
+
327
384
  .hd-mac-button[disabled],
328
385
  .hd-mac-select-trigger[disabled],
329
386
  .hd-mac-toggle[disabled],
@@ -979,6 +1036,7 @@
979
1036
 
980
1037
  .hd-mac-symbol-icon-button[data-loading]::after {
981
1038
  content: "";
1039
+ z-index: 2;
982
1040
  width: 15px;
983
1041
  height: 15px;
984
1042
  border: 1.5px solid color-mix(in srgb, currentColor 28%, transparent);