@teamofsilicons/silicon-ui 0.3.0 → 0.4.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.
Files changed (76) hide show
  1. package/components/animated-counter/animated-counter.js +1 -1
  2. package/components/bar-chart/bar-chart.js +1 -1
  3. package/components/billing-toggle/billing-toggle.js +2 -2
  4. package/components/bottom-sheet/bottom-sheet.js +1 -1
  5. package/components/button-group/button-group.module.css +1 -1
  6. package/components/calendar/calendar.js +15 -5
  7. package/components/calendar/calendar.module.css +6 -3
  8. package/components/card/card.js +4 -4
  9. package/components/card-stack/card-stack.js +2 -2
  10. package/components/carousel/carousel.module.css +1 -1
  11. package/components/chip-group/chip-group.js +1 -1
  12. package/components/combobox/combobox.js +5 -1
  13. package/components/command-palette/command-palette.js +18 -11
  14. package/components/confirm-morph/confirm-morph.js +5 -5
  15. package/components/context-menu/context-menu.js +10 -28
  16. package/components/copy-button/copy-button.js +4 -4
  17. package/components/date-picker/date-picker.js +3 -3
  18. package/components/date-picker/date-picker.module.css +1 -1
  19. package/components/date-range-picker/date-range-picker.js +13 -10
  20. package/components/date-range-picker/date-range-picker.module.css +2 -1
  21. package/components/donut-chart/donut-chart.module.css +1 -1
  22. package/components/drawer/drawer.module.css +2 -2
  23. package/components/elastic-slider/elastic-slider.js +2 -2
  24. package/components/expandable-card/expandable-card.js +1 -1
  25. package/components/expanding-button-group/expanding-button-group.module.css +2 -2
  26. package/components/expanding-search/expanding-search.js +3 -3
  27. package/components/file-dropzone/file-dropzone.js +3 -3
  28. package/components/filter-toolbar/filter-toolbar.js +7 -3
  29. package/components/floating-button-group/floating-button-group.module.css +1 -1
  30. package/components/gauge/gauge.js +1 -1
  31. package/components/hover-card/hover-card.js +2 -2
  32. package/components/line-chart/line-chart.js +1 -1
  33. package/components/mention-input/mention-input.js +4 -2
  34. package/components/money-input/money-input.js +6 -3
  35. package/components/money-input/money-input.module.css +1 -1
  36. package/components/morph-select/morph-select.js +7 -5
  37. package/components/multi-select/multi-select.js +4 -2
  38. package/components/nav-list/nav-list.d.ts +2 -2
  39. package/components/nav-list/nav-list.js +6 -13
  40. package/components/number-field/number-field.js +3 -3
  41. package/components/number-field/number-field.module.css +1 -1
  42. package/components/pagination/pagination.module.css +1 -1
  43. package/components/password-field/password-field.module.css +1 -1
  44. package/components/password-strength/password-strength.module.css +1 -1
  45. package/components/phone-input/phone-input.js +9 -7
  46. package/components/radio-group/radio-group.js +4 -1
  47. package/components/radio-group/radio-group.module.css +3 -2
  48. package/components/rich-text-editor/rich-text-editor.js +3 -3
  49. package/components/segmented-control/segmented-control.js +4 -1
  50. package/components/segmented-control/segmented-control.module.css +3 -1
  51. package/components/shortcut-recorder/shortcut-recorder.js +5 -5
  52. package/components/shortcut-recorder/shortcut-recorder.module.css +1 -1
  53. package/components/slot-text/slot-text.js +2 -1
  54. package/components/sparkline/sparkline.js +1 -1
  55. package/components/split-button/split-button.module.css +1 -1
  56. package/components/swipe-actions/swipe-actions.js +2 -2
  57. package/components/switch/switch.js +2 -2
  58. package/components/tabs/tabs.js +7 -3
  59. package/components/tabs/tabs.module.css +3 -1
  60. package/components/text-reveal/text-reveal.js +1 -1
  61. package/components/time-picker/time-picker.js +4 -2
  62. package/components/timeline/timeline.js +4 -4
  63. package/components/toast/toast.js +1 -1
  64. package/components/toast-stack/toast-stack.js +1 -1
  65. package/components/tree-view/tree-view.js +16 -12
  66. package/components/tree-view/tree-view.module.css +4 -2
  67. package/components/usage-meter/usage-meter.js +1 -1
  68. package/components/user-menu/user-menu.js +16 -14
  69. package/lib/fluid-hover.d.ts +83 -0
  70. package/lib/fluid-hover.js +184 -0
  71. package/lib/menu-highlight.d.ts +24 -11
  72. package/lib/menu-highlight.js +29 -28
  73. package/lib/motion-tokens.d.ts +76 -18
  74. package/lib/motion-tokens.js +45 -10
  75. package/package.json +1 -1
  76. package/styles.css +6 -3
