beez-ui 2.3.1 → 2.5.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.
@@ -0,0 +1,49 @@
1
+ import { ARC_BLUR, ARC_DURATION, ARC_EASE, ARC_SPRING_SNAPPY } from "./arc-tokens.js";
2
+ /** Resting pose every swapped element settles into. */
3
+ export const ARC_SWAP_REST = {
4
+ opacity: 1,
5
+ x: 0,
6
+ y: 0,
7
+ scale: 1,
8
+ filter: "blur(0px)"
9
+ };
10
+ /** Outgoing elements leave faster than incoming ones arrive. */
11
+ export const ARC_SWAP_EXIT_TRANSITION = {
12
+ duration: ARC_DURATION.fast,
13
+ ease: [...ARC_EASE.standard]
14
+ };
15
+ /** An incoming icon grows out of a slight blur. */
16
+ export const ARC_ICON_ENTER = {
17
+ opacity: 0,
18
+ scale: .6,
19
+ filter: `blur(${ARC_BLUR.subtle}px)`
20
+ };
21
+ /** An outgoing icon shrinks back into the same blur. */
22
+ export const ARC_ICON_EXIT = {
23
+ ...ARC_ICON_ENTER,
24
+ transition: ARC_SWAP_EXIT_TRANSITION
25
+ };
26
+ /** Scale rides the spring; opacity and blur tween so blur never overshoots below zero. */
27
+ export const ARC_ICON_TRANSITION = {
28
+ ...ARC_SPRING_SNAPPY,
29
+ opacity: {
30
+ duration: ARC_DURATION.fast,
31
+ ease: [...ARC_EASE.enter]
32
+ },
33
+ filter: {
34
+ duration: ARC_DURATION.fast,
35
+ ease: [...ARC_EASE.enter]
36
+ }
37
+ };
38
+ /** With reduced motion, every swap is an instant fade. */
39
+ export const ARC_REDUCED_TRANSITION = { duration: ARC_DURATION.instant };
40
+ /** Reduced-motion entrance: the resting pose, only faded out. */
41
+ export const ARC_REDUCED_ENTER = {
42
+ ...ARC_SWAP_REST,
43
+ opacity: 0
44
+ };
45
+ /** Reduced-motion exit: an instant fade. */
46
+ export const ARC_REDUCED_EXIT = {
47
+ opacity: 0,
48
+ transition: ARC_REDUCED_TRANSITION
49
+ };
@@ -6,6 +6,7 @@
6
6
  export declare const ARC_DURATION: {
7
7
  readonly instant: 0.12;
8
8
  readonly fast: 0.16;
9
+ readonly exit: 0.18;
9
10
  readonly standard: 0.24;
10
11
  };
11
12
  /** Cubic-bezier curves for entrances and color or opacity tweens. */
@@ -19,12 +20,22 @@ export declare const ARC_SPRING_SNAPPY: {
19
20
  readonly visualDuration: 0.26;
20
21
  readonly bounce: 0.12;
21
22
  };
23
+ /** Panels, height changes and layout shifts: critically damped, never overshoots. */
24
+ export declare const ARC_SPRING_SMOOTH: {
25
+ readonly type: "spring";
26
+ readonly visualDuration: 0.4;
27
+ readonly bounce: 0;
28
+ };
22
29
  /** Shape morphs and width changes that follow new content. */
23
30
  export declare const ARC_SPRING_MORPH: {
24
31
  readonly type: "spring";
25
32
  readonly visualDuration: 0.42;
26
33
  readonly bounce: 0.16;
27
34
  };
35
+ /** Stagger steps in seconds; keep a whole staggered entrance under roughly 0.4s. */
36
+ export declare const ARC_STAGGER: {
37
+ readonly char: 0.016;
38
+ };
28
39
  /** Blur radii in pixels for brief content crossfades. */
