@bug-on/md3-react 3.0.1 → 3.0.2

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.
@@ -1,5 +1,5 @@
1
1
  import { cva } from "class-variance-authority";
2
- import { AnimatePresence, domMax, LazyMotion, m } from "motion/react";
2
+ import { AnimatePresence, domMax, LazyMotion, m, type Transition } from "motion/react";
3
3
  import * as React from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  import { cn } from "../lib/utils";
@@ -15,7 +15,7 @@ import { TouchTarget } from "./shared/touch-target";
15
15
  // Types & Constants
16
16
  // ─────────────────────────────────────────────────────────────────────────────
17
17
 
18
- export type NavigationRailVariant = "collapsed" | "expanded" | "modal";
18
+ export type NavigationRailVariant = "collapsed" | "expanded" | "modal" | "xr";
19
19
  export type NavigationRailLabelVisibility = "labeled" | "auto" | "unlabeled";
20
20
 
21
21
  export interface NavigationRailItemProps {
@@ -34,11 +34,12 @@ export interface NavigationRailProps {
34
34
  labelVisibility?: NavigationRailLabelVisibility;
35
35
  header?: React.ReactNode;
36
36
  fab?: React.ReactNode;
37
+ fabPlacement?: "contained" | "spatialized";
37
38
  footer?: React.ReactNode;
38
39
  narrow?: boolean;
39
40
  open?: boolean;
40
- xr?: boolean | "contained" | "spatialized";
41
41
  onClose?: () => void;
42
+ activeIndicatorTransition?: Transition;
42
43
  children: React.ReactNode;
43
44
  className?: string;
44
45
  style?: React.CSSProperties;
@@ -47,8 +48,8 @@ export interface NavigationRailProps {
47
48
  const NavigationRailContext = React.createContext<{
48
49
  variant: NavigationRailVariant;
49
50
  labelVisibility: NavigationRailLabelVisibility;
50
- xr: boolean;
51
- }>({ variant: "collapsed", labelVisibility: "labeled", xr: false });
51
+ activeIndicatorTransition?: Transition;
52
+ }>({ variant: "collapsed", labelVisibility: "labeled" });
52
53
 
53
54
  const MD3_MODAL_TRANSITION = {
54
55
  type: "tween",
@@ -65,19 +66,16 @@ const railContainerVariants = cva(
65
66
  {
66
67
  variants: {
67
68
  variant: {
68
- collapsed: "items-center",
69
- expanded: "items-start",
69
+ collapsed: "items-center h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
70
+ expanded: "items-start h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
70
71
  modal:
71
- "bg-m3-surface shadow-lg rounded-r-[var(--m3-shape-corner-large)]",
72
+ "bg-m3-surface shadow-lg rounded-r-[var(--m3-shape-corner-large)] h-full pt-11 pb-4",
73
+ xr: "h-fit py-5 rounded-[48px] shadow-xl bg-m3-surface border border-white/5",
72
74
  },
73
75
  narrow: {
74
76
  true: "w-20",
75
77
  false: "w-24",
76
78
  },
77
- xr: {
78
- true: "h-fit py-5 rounded-[48px] shadow-xl bg-m3-surface border border-white/5",
79
- false: "h-full pt-11 pb-4 shadow-none bg-m3-surface rounded-none",
80
- },
81
79
  },
82
80
  compoundVariants: [
83
81
  { variant: "expanded", className: "min-w-[13.75rem] max-w-[22.5rem]" },
@@ -86,7 +84,6 @@ const railContainerVariants = cva(
86
84
  defaultVariants: {
87
85
  variant: "collapsed",
88
86
  narrow: false,
89
- xr: false,
90
87
  },
91
88
  },
92
89
  );
@@ -136,6 +133,8 @@ interface ActivePillProps {
136
133
  }
137
134
 
138
135
  function ActivePill({ layoutId, disableInitial = false }: ActivePillProps) {
136
+ const { activeIndicatorTransition } = React.useContext(NavigationRailContext);
137
+
139
138
  return (
140
139
  <m.div
141
140
  layoutId={layoutId}
@@ -144,7 +143,7 @@ function ActivePill({ layoutId, disableInitial = false }: ActivePillProps) {
144
143
  initial={disableInitial ? false : { opacity: 0, scale: 0.5 }}
145
144
  animate={{ opacity: 1, scale: 1 }}
146
145
  exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.15 } }}
147
- transition={SPRING_TRANSITION_EXPRESSIVE}
146
+ transition={activeIndicatorTransition || SPRING_TRANSITION_EXPRESSIVE}
148
147
  />
149
148
  );
150
149
  }
@@ -431,11 +430,12 @@ const NavigationRailComponent = React.forwardRef<
431
430
  labelVisibility = "labeled",
432
431
  header,
433
432
  fab,
433
+ fabPlacement = "contained",
434
434
  footer,
435
435
  narrow = false,
436
436
  open = false,
437
- xr = false,
438
437
  onClose,
438
+ activeIndicatorTransition,
439
439
  children,
440
440
  className,
441
441
  style,
@@ -443,9 +443,8 @@ const NavigationRailComponent = React.forwardRef<
443
443
  ref,
444
444
  ) => {
445
445
  const isModal = variant === "modal";
446
- const isXr = xr === true || xr === "contained" || xr === "spatialized";
447
- const xrMode = xr === "spatialized" ? "spatialized" : "contained";
448
- const isSpatial = isXr && xrMode === "spatialized";
446
+ const isXr = variant === "xr";
447
+ const isSpatial = isXr && fabPlacement === "spatialized";
449
448
  const applyAnimation = !isXr || !isSpatial;
450
449
 
451
450
  const navRef = React.useRef<HTMLElement>(null);
@@ -461,13 +460,13 @@ const NavigationRailComponent = React.forwardRef<
461
460
  );
462
461
 
463
462
  const navBaseClasses = cn(
464
- railContainerVariants({ variant, narrow, xr: isXr }),
463
+ railContainerVariants({ variant, narrow }),
465
464
  );
466
465
  const modalPositioning = isModal ? "fixed left-0 top-0 z-[100]" : "";
467
466
 
468
467
  const navHeaderSpacing = (() => {
469
468
  if (!isXr) return "mb-6 min-h-10";
470
- if (xrMode === "contained") return fab ? "mb-10" : "mb-5";
469
+ if (fabPlacement === "contained") return fab ? "mb-10" : "mb-5";
471
470
  return "mb-5";
472
471
  })();
473
472
 
@@ -564,7 +563,7 @@ const NavigationRailComponent = React.forwardRef<
564
563
 
565
564
  const finalNavElement = isSpatial ? spatialWrapper : navElement;
566
565
 
567
- const contextValue = { variant, labelVisibility, xr: isXr };
566
+ const contextValue = { variant, labelVisibility, activeIndicatorTransition };
568
567
 
569
568
  if (isModal) {
570
569
  if (typeof document === "undefined") return null;
@@ -32,6 +32,8 @@ export interface ScrollAreaProps
32
32
  scrollHideDelay?: number;
33
33
  /** Extra classes applied to the inner viewport element. */
34
34
  viewportClassName?: string;
35
+ /** Ref to the scrolling viewport element. */
36
+ viewportRef?: React.Ref<HTMLDivElement>;
35
37
  }
36
38
 
37
39
  // ─── Root ─────────────────────────────────────────────────────────────────────
@@ -47,6 +49,7 @@ const ScrollArea = React.forwardRef<
47
49
  type = "hover",
48
50
  orientation = "vertical",
49
51
  scrollHideDelay = 600,
52
+ viewportRef,
50
53
  ...props
51
54
  },
52
55
  ref,
@@ -62,6 +65,7 @@ const ScrollArea = React.forwardRef<
62
65
  {...props}
63
66
  >
64
67
  <RadixScrollArea.Viewport
68
+ ref={viewportRef}
65
69
  className={cn(
66
70
  "h-full w-full flex-1 min-h-0 min-w-0 rounded-[inherit]",
67
71
  "outline-none focus-visible:ring-2 focus-visible:ring-m3-primary focus-visible:ring-offset-1",
@@ -57,7 +57,7 @@ export const SPRING_TRANSITION: Transition = {
57
57
  */
58
58
  export const SPRING_TRANSITION_EXPRESSIVE: Transition = {
59
59
  type: "spring",
60
- bounce: 0.35,
60
+ bounce: 0.45,
61
61
  duration: 0.4,
62
62
  } as const;
63
63
 
package/test_output.txt DELETED
@@ -1,164 +0,0 @@
1
-
2
- > @bug-on/md3-react@2.0.3 test
3
- > vitest run src/ui/menu/menu.test.tsx
4
-
5
-
6
- RUN v4.1.0 /Users/stark/Documents/GitHub/bug-on-md3-expressive/packages/react
7
-
8
- ❯ src/ui/menu/menu.test.tsx (28 tests | 8 failed) 16014ms
9
- ✓ renders children when open 277ms
10
- × MenuItem applies correct shape class for each itemPosition 82ms
11
- ✓ MenuItem shows check icon when selected 83ms
12
- ✓ MenuItem applies disabled state — opacity class and aria-disabled 66ms
13
- × MenuGroup auto-injects correct itemPosition based on child index 56ms
14
- × MenuDivider renders with correct role and classes 51ms
15
- × Standard variant applies surface-container-low on MenuGroup 49ms
16
- × Vibrant variant applies tertiary-container on MenuGroup 49ms
17
- ✓ Keyboard ArrowDown: menu opens via click 91ms
18
- ✓ Keyboard Escape closes menu 77ms
19
- ✓ renders children directly without a trigger 4ms
20
- ✓ gap separatorStyle renders no divider elements between groups 3ms
21
- ✓ divider separatorStyle inserts an hr between each pair of groups 5ms
22
- ✓ VerticalMenuDivider renders as hr with correct classes 2ms
23
- ✓ auto-injects index and count props into group children for shape morphing 3ms
24
- ✓ standard colorVariant gap: root is transparent, MenuGroup has surface-container-low 3ms
25
- ✓ vibrant colorVariant gap: root is transparent, MenuGroup has tertiary-container 2ms
26
- ✓ standard colorVariant divider: VerticalMenuContent has surface-container-low 2ms
27
- ✓ VerticalMenu root has role=menu and aria-orientation=vertical 2ms
28
- ✓ ArrowDown key moves focus to next menuitem 14ms
29
- ✓ ArrowUp from first item wraps to last item 9ms
30
- ✓ MenuItem inside VerticalMenu shows check icon when selected=true 4ms
31
- ✓ renders trigger item correctly 35ms
32
- × opens after hoverOpenDelay (default 200ms) 5009ms
33
- × closes after hoverCloseDelay (default 300ms) 5008ms
34
- ✓ useMenuContext returns default values when used outside Provider 4ms
35
- × MenuGroup updates state on hover for shape morphing 5003ms
36
- ✓ FAST_SPATIAL_SPRING has correct spring parameters 0ms
37
-
38
- ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 8 ⎯⎯⎯⎯⎯⎯⎯
39
-
40
- FAIL src/ui/menu/menu.test.tsx > Menu > MenuItem applies correct shape class for each itemPosition
41
- AssertionError: expected 'relative flex w-full cursor-pointer s…' to contain 'rounded-t-[12px]'
42
-
43
- Expected: "rounded-t-[12px]"
44
- Received: "relative flex w-full cursor-pointer select-none items-center outline-none min-h-12 min-w-28 max-w-70 px-3 rounded-none transition-[border-radius,background-color] duration-150 ease-in text-m3-on-surface hover:bg-m3-on-surface/8 focus:bg-m3-on-surface/12 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-m3-primary"
45
-
46
- ❯ src/ui/menu/menu.test.tsx:90:29
47
- 88|
48
- 89| // Shape classes based on ITEM_SHAPE_CLASSES token
49
- 90| expect(leading.className).toContain("rounded-t-[12px]");
50
- | ^
51
- 91| expect(leading.className).toContain("rounded-b-[4px]");
52
- 92| expect(middle.className).toContain("rounded-[4px]");
53
-
54
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/8]⎯
55
-
56
- FAIL src/ui/menu/menu.test.tsx > Menu > MenuGroup auto-injects correct itemPosition based on child index
57
- AssertionError: expected 'relative flex w-full cursor-pointer s…' to contain 'rounded-t-[12px]'
58
-
59
- Expected: "rounded-t-[12px]"
60
- Received: "relative flex w-full cursor-pointer select-none items-center outline-none min-h-12 min-w-28 max-w-70 px-3 rounded-none transition-[border-radius,background-color] duration-150 ease-in text-m3-on-surface hover:bg-m3-on-surface/8 focus:bg-m3-on-surface/12 focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-m3-primary"
61
-
62
- ❯ src/ui/menu/menu.test.tsx:146:27
63
- 144|
64
- 145| // First item should be "leading" shape: rounded-t-[12px] rounded-b-…
65
- 146| expect(first.className).toContain("rounded-t-[12px]");
66
- | ^
67
- 147| // Last item should be "trailing" shape: rounded-t-[4px] rounded-b-[…
68
- 148| expect(last.className).toContain("rounded-b-[12px]");
69
-
70
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/8]⎯
71
-
72
- FAIL src/ui/menu/menu.test.tsx > Menu > MenuDivider renders with correct role and classes
73
- AssertionError: expected 'my-2 mx-0 h-px border-0 bg-m3-outline…' to contain 'mx-3'
74
-
75
- Expected: "mx-3"
76
- Received: "my-2 mx-0 h-px border-0 bg-m3-outline-variant"
77
-
78
- ❯ src/ui/menu/menu.test.tsx:167:29
79
- 165| const divider = screen.getByTestId("divider");
80
- 166| expect(divider.getAttribute("role")).toBe("separator");
81
- 167| expect(divider.className).toContain("mx-3");
82
- | ^
83
- 168| expect(divider.className).toContain("bg-m3-outline-variant");
84
- 169| });
85
-
86
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/8]⎯
87
-
88
- FAIL src/ui/menu/menu.test.tsx > Menu > Standard variant applies surface-container-low on MenuGroup
89
- AssertionError: expected 'relative overflow-hidden py-1 bg-tran…' to contain 'bg-m3-surface-container-low'
90
-
91
- Expected: "bg-m3-surface-container-low"
92
- Received: "relative overflow-hidden py-1 bg-transparent"
93
-
94
- ❯ src/ui/menu/menu.test.tsx:186:27
95
- 184| );
96
- 185| const group = screen.getByTestId("group-standard");
97
- 186| expect(group.className).toContain("bg-m3-surface-container-low");
98
- | ^
99
- 187| });
100
- 188|
101
-
102
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/8]⎯
103
-
104
- FAIL src/ui/menu/menu.test.tsx > Menu > Vibrant variant applies tertiary-container on MenuGroup
105
- AssertionError: expected 'relative overflow-hidden py-1 bg-tran…' to contain 'bg-m3-tertiary-container'
106
-
107
- Expected: "bg-m3-tertiary-container"
108
- Received: "relative overflow-hidden py-1 bg-transparent"
109
-
110
- ❯ src/ui/menu/menu.test.tsx:204:27
111
- 202| );
112
- 203| const group = screen.getByTestId("group-vibrant");
113
- 204| expect(group.className).toContain("bg-m3-tertiary-container");
114
- | ^
115
- 205| });
116
- 206|
117
-
118
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/8]⎯
119
-
120
- FAIL src/ui/menu/menu.test.tsx > SubMenu > opens after hoverOpenDelay (default 200ms)
121
- Error: Test timed out in 5000ms.
122
- If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
123
- ❯ src/ui/menu/menu.test.tsx:513:2
124
- 511|
125
- 512| // 2. SubMenu opens after hover delay
126
- 513| it("opens after hoverOpenDelay (default 200ms)", async () => {
127
- | ^
128
- 514| vi.useFakeTimers();
129
- 515| const user = userEvent.setup({ delay: null });
130
-
131
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/8]⎯
132
-
133
- FAIL src/ui/menu/menu.test.tsx > SubMenu > closes after hoverCloseDelay (default 300ms)
134
- Error: Test timed out in 5000ms.
135
- If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
136
- ❯ src/ui/menu/menu.test.tsx:553:2
137
- 551|
138
- 552| // 3. SubMenu closes after hover close delay
139
- 553| it("closes after hoverCloseDelay (default 300ms)", async () => {
140
- | ^
141
- 554| vi.useFakeTimers();
142
- 555| const user = userEvent.setup({ delay: null });
143
-
144
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[7/8]⎯
145
-
146
- FAIL src/ui/menu/menu.test.tsx > Menu Internals > MenuGroup updates state on hover for shape morphing
147
- Error: Test timed out in 5000ms.
148
- If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
149
- ❯ src/ui/menu/menu.test.tsx:616:2
150
- 614|
151
- 615| // 2. MenuGroup shape morphing triggers on hover
152
- 616| it("MenuGroup updates state on hover for shape morphing", async () =>…
153
- | ^
154
- 617| const user = userEvent.setup();
155
- 618| render(
156
-
157
- ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[8/8]⎯
158
-
159
-
160
- Test Files 1 failed (1)
161
- Tests 8 failed | 20 passed (28)
162
- Start at 09:33:33
163
- Duration 17.69s (transform 152ms, setup 97ms, import 567ms, tests 16.01s, environment 805ms)
164
-
@@ -1,5 +0,0 @@
1
- npm error Missing script: "test"
2
- npm error
3
- npm error To see a list of scripts, run:
4
- npm error npm run
5
- npm error A complete log of this run can be found in: /Users/stark/.npm/_logs/2026-04-20T02_34_50_686Z-debug-0.log