@@ -1,43 +1,44 @@
1
1
  "use client";
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { useEffect, useRef, useState } from "react";
3
+ import { useRef } from "react";
4
4
  import { motion, useReducedMotion } from "motion/react";
5
+ import { FluidHoverHighlight, useFluidHover } from "./fluid-hover.js";
5
6
  import { motionTokens } from "./motion-tokens.js";
7
+ const ITEMS = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"]';
6
8
  /**
7
- * One highlight that glides between the items of a Radix menu or listbox for the pointer, and jumps instantly for the keyboard.
8
- * Radix focuses the highlighted item, so the highlight follows focus inside the content. Spread `contentProps` on the content,
9
- * call `reset` when it opens, and render `<MenuHighlight state={highlight} className={styles.highlight} />` as its first child.
10
- * Items mark a destructive tone with `data-tone="danger"`.
9
+ * Fluid hover for a Radix menu or listbox (lib/fluid-hover): the item nearest the pointer is lit, gaps and padding
10
+ * included, a click in a gap picks the lit item, and the keyboard moves the highlight with Radix's focus, without travel.
11
+ * Spread `contentProps` on the content, call `reset` when it opens, and render `<MenuHighlight state={highlight} … />`
12
+ * as its first child. Items mark a destructive tone with `data-tone="danger"`.
11
13
  */