29
40
  export declare const ARC_BLUR: {
30
41
  readonly subtle: 2;
@@ -6,6 +6,7 @@
6
6
  export const ARC_DURATION = {
7
7
  instant: .12,
8
8
  fast: .16,
9
+ exit: .18,
9
10
  standard: .24
10
11
  };
11
12
  /** Cubic-bezier curves for entrances and color or opacity tweens. */
@@ -29,12 +30,20 @@ export const ARC_SPRING_SNAPPY = {
29
30
  visualDuration: .26,
30
31
  bounce: .12
31
32
  };
33
+ /** Panels, height changes and layout shifts: critically damped, never overshoots. */
34
+ export const ARC_SPRING_SMOOTH = {
35
+ type: "spring",
36
+ visualDuration: .4,
37
+ bounce: 0
38
+ };
32
39
  /** Shape morphs and width changes that follow new content. */
33
40
  export const ARC_SPRING_MORPH = {
34
41
  type: "spring",
35
42
  visualDuration: .42,
36
43
  bounce: .16
37
44
  };
45
+ /** Stagger steps in seconds; keep a whole staggered entrance under roughly 0.4s. */
46
+ export const ARC_STAGGER = { char: .016 };
38
47
  /** Blur radii in pixels for brief content crossfades. */
39
48
  export const ARC_BLUR = {
40
49
  subtle: 2,
@@ -0,0 +1,13 @@
1
+ import type { RefObject } from "react";
2
+ import type { MotionValue } from "motion/react";
3
+ /**
4
+ * Springs a label slot to the natural width of its content when the label changes, so new text never snaps the
5
+ * layout. Other resizes (a late web font, a parent reflow) jump straight to the new width, so nothing wobbles on
6
+ * first paint. While it springs, the slot carries `data-morphing` so it can clip overflowing glyphs.
7
+ *
8
+ * @param contentRef - Content measured by a `ResizeObserver`; its parent element is the slot that gets the width.
9
+ * @param labelKey - Identity of the current label: a change opens the window in which resizes spring.
10
+ * @param reduceMotion - Whether the user prefers reduced motion, which makes every resize jump.
11
+ * @returns Width to bind to the slot's `style.width`.
12
+ */
13
+ export declare function useMorphWidth(contentRef: RefObject<HTMLElement | null>, labelKey: string, reduceMotion: boolean): MotionValue<number | "auto">;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Label width spring adapted from Arc (https://uiarc.dev), Copyright (c) 2026 Elia Kuratli, MIT License.
3
+ * The full license text ships in THIRD_PARTY_NOTICES.md. Internal: shared by `Button` and `ActionButton`.
4
+ */
5
+ import { useEffect, useLayoutEffect, useRef } from "react";
6
+ import { animate, useMotionValue } from "motion/react";
7
+ import { BUTTON_MORPH_WINDOW_MS } from "../constants/button.js";
8
+ import { ARC_SPRING_MORPH } from "./arc-tokens.js";
9
+ /**
10
+ * Springs a label slot to the natural width of its content when the label changes, so new text never snaps the
11
+ * layout. Other resizes (a late web font, a parent reflow) jump straight to the new width, so nothing wobbles on
12
+ * first paint. While it springs, the slot carries `data-morphing` so it can clip overflowing glyphs.
13
+ *
14
+ * @param contentRef - Content measured by a `ResizeObserver`; its parent element is the slot that gets the width.
15
+ * @param labelKey - Identity of the current label: a change opens the window in which resizes spring.
16
+ * @param reduceMotion - Whether the user prefers reduced motion, which makes every resize jump.
17
+ * @returns Width to bind to the slot's `style.width`.
18
+ */
19
+ export function useMorphWidth(contentRef, labelKey, reduceMotion) {
20
+ const width = useMotionValue("auto");
21
+ const lastLabelKey = useRef(labelKey);
22
+ const morphDeadline = useRef(0);
23
+ useLayoutEffect(() => {
24
+ if (lastLabelKey.current === labelKey) return;
25
+ lastLabelKey.current = labelKey;
26
+ morphDeadline.current = performance.now() + BUTTON_MORPH_WINDOW_MS;
27
+ }, [labelKey]);
28
+ useEffect(() => {
29
+ const content = contentRef.current;
30
+ const slot = content?.parentElement;
31
+ if (!content || !slot || typeof ResizeObserver === "undefined") return;
32
+ let hasMeasured = false;
33
+ const observer = new ResizeObserver(([entry]) => {
34
+ if (!entry) return;
35
+ const nextWidth = entry.contentRect.width;
36
+ const shouldJump = !nextWidth || !hasMeasured || reduceMotion || performance.now() > morphDeadline.current;
37
+ if (shouldJump) {
38
+ hasMeasured = nextWidth > 0;
39
+ width.jump(nextWidth || "auto");
40
+ delete slot.dataset.morphing;
41
+ return;
42
+ }
43
+ slot.dataset.morphing = "";
44
+ animate(width, nextWidth, {
45
+ ...ARC_SPRING_MORPH,
46
+ onComplete: () => {
47
+ delete slot.dataset.morphing;
48
+ }
49
+ });
50
+ });
51
+ observer.observe(content);
52
+ return () => observer.disconnect();
53
+ }, [
54
+ contentRef,
55
+ reduceMotion,
56
+ width
57
+ ]);
58
+ return width;
59
+ }