12
- export function useMenuHighlight(itemSelector = '[role="menuitem"], [role="option"]') {
13
- const [highlight, setHighlight] = useState(null);
14
- const pointer = useRef(false);
15
- const clearTimer = useRef(0);
16
- useEffect(() => () => window.clearTimeout(clearTimer.current), []);
17
- function onFocus(event) {
18
- const item = event.target instanceof HTMLElement ? event.target.closest(itemSelector) : null;
19
- window.clearTimeout(clearTimer.current);
20
- if (!item) {
21
- // A short grace period keeps the highlight gliding across separators and item gaps.
22
- clearTimer.current = window.setTimeout(() => setHighlight(null), pointer.current ? 70 : 0);
23
- return;
24
- }
25
- const next = { top: item.offsetTop, height: item.offsetHeight, danger: item.dataset.tone === "danger" };
26
- const glide = pointer.current;
27
- setHighlight(current => ({ ...next, glide: glide && current !== null }));
28
- }
14
+ export function useMenuHighlight(itemSelector = ITEMS) {
15
+ // The content element is taken from its own events, so the hook never competes with the content's ref.
16
+ const content = useRef(null);
17
+ const hover = useFluidHover(content, { items: itemSelector });
18
+ const own = (event) => { content.current = event.currentTarget; };
19
+ const { handlers } = hover;
20
+ const highlight = { rect: hover.rect, glide: hover.glide, session: hover.session, danger: hover.item?.dataset.tone === "danger" };
29
21
  return {
30
22
  highlight,
31
- reset: () => { window.clearTimeout(clearTimer.current); setHighlight(null); },
23
+ reset: hover.reset,
32
24
  contentProps: {
33
- onFocus,
34
- onPointerMoveCapture: () => { pointer.current = true; },
35
- onKeyDownCapture: () => { pointer.current = false; },
25
+ onMouseEnter: (event) => { own(event); handlers.onMouseEnter(); },
26
+ onMouseMove: (event) => { own(event); handlers.onMouseMove(event); },
27
+ onMouseLeave: handlers.onMouseLeave,
28
+ onKeyDownCapture: handlers.onKeyDownCapture,
29
+ onFocus: (event) => { own(event); handlers.onFocus(event); },
30
+ onBlur: handlers.onBlur,
31
+ onClick: (event) => { own(event); handlers.onClick(event); },
36
32
  },
37
33
  };
38
34
  }
39
- /** The gliding highlight itself. Position it absolutely inside the content, under the items. */
35
+ /** The highlight itself: the fluid hover highlight, or a marker that moves by top and height (a current-page marker). */
40
36
  export function MenuHighlight({ state, className }) {
37
+ if (state && "session" in state)
38
+ return _jsx(FluidHoverHighlight, { hover: state, className: className, tone: state.danger ? "danger" : undefined });
39
+ return _jsx(BarMarker, { state: state, className: className });
40
+ }
41
+ function BarMarker({ state, className }) {
41
42
  const reduced = useReducedMotion();
42
- return _jsx(motion.span, { className: className, "data-tone": state?.danger ? "danger" : undefined, "aria-hidden": "true", initial: false, animate: state ? { y: state.top, height: state.height, opacity: 1 } : { opacity: 0 }, transition: { default: state?.glide && !reduced ? motionTokens.spring.snappy : { duration: 0 }, opacity: { duration: reduced ? 0 : .08 } } });
43
+ return _jsx(motion.span, { className: className, "data-tone": state?.danger ? "danger" : undefined, "aria-hidden": "true", initial: false, animate: state ? { y: state.top, height: state.height, opacity: 1 } : { opacity: 0 }, transition: { default: state?.glide && !reduced ? motionTokens.spring.fast : { duration: 0 }, opacity: { duration: reduced ? 0 : motionTokens.duration.fast } } });
43
44
  }
@@ -1,11 +1,14 @@
1
- /** Motion presets are shared by the gallery and future registry consumers. */
2
1
  export declare const motionTokens: {
2
+ /** Tween durations in seconds, for opacity, colour and blur, and for CSS (the same steps as `--duration-*`). */
3
3
  readonly duration: {
4
- readonly instant: 0.12;
5
- readonly fast: 0.16;
6
- readonly exit: 0.18;
7
- readonly standard: 0.24;
8
- readonly considered: 0.48;
4
+ readonly instant: 0.06;
5
+ readonly fast: 0.08;
6
+ readonly exit: 0.12;
7
+ readonly standard: 0.16;
8
+ readonly considered: 0.24;
9
+ /** Motion that tells a story over time rather than answering a click: a chart drawing in, a counter rolling, text
10
+ * revealing, a success ring drawing, a slow image zoom. Never for a hover, press, menu or panel. */
11
+ readonly expressive: 0.48;
9
12
  };
10
13
  readonly ease: {
11
14
  readonly enter: readonly [0.16, 1, 0.3, 1];
@@ -15,30 +18,76 @@ export declare const motionTokens: {
15
18
  readonly inOut: readonly [0.65, 0, 0.35, 1];
16
19
  };
17
20
  readonly spring: {
18
- readonly responsive: {
21
+ /** Hover highlights, presses, toggles, thumbs and small indicators. */
22
+ readonly fast: {
19
23
  readonly type: "spring";
20
- readonly stiffness: 520;
21
- readonly damping: 38;
24
+ readonly visualDuration: 0.05;
25
+ readonly bounce: 0;
26
+ readonly exit: {
27
+ readonly duration: 0.06;
28
+ };
22
29
  };
23
- readonly gentle: {
30
+ /** Menus, popovers, tabs, drawers, height changes: short travel that must land exactly. */
31
+ readonly moderate: {
24
32
  readonly type: "spring";
25
- readonly stiffness: 340;
26
- readonly damping: 34;
33
+ readonly visualDuration: 0.09;
34
+ readonly bounce: 0;
35
+ readonly exit: {
36
+ readonly duration: 0.12;
37
+ };
27
38
  };
28
- /** Presses, toggles, thumbs, and small indicators. Settles fast with a hint of life. */
29
- readonly snappy: {
39
+ /** Dialogs, sheets, shape morphs and large shared-layout moves. A little bounce. */
40
+ readonly slow: {
30
41
  readonly type: "spring";
31
- readonly visualDuration: 0.26;
42
+ readonly visualDuration: 0.15;
32
43
  readonly bounce: 0.12;
44
+ readonly exit: {
45
+ readonly duration: 0.16;
46
+ };
47
+ };
48
+ /** Earlier names, kept so existing code keeps working. Prefer the tier names. */
49
+ readonly snappy: {
50
+ readonly type: "spring";
51
+ readonly visualDuration: 0.05;
52
+ readonly bounce: 0;
53
+ readonly exit: {
54
+ readonly duration: 0.06;
55
+ };
33
56
  };
34
- /** Panels, height changes, and layout shifts. Critically damped, never overshoots. */
35
57
  readonly smooth: {
36
58
  readonly type: "spring";
37
- readonly visualDuration: 0.4;
59
+ readonly visualDuration: 0.09;
38
60
  readonly bounce: 0;
61
+ readonly exit: {
62
+ readonly duration: 0.12;
63
+ };
39
64
  };
40
- /** Shape morphs, shared layout highlights, and width changes that follow new content. */
41
65
  readonly morph: {
66
+ readonly type: "spring";
67
+ readonly visualDuration: 0.15;
68
+ readonly bounce: 0.12;
69
+ readonly exit: {
70
+ readonly duration: 0.16;
71
+ };
72
+ };
73
+ readonly responsive: {
74
+ readonly type: "spring";
75
+ readonly visualDuration: 0.09;
76
+ readonly bounce: 0;
77
+ readonly exit: {
78
+ readonly duration: 0.12;
79
+ };
80
+ };
81
+ readonly gentle: {
82
+ readonly type: "spring";
83
+ readonly visualDuration: 0.09;
84
+ readonly bounce: 0;
85
+ readonly exit: {
86
+ readonly duration: 0.12;
87
+ };
88
+ };
89
+ /** The spring for expressive motion (see duration.expressive): a bar growing, a value settling into place. */
90
+ readonly expressive: {
42
91
  readonly type: "spring";
43
92
  readonly visualDuration: 0.42;
44
93
  readonly bounce: 0.16;
@@ -58,3 +107,12 @@ export declare const motionTokens: {
58
107
  readonly text: 8;
59
108
  };
60
109
  };
110
+ /**
111
+ * How long (ms) a deferred-unmount timer waits for a tier's exit before forcing it: the exit plus a safety margin, for
112
+ * a throttled background tab that never finishes the animation.
113
+ */
114
+ export declare const exitFallbackMs: (tier: {
115
+ exit: {
116
+ duration: number;
117
+ };
118
+ }) => number;
@@ -1,6 +1,30 @@
1
- /** Motion presets are shared by the gallery and future registry consumers. */
1
+ /**
2
+ * Motion tokens, shared by every component and by apps (`@teamofsilicons/silicon-ui/motion-tokens`).
3
+ *
4
+ * Interface motion runs on three spring tiers. Each tier is the ENTER transition (critically damped, except slow, which
5
+ * keeps a little bounce), and its `.exit` is the matching EXIT: a plain tween one tier quicker, so a dismissal reads as
6
+ * crisp and final instead of replaying the entrance in reverse. The bigger the thing that moves, the slower the tier.
7
+ * Never hand-write a duration: reach for a tier.
8
+ *
9
+ * transition={motionTokens.spring.fast} // enter
10
+ * exit={{ opacity: 0, transition: motionTokens.spring.fast.exit }} // leave
11
+ *
12
+ * The tier values and the exit rule are adapted from Fluid Functionalism by Micka Touillaud (MIT),
13
+ * https://github.com/mickadesign/fluid-functionalism.
14
+ */
15
+ // Written as visualDuration (the form the components read) with values that arrive (95% of the way) at the same moment as
16
+ // FF's settling durations of 0.08, 0.16 and 0.24s: 42, 83 and 109ms.
17
+ const fast = { type: "spring", visualDuration: 0.05, bounce: 0, exit: { duration: 0.06 } };
18
+ const moderate = { type: "spring", visualDuration: 0.09, bounce: 0, exit: { duration: 0.12 } };
19
+ const slow = { type: "spring", visualDuration: 0.15, bounce: 0.12, exit: { duration: 0.16 } };
2
20
  export const motionTokens = {
3
- duration: { instant: 0.12, fast: 0.16, exit: 0.18, standard: 0.24, considered: 0.48 },
21
+ /** Tween durations in seconds, for opacity, colour and blur, and for CSS (the same steps as `--duration-*`). */
22
+ duration: {
23
+ instant: 0.06, fast: 0.08, exit: 0.12, standard: 0.16, considered: 0.24,
24
+ /** Motion that tells a story over time rather than answering a click: a chart drawing in, a counter rolling, text
25
+ * revealing, a success ring drawing, a slow image zoom. Never for a hover, press, menu or panel. */
26
+ expressive: 0.48,
27
+ },
4
28
  ease: {
5
29
  enter: [0.16, 1, 0.3, 1],
6
30
  exit: [0.7, 0, 0.84, 0],
@@ -9,17 +33,28 @@ export const motionTokens = {
9
33
  inOut: [0.65, 0, 0.35, 1],
10
34
  },
11
35
  spring: {
12
- responsive: { type: "spring", stiffness: 520, damping: 38 },
13
- gentle: { type: "spring", stiffness: 340, damping: 34 },
14
- /** Presses, toggles, thumbs, and small indicators. Settles fast with a hint of life. */
15
- snappy: { type: "spring", visualDuration: 0.26, bounce: 0.12 },
16
- /** Panels, height changes, and layout shifts. Critically damped, never overshoots. */
17
- smooth: { type: "spring", visualDuration: 0.4, bounce: 0 },
18
- /** Shape morphs, shared layout highlights, and width changes that follow new content. */
19
- morph: { type: "spring", visualDuration: 0.42, bounce: 0.16 },
36
+ /** Hover highlights, presses, toggles, thumbs and small indicators. */
37
+ fast,
38
+ /** Menus, popovers, tabs, drawers, height changes: short travel that must land exactly. */
39
+ moderate,
40
+ /** Dialogs, sheets, shape morphs and large shared-layout moves. A little bounce. */
41
+ slow,
42
+ /** Earlier names, kept so existing code keeps working. Prefer the tier names. */
43
+ snappy: fast,
44
+ smooth: moderate,
45
+ morph: slow,
46
+ responsive: moderate,
47
+ gentle: moderate,
48
+ /** The spring for expressive motion (see duration.expressive): a bar growing, a value settling into place. */
49
+ expressive: { type: "spring", visualDuration: 0.42, bounce: 0.16 },
20
50
  },
21
51
  /** Stagger steps in seconds. Keep total stagger under roughly 0.4s. */
22
52
  stagger: { char: 0.016, word: 0.04, line: 0.08, item: 0.035 },
23
53
  /** Blur radii in px for text and content crossfades. Keep blur small and brief. */
24
54
  blur: { subtle: 2, soft: 4, text: 8 },
25
55
  };
56
+ /**
57
+ * How long (ms) a deferred-unmount timer waits for a tier's exit before forcing it: the exit plus a safety margin, for
58
+ * a throttled background tab that never finishes the animation.
59
+ */
60
+ export const exitFallbackMs = (tier) => Math.round(tier.exit.duration * 1000) + 100;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamofsilicons/silicon-ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "React components with calm motion, on the Silicon design tokens. By Team of Silicons.",
5
5
  "license": "MIT",
6
6
  "author": "Team of Silicons",
package/styles.css CHANGED
@@ -110,9 +110,12 @@
110
110
  --ease-standard: cubic-bezier(.22,1,.36,1);
111
111
  --ease-exit: cubic-bezier(.7,0,.84,0);
112
112
  --ease-in-out: cubic-bezier(.65,0,.35,1);
113
- /* Spring with a gentle settle (bounce .15). Pair with --duration-spring for transform transitions. */
114
- --ease-spring: linear(0, 0.0258, 0.09, 0.1763, 0.2732, 0.3724, 0.4683, 0.5573, 0.6376, 0.7082, 0.7689, 0.8202, 0.8628, 0.8976, 0.9256, 0.9476, 0.9648, 0.9778, 0.9875, 0.9945, 0.9994, 1.0026, 1.0047, 1.0058, 1.0062, 1.0062, 1.0059, 1.0055, 1.0049, 1.0043, 1.0036, 1.0031, 1.0025, 1.002, 1.0016, 1.0013, 1);
115
- --duration-instant: 120ms; --duration-fast: 160ms; --duration-standard: 240ms; --duration-considered: 480ms; --duration-spring: 580ms;
113
+ /* The slow spring tier (bounce .12, settles in 240ms), sampled for CSS. Pair with --duration-spring for transforms. */
114
+ --ease-spring: linear(0, 0.0515, 0.1675, 0.3072, 0.4464, 0.5725, 0.6797, 0.7668, 0.8352, 0.887, 0.9253, 0.9527, 0.9717, 0.9846, 0.9928, 0.9979, 1.0008, 1.0023, 1.0029, 1.0029, 1.0027, 1.0023, 1.0019, 1.0015, 1);
115
+ /* The same steps as motionTokens.duration: fast is a hover or press, standard a menu or panel, considered a dialog. */
116
+ --duration-instant: 60ms; --duration-fast: 80ms; --duration-standard: 160ms; --duration-considered: 240ms; --duration-spring: 240ms;
117
+ /* Motion that tells a story (a chart drawing in, a slow image zoom), never a hover, press or panel. */
118
+ --duration-expressive: 480ms;
116
119
  /* Stagger steps (the same values as motionTokens.stagger). Keep total stagger under roughly 400ms. */
117
120
  --stagger-item: 35ms;
118
121
  /* Stacking order, by role. Menus and listboxes sit above popovers and dialogs because they open from inside them.