@tinybigui/react 0.1.1 → 0.3.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/dist/index.cjs CHANGED
@@ -4,15 +4,49 @@ var clsx = require('clsx');
4
4
  var tailwindMerge = require('tailwind-merge');
5
5
  var materialColorUtilities = require('@material/material-color-utilities');
6
6
  var react = require('react');
7
- var reactAria = require('react-aria');
8
- var utils = require('@react-aria/utils');
9
7
  var jsxRuntime = require('react/jsx-runtime');
10
8
  var classVarianceAuthority = require('class-variance-authority');
9
+ var reactAria = require('react-aria');
10
+ var utils = require('@react-aria/utils');
11
11
  var reactStately = require('react-stately');
12
+ var reactAriaComponents = require('react-aria-components');
13
+ var reactDom = require('react-dom');
12
14
 
13
15
  // src/utils/cn.ts
16
+ var twMerge = tailwindMerge.extendTailwindMerge({
17
+ extend: {
18
+ classGroups: {
19
+ "font-size": [
20
+ {
21
+ text: [
22
+ // MD3 Display scale
23
+ "display-large",
24
+ "display-medium",
25
+ "display-small",
26
+ // MD3 Headline scale
27
+ "headline-large",
28
+ "headline-medium",
29
+ "headline-small",
30
+ // MD3 Title scale
31
+ "title-large",
32
+ "title-medium",
33
+ "title-small",
34
+ // MD3 Body scale
35
+ "body-large",
36
+ "body-medium",
37
+ "body-small",
38
+ // MD3 Label scale
39
+ "label-large",
40
+ "label-medium",
41
+ "label-small"
42
+ ]
43
+ }
44
+ ]
45
+ }
46
+ }
47
+ });
14
48
  function cn(...inputs) {
15
- return tailwindMerge.twMerge(clsx.clsx(inputs));
49
+ return twMerge(clsx.clsx(inputs));
16
50
  }
17
51
  function getColorValue(variable, element = document.documentElement) {
18
52
  const varName = variable.startsWith("--") ? variable : `--${variable}`;
@@ -131,6 +165,162 @@ function truncateText(lines = 1) {
131
165
  textOverflow: "ellipsis"
132
166
  };
133
167
  }
168
+ function useScrollElevation(options = {}) {
169
+ const { scrolled: controlledScrolled, onScrollStateChange, threshold = 0 } = options;
170
+ const isControlled = controlledScrolled !== void 0;
171
+ const [internalScrolled, setInternalScrolled] = react.useState(false);
172
+ const handleScroll = react.useCallback(() => {
173
+ const currentlyScrolled = window.scrollY > threshold;
174
+ setInternalScrolled((prev) => {
175
+ if (prev !== currentlyScrolled) {
176
+ onScrollStateChange?.(currentlyScrolled);
177
+ return currentlyScrolled;
178
+ }
179
+ return prev;
180
+ });
181
+ }, [threshold, onScrollStateChange]);
182
+ react.useEffect(() => {
183
+ if (isControlled) return;
184
+ window.addEventListener("scroll", handleScroll, { passive: true });
185
+ handleScroll();
186
+ return () => {
187
+ window.removeEventListener("scroll", handleScroll);
188
+ };
189
+ }, [isControlled, handleScroll]);
190
+ return {
191
+ isScrolled: isControlled ? controlledScrolled : internalScrolled
192
+ };
193
+ }
194
+ var AppBarHeadless = react.forwardRef(
195
+ ({ className, children, scrolled: scrolledProp, onScrollStateChange }, ref) => {
196
+ const { isScrolled } = useScrollElevation({
197
+ scrolled: scrolledProp,
198
+ onScrollStateChange
199
+ });
200
+ return /* @__PURE__ */ jsxRuntime.jsx("header", { ref, role: "banner", className, "data-scrolled": isScrolled, children });
201
+ }
202
+ );
203
+ AppBarHeadless.displayName = "AppBarHeadless";
204
+ var appBarVariants = classVarianceAuthority.cva(
205
+ [
206
+ // Base classes (always applied)
207
+ "w-full",
208
+ "bg-surface text-on-surface",
209
+ "flex flex-col",
210
+ // Elevation transition using MD3 motion tokens
211
+ "transition-shadow duration-medium2 ease-standard"
212
+ ],
213
+ {
214
+ variants: {
215
+ /**
216
+ * Size variant (MD3 specification)
217
+ * Controls bar height, title placement, and type scale
218
+ */
219
+ variant: {
220
+ /** 64dp, title left-aligned, title-large */
221
+ small: "h-appbar-small",
222
+ /** 64dp, title centered, title-large */
223
+ "center-aligned": "h-appbar-small variant-center-aligned",
224
+ /** 112dp, title bottom-left, headline-small */
225
+ medium: "h-appbar-medium",
226
+ /** 152dp, title bottom-left, display-small */
227
+ large: "h-appbar-large"
228
+ },
229
+ /**
230
+ * Scroll state — controls surface elevation
231
+ * MD3: flat at rest, elevated on scroll
232
+ */
233
+ scrolled: {
234
+ false: "shadow-elevation-0",
235
+ true: "shadow-elevation-2"
236
+ }
237
+ },
238
+ defaultVariants: {
239
+ variant: "small",
240
+ scrolled: false
241
+ }
242
+ }
243
+ );
244
+ var appBarTitleVariants = classVarianceAuthority.cva("text-on-surface font-normal truncate", {
245
+ variants: {
246
+ variant: {
247
+ small: "text-title-large",
248
+ "center-aligned": "text-title-large",
249
+ medium: "text-headline-small",
250
+ large: "text-display-small"
251
+ }
252
+ },
253
+ defaultVariants: {
254
+ variant: "small"
255
+ }
256
+ });
257
+ var AppBar = react.forwardRef(
258
+ ({
259
+ variant = "small",
260
+ title,
261
+ navigationIcon,
262
+ actions,
263
+ scrolled: scrolledProp,
264
+ onScrollStateChange,
265
+ className
266
+ }, ref) => {
267
+ const { isScrolled } = useScrollElevation({
268
+ scrolled: scrolledProp,
269
+ onScrollStateChange
270
+ });
271
+ const isExpandedVariant = variant === "medium" || variant === "large";
272
+ return /* @__PURE__ */ jsxRuntime.jsxs(
273
+ AppBarHeadless,
274
+ {
275
+ ref,
276
+ scrolled: isScrolled,
277
+ className: cn(appBarVariants({ variant, scrolled: isScrolled }), className),
278
+ children: [
279
+ /* @__PURE__ */ jsxRuntime.jsxs(
280
+ "div",
281
+ {
282
+ "data-slot": "top-row",
283
+ className: cn(
284
+ "flex items-center",
285
+ "px-1",
286
+ // Small and center-aligned: fill the full bar height
287
+ !isExpandedVariant && "flex-1",
288
+ // Expanded variants: fixed height for the top row (64dp)
289
+ isExpandedVariant && "h-16 shrink-0"
290
+ ),
291
+ children: [
292
+ navigationIcon != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "navigation", className: "flex shrink-0 items-center", children: navigationIcon }),
293
+ !isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsx(
294
+ "span",
295
+ {
296
+ "data-testid": "appbar-title",
297
+ className: cn(
298
+ "min-w-0 flex-1 px-1",
299
+ appBarTitleVariants({ variant }),
300
+ // Center-aligned: center the title text
301
+ variant === "center-aligned" && "text-center"
302
+ ),
303
+ children: title
304
+ }
305
+ ),
306
+ actions != null && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "actions", className: "flex shrink-0 items-center gap-0.5", children: actions })
307
+ ]
308
+ }
309
+ ),
310
+ isExpandedVariant && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-slot": "expanded-title", className: cn("flex flex-1 items-end", "px-4 pb-4"), children: /* @__PURE__ */ jsxRuntime.jsx(
311
+ "span",
312
+ {
313
+ "data-testid": "appbar-title",
314
+ className: cn("min-w-0 truncate", appBarTitleVariants({ variant })),
315
+ children: title
316
+ }
317
+ ) })
318
+ ]
319
+ }
320
+ );
321
+ }
322
+ );
323
+ AppBar.displayName = "AppBar";
134
324
  var ButtonHeadless = react.forwardRef(
135
325
  ({ className, children, tabIndex = 0, onMouseDown, type, ...restProps }, forwardedRef) => {
136
326
  const internalRef = react.useRef(null);
@@ -304,7 +494,7 @@ var buttonVariants = classVarianceAuthority.cva(
304
494
  {
305
495
  variant: "tonal",
306
496
  color: "primary",
307
- className: "bg-secondary-container text-on-secondary-container"
497
+ className: "bg-primary-container text-on-primary-container"
308
498
  },
309
499
  {
310
500
  variant: "tonal",
@@ -575,7 +765,7 @@ IconButtonHeadless.displayName = "IconButtonHeadless";
575
765
  var iconButtonVariants = classVarianceAuthority.cva(
576
766
  [
577
767
  // Base classes (always applied)
578
- "relative inline-flex items-center justify-center",
768
+ "relative inline-flex items-center justify-center cursor-pointer",
579
769
  "overflow-hidden rounded-full",
580
770
  // Circular shape
581
771
  "transition-all duration-200",
@@ -889,7 +1079,7 @@ FABHeadless.displayName = "FABHeadless";
889
1079
  var fabVariants = classVarianceAuthority.cva(
890
1080
  [
891
1081
  // Base classes (always applied)
892
- "relative inline-flex items-center justify-center",
1082
+ "relative inline-flex items-center justify-center cursor-pointer",
893
1083
  "overflow-hidden",
894
1084
  "transition-all duration-200",
895
1085
  "focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
@@ -1330,8 +1520,8 @@ var textFieldLabelVariants = classVarianceAuthority.cva(
1330
1520
  {
1331
1521
  variants: {
1332
1522
  variant: {
1333
- filled: "top-4",
1334
- outlined: "top-3 bg-surface px-1"
1523
+ filled: "top-2.5",
1524
+ outlined: "top-2.5 bg-surface px-1"
1335
1525
  },
1336
1526
  size: {
1337
1527
  small: "text-sm",
@@ -1339,7 +1529,7 @@ var textFieldLabelVariants = classVarianceAuthority.cva(
1339
1529
  large: "text-lg"
1340
1530
  },
1341
1531
  floating: {
1342
- true: "-translate-y-6 scale-75",
1532
+ true: "-translate-y-5 scale-75",
1343
1533
  false: "scale-100"
1344
1534
  },
1345
1535
  focused: {
@@ -1364,7 +1554,7 @@ var textFieldLabelVariants = classVarianceAuthority.cva(
1364
1554
  {
1365
1555
  variant: "outlined",
1366
1556
  floating: true,
1367
- className: "-top-2"
1557
+ className: "top-2.5"
1368
1558
  }
1369
1559
  ],
1370
1560
  defaultVariants: {
@@ -1677,7 +1867,8 @@ var TextField = react.forwardRef(
1677
1867
  hasLeadingIcon: !!leadingIcon,
1678
1868
  hasTrailingIcon: !!trailingIcon,
1679
1869
  multiline: true
1680
- })
1870
+ }),
1871
+ label && "placeholder:opacity-0"
1681
1872
  ),
1682
1873
  rows,
1683
1874
  spellCheck: spellCheckProp
@@ -1695,7 +1886,9 @@ var TextField = react.forwardRef(
1695
1886
  hasLeadingIcon: !!leadingIcon,
1696
1887
  hasTrailingIcon: !!trailingIcon,
1697
1888
  multiline: false
1698
- })
1889
+ }),
1890
+ label && "placeholder:opacity-0"
1891
+ // Hide placeholder when there's a value to prevent overlap with floating label
1699
1892
  ),
1700
1893
  spellCheck: spellCheckProp
1701
1894
  }
@@ -1929,8 +2122,7 @@ var checkboxLabelVariants = classVarianceAuthority.cva(
1929
2122
  // MD3: Body Medium (14px)
1930
2123
  "text-on-surface",
1931
2124
  "select-none",
1932
- "ml-4"
1933
- // 16px spacing between checkbox and label (MD3 standard)
2125
+ "ml-1.5"
1934
2126
  ],
1935
2127
  {
1936
2128
  variants: {
@@ -2192,7 +2384,7 @@ var switchHandleContainerVariants = classVarianceAuthority.cva(
2192
2384
  */
2193
2385
  selected: {
2194
2386
  true: [
2195
- "left-[28px]",
2387
+ "left-[24px]",
2196
2388
  // Position when ON (52px - 24px = 28px)
2197
2389
  "text-primary"
2198
2390
  // State layer color
@@ -2732,8 +2924,7 @@ var radioLabelVariants = classVarianceAuthority.cva(
2732
2924
  // MD3: Body Medium (14px)
2733
2925
  "text-on-surface",
2734
2926
  "select-none",
2735
- "ml-4"
2736
- // 16px spacing between radio and label (MD3 standard)
2927
+ "ml-1.5"
2737
2928
  ],
2738
2929
  {
2739
2930
  variants: {
@@ -2985,6 +3176,2964 @@ var RadioHeadless = react.forwardRef(
2985
3176
  }
2986
3177
  );
2987
3178
  RadioHeadless.displayName = "RadioHeadless";
3179
+ var HeadlessTabsContext = react.createContext(null);
3180
+ function useHeadlessTabsContext(componentName) {
3181
+ const context = react.useContext(HeadlessTabsContext);
3182
+ if (!context) {
3183
+ throw new Error(`${componentName} must be used within a Tabs component`);
3184
+ }
3185
+ return context;
3186
+ }
3187
+ var HeadlessTabList = react.forwardRef(
3188
+ ({ children, className }, forwardedRef) => {
3189
+ const { state } = useHeadlessTabsContext("HeadlessTabList");
3190
+ const internalRef = react.useRef(null);
3191
+ const ref = forwardedRef ?? internalRef;
3192
+ const { tabListProps } = reactAria.useTabList({}, state, ref);
3193
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabListProps, ref, className, children });
3194
+ }
3195
+ );
3196
+ HeadlessTabList.displayName = "HeadlessTabList";
3197
+ var HeadlessTab = react.forwardRef(
3198
+ ({ item, className, onMouseDown, children, ...props }, forwardedRef) => {
3199
+ const { state } = useHeadlessTabsContext("HeadlessTab");
3200
+ const internalRef = react.useRef(null);
3201
+ const ref = forwardedRef ?? internalRef;
3202
+ const { tabProps, isSelected, isDisabled, isPressed } = reactAria.useTab(
3203
+ {
3204
+ key: item.key,
3205
+ ...item.isDisabled !== void 0 && { isDisabled: item.isDisabled }
3206
+ },
3207
+ state,
3208
+ ref
3209
+ );
3210
+ const { isFocusVisible, focusProps } = reactAria.useFocusRing();
3211
+ const mergedProps = utils.mergeProps(tabProps, focusProps, {
3212
+ className,
3213
+ onMouseDown
3214
+ });
3215
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { ...mergedProps, ref, type: "button", "data-key": String(item.key), ...props, children: typeof children === "function" ? children({ isSelected, isDisabled, isFocusVisible, isPressed }) : children });
3216
+ }
3217
+ );
3218
+ HeadlessTab.displayName = "HeadlessTab";
3219
+ var HeadlessTabPanel = react.forwardRef(
3220
+ ({ children, className, ...props }, forwardedRef) => {
3221
+ const { state } = useHeadlessTabsContext("HeadlessTabPanel");
3222
+ const internalRef = react.useRef(null);
3223
+ const ref = forwardedRef ?? internalRef;
3224
+ const { tabPanelProps } = reactAria.useTabPanel(props, state, ref);
3225
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabPanelProps, ref, className, children });
3226
+ }
3227
+ );
3228
+ HeadlessTabPanel.displayName = "HeadlessTabPanel";
3229
+ var TabsContext = react.createContext(null);
3230
+ function useTabsContext() {
3231
+ const context = react.useContext(TabsContext);
3232
+ if (!context) {
3233
+ throw new Error("Component must be used within a Tabs component");
3234
+ }
3235
+ return context;
3236
+ }
3237
+ function getComponentName(type) {
3238
+ if (typeof type === "string") return type;
3239
+ const exotic = type;
3240
+ return exotic.displayName ?? exotic.name ?? "";
3241
+ }
3242
+ function extractTabItemsFromChildren(children) {
3243
+ const items = [];
3244
+ react.Children.forEach(children, (child) => {
3245
+ if (!react.isValidElement(child)) return;
3246
+ if (getComponentName(child.type) === "TabList") {
3247
+ const tabListProps = child.props;
3248
+ react.Children.forEach(tabListProps.children, (tabChild) => {
3249
+ if (!react.isValidElement(tabChild)) return;
3250
+ if (getComponentName(tabChild.type) === "Tab") {
3251
+ const tabProps = tabChild.props;
3252
+ items.push({
3253
+ key: tabProps.id,
3254
+ ...tabProps.label !== void 0 && { label: tabProps.label },
3255
+ ...tabProps.icon !== void 0 && { icon: tabProps.icon },
3256
+ ...tabProps.isDisabled !== void 0 && { isDisabled: tabProps.isDisabled },
3257
+ ...tabProps["aria-label"] !== void 0 && { "aria-label": tabProps["aria-label"] }
3258
+ });
3259
+ }
3260
+ });
3261
+ }
3262
+ });
3263
+ return items;
3264
+ }
3265
+ var Tabs = react.forwardRef(
3266
+ ({
3267
+ selectedKey,
3268
+ defaultSelectedKey,
3269
+ onSelectionChange,
3270
+ variant = "primary",
3271
+ layout = "fixed",
3272
+ children,
3273
+ className,
3274
+ "aria-label": ariaLabel,
3275
+ "aria-labelledby": ariaLabelledBy
3276
+ }, ref) => {
3277
+ const tabItems = react.useMemo(() => extractTabItemsFromChildren(children), [children]);
3278
+ const state = reactStately.useTabListState({
3279
+ ...selectedKey !== void 0 && { selectedKey },
3280
+ ...defaultSelectedKey !== void 0 && { defaultSelectedKey },
3281
+ ...onSelectionChange !== void 0 && { onSelectionChange },
3282
+ disabledKeys: tabItems.filter((t) => t.isDisabled).map((t) => t.key),
3283
+ children: tabItems.map((item) => /* @__PURE__ */ jsxRuntime.jsx(reactStately.Item, { textValue: item.label ?? item["aria-label"] ?? String(item.key), children: item.label ?? item["aria-label"] ?? "" }, item.key))
3284
+ });
3285
+ const tabsContextValue = react.useMemo(
3286
+ () => ({
3287
+ selectedKey: state.selectedKey,
3288
+ variant,
3289
+ layout,
3290
+ disabledKeys: tabItems.filter((t) => t.isDisabled).map((t) => t.key),
3291
+ ...ariaLabel !== void 0 && { "aria-label": ariaLabel },
3292
+ ...ariaLabelledBy !== void 0 && { "aria-labelledby": ariaLabelledBy }
3293
+ }),
3294
+ [state.selectedKey, variant, layout, tabItems, ariaLabel, ariaLabelledBy]
3295
+ );
3296
+ const headlessContextValue = react.useMemo(() => ({ state }), [state]);
3297
+ return /* @__PURE__ */ jsxRuntime.jsx(HeadlessTabsContext.Provider, { value: headlessContextValue, children: /* @__PURE__ */ jsxRuntime.jsx(TabsContext.Provider, { value: tabsContextValue, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex flex-col", className), children }) }) });
3298
+ }
3299
+ );
3300
+ Tabs.displayName = "Tabs";
3301
+ var tabListVariants = classVarianceAuthority.cva(
3302
+ [
3303
+ // Base classes
3304
+ "relative flex",
3305
+ "bg-surface",
3306
+ // Bottom divider line (MD3 spec)
3307
+ "border-b border-outline-variant"
3308
+ ],
3309
+ {
3310
+ variants: {
3311
+ layout: {
3312
+ fixed: "w-full",
3313
+ scrollable: "overflow-x-auto scrollbar-none"
3314
+ }
3315
+ },
3316
+ defaultVariants: {
3317
+ layout: "fixed"
3318
+ }
3319
+ }
3320
+ );
3321
+ var tabVariants = classVarianceAuthority.cva(
3322
+ [
3323
+ // Base layout
3324
+ "relative flex flex-col items-center justify-center",
3325
+ "min-h-12 px-4",
3326
+ "cursor-pointer select-none",
3327
+ "overflow-hidden",
3328
+ // Typography: MD3 Title Small
3329
+ "text-sm font-medium tracking-[0.1px]",
3330
+ // Transition
3331
+ "transition-colors duration-200",
3332
+ // Focus visible
3333
+ "focus-visible:outline-none",
3334
+ // State layer via before pseudo-element
3335
+ "before:absolute before:inset-0 before:transition-opacity before:duration-200",
3336
+ "before:bg-current before:opacity-0",
3337
+ "hover:before:opacity-8",
3338
+ "active:before:opacity-12",
3339
+ "focus-visible:before:opacity-12"
3340
+ ],
3341
+ {
3342
+ variants: {
3343
+ /**
3344
+ * Tab variant (Primary or Secondary)
3345
+ */
3346
+ variant: {
3347
+ primary: "",
3348
+ secondary: ""
3349
+ },
3350
+ /**
3351
+ * Selected state
3352
+ */
3353
+ selected: {
3354
+ true: "",
3355
+ false: ""
3356
+ },
3357
+ /**
3358
+ * Disabled state
3359
+ */
3360
+ disabled: {
3361
+ true: "opacity-38 cursor-not-allowed pointer-events-none",
3362
+ false: ""
3363
+ },
3364
+ /**
3365
+ * Layout determines min-width behavior
3366
+ */
3367
+ layout: {
3368
+ fixed: "flex-1",
3369
+ scrollable: "min-w-[90px] shrink-0"
3370
+ }
3371
+ },
3372
+ compoundVariants: [
3373
+ // Primary + selected
3374
+ {
3375
+ variant: "primary",
3376
+ selected: true,
3377
+ disabled: false,
3378
+ className: "text-primary"
3379
+ },
3380
+ // Primary + unselected
3381
+ {
3382
+ variant: "primary",
3383
+ selected: false,
3384
+ disabled: false,
3385
+ className: "text-on-surface-variant"
3386
+ },
3387
+ // Secondary + selected
3388
+ {
3389
+ variant: "secondary",
3390
+ selected: true,
3391
+ disabled: false,
3392
+ className: "text-on-surface"
3393
+ },
3394
+ // Secondary + unselected
3395
+ {
3396
+ variant: "secondary",
3397
+ selected: false,
3398
+ disabled: false,
3399
+ className: "text-on-surface-variant"
3400
+ }
3401
+ ],
3402
+ defaultVariants: {
3403
+ variant: "primary",
3404
+ selected: false,
3405
+ disabled: false,
3406
+ layout: "fixed"
3407
+ }
3408
+ }
3409
+ );
3410
+ var tabIndicatorVariants = classVarianceAuthority.cva(
3411
+ [
3412
+ // Base: absolutely positioned at bottom
3413
+ "absolute bottom-0 left-0",
3414
+ "pointer-events-none",
3415
+ // Transition using MD3 motion tokens (medium2 duration, emphasized easing)
3416
+ "transition-[left,width]",
3417
+ "duration-medium2",
3418
+ "ease-emphasized"
3419
+ ],
3420
+ {
3421
+ variants: {
3422
+ variant: {
3423
+ primary: ["h-[3px]", "bg-primary", "rounded-t-sm"],
3424
+ secondary: ["h-[2px]", "bg-on-surface-variant"]
3425
+ }
3426
+ },
3427
+ defaultVariants: {
3428
+ variant: "primary"
3429
+ }
3430
+ }
3431
+ );
3432
+ var tabPanelVariants = classVarianceAuthority.cva(
3433
+ [
3434
+ // Base panel styles
3435
+ "outline-none",
3436
+ "focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2"
3437
+ ],
3438
+ {
3439
+ variants: {},
3440
+ defaultVariants: {}
3441
+ }
3442
+ );
3443
+ var tabBadgeVariants = classVarianceAuthority.cva(
3444
+ [
3445
+ // Base badge
3446
+ "absolute",
3447
+ "inline-flex items-center justify-center",
3448
+ "bg-error text-on-error",
3449
+ "font-medium leading-none",
3450
+ "pointer-events-none"
3451
+ ],
3452
+ {
3453
+ variants: {
3454
+ type: {
3455
+ dot: ["top-1 right-1", "w-1.5 h-1.5", "rounded-full"],
3456
+ count: ["-top-1 -right-1", "min-w-[16px] h-4", "px-1", "rounded-full", "text-[11px]"]
3457
+ }
3458
+ },
3459
+ defaultVariants: {
3460
+ type: "count"
3461
+ }
3462
+ }
3463
+ );
3464
+ var tabIconVariants = classVarianceAuthority.cva(
3465
+ ["relative", "inline-flex items-center justify-center", "w-6 h-6"],
3466
+ {
3467
+ variants: {
3468
+ hasLabel: {
3469
+ true: "mb-1",
3470
+ false: ""
3471
+ }
3472
+ },
3473
+ defaultVariants: {
3474
+ hasLabel: false
3475
+ }
3476
+ }
3477
+ );
3478
+ var TabList = react.forwardRef(
3479
+ ({ children, className }, forwardedRef) => {
3480
+ const { state } = useHeadlessTabsContext("TabList");
3481
+ const {
3482
+ variant,
3483
+ layout,
3484
+ "aria-label": ariaLabel,
3485
+ "aria-labelledby": ariaLabelledBy
3486
+ } = useTabsContext();
3487
+ const internalRef = react.useRef(null);
3488
+ const ref = forwardedRef ?? internalRef;
3489
+ const { tabListProps } = reactAria.useTabList(
3490
+ {
3491
+ ...ariaLabel !== void 0 && { "aria-label": ariaLabel },
3492
+ ...ariaLabelledBy !== void 0 && { "aria-labelledby": ariaLabelledBy }
3493
+ },
3494
+ state,
3495
+ ref
3496
+ );
3497
+ const handleNavKeyDown = react.useCallback(
3498
+ (e) => {
3499
+ if (!["ArrowRight", "ArrowLeft", "Home", "End"].includes(e.key)) return;
3500
+ const container = ref.current;
3501
+ if (!container) return;
3502
+ const focusedEl = document.activeElement;
3503
+ const currentKey = focusedEl?.dataset?.key;
3504
+ if (!currentKey) return;
3505
+ const allTabEls = [...container.querySelectorAll("[data-key]")];
3506
+ const allKeys = allTabEls.map((el) => el.dataset.key).filter(Boolean);
3507
+ const enabledKeys = allKeys.filter(
3508
+ (k) => allTabEls.find((el) => el.dataset.key === k)?.getAttribute("aria-disabled") !== "true"
3509
+ );
3510
+ const currentIndex = enabledKeys.indexOf(currentKey);
3511
+ if (currentIndex === -1) return;
3512
+ let nextKey = null;
3513
+ switch (e.key) {
3514
+ case "ArrowRight":
3515
+ nextKey = enabledKeys[(currentIndex + 1) % enabledKeys.length] ?? null;
3516
+ break;
3517
+ case "ArrowLeft":
3518
+ nextKey = enabledKeys[(currentIndex - 1 + enabledKeys.length) % enabledKeys.length] ?? null;
3519
+ break;
3520
+ case "Home":
3521
+ nextKey = enabledKeys[0] ?? null;
3522
+ break;
3523
+ case "End":
3524
+ nextKey = enabledKeys[enabledKeys.length - 1] ?? null;
3525
+ break;
3526
+ }
3527
+ if (nextKey != null) {
3528
+ e.preventDefault();
3529
+ state.setSelectedKey(nextKey);
3530
+ const nextEl = container.querySelector(
3531
+ `[data-key="${CSS.escape(nextKey)}"]`
3532
+ );
3533
+ nextEl?.focus();
3534
+ }
3535
+ },
3536
+ [state, ref]
3537
+ );
3538
+ const { onKeyDown: tabListKeyDown } = tabListProps;
3539
+ const handleKeyDown = react.useCallback(
3540
+ (e) => {
3541
+ handleNavKeyDown(e);
3542
+ if (!e.defaultPrevented) {
3543
+ tabListKeyDown?.(e);
3544
+ }
3545
+ },
3546
+ [handleNavKeyDown, tabListKeyDown]
3547
+ );
3548
+ const [indicatorStyle, setIndicatorStyle] = react.useState({ left: 0, width: 0 });
3549
+ const [indicatorReady, setIndicatorReady] = react.useState(false);
3550
+ const updateIndicator = react.useCallback(() => {
3551
+ const container = ref.current;
3552
+ if (!container) return;
3553
+ const selectedTab = container.querySelector('[aria-selected="true"]');
3554
+ if (!selectedTab) return;
3555
+ const containerRect = container.getBoundingClientRect();
3556
+ const tabRect = selectedTab.getBoundingClientRect();
3557
+ const newLeft = tabRect.left - containerRect.left + container.scrollLeft;
3558
+ const newWidth = tabRect.width;
3559
+ setIndicatorStyle((prev) => {
3560
+ if (prev.left === newLeft && prev.width === newWidth) return prev;
3561
+ return { left: newLeft, width: newWidth };
3562
+ });
3563
+ setIndicatorReady(true);
3564
+ }, [ref]);
3565
+ react.useLayoutEffect(() => {
3566
+ updateIndicator();
3567
+ }, [state.selectedKey, updateIndicator]);
3568
+ const mergedTabListProps = { ...tabListProps, onKeyDown: handleKeyDown };
3569
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...mergedTabListProps, ref, className: cn(tabListVariants({ layout }), className), children: [
3570
+ children,
3571
+ /* @__PURE__ */ jsxRuntime.jsx(
3572
+ "span",
3573
+ {
3574
+ "data-tab-indicator": true,
3575
+ "aria-hidden": "true",
3576
+ className: cn(
3577
+ tabIndicatorVariants({ variant }),
3578
+ // Hide until first position is calculated to avoid flash
3579
+ !indicatorReady && "opacity-0"
3580
+ ),
3581
+ style: {
3582
+ // Dynamic left/width values from DOM measurements
3583
+ left: `${indicatorStyle.left}px`,
3584
+ width: `${indicatorStyle.width}px`
3585
+ }
3586
+ }
3587
+ )
3588
+ ] });
3589
+ }
3590
+ );
3591
+ TabList.displayName = "TabList";
3592
+ function resolveBadgeDisplay(badge) {
3593
+ if (badge === void 0 || badge === false) return null;
3594
+ if (badge === true) return "dot";
3595
+ if (typeof badge === "number") {
3596
+ if (badge === 0) return null;
3597
+ return badge > 999 ? "999+" : String(badge);
3598
+ }
3599
+ return null;
3600
+ }
3601
+ var Tab = react.forwardRef(
3602
+ ({ id, icon, label, badge, isDisabled = false, disableRipple = false, className, ...htmlProps }, forwardedRef) => {
3603
+ const { state } = useHeadlessTabsContext("Tab");
3604
+ const { variant, layout } = useTabsContext();
3605
+ const internalRef = react.useRef(null);
3606
+ const ref = forwardedRef ?? internalRef;
3607
+ const {
3608
+ tabProps,
3609
+ isSelected,
3610
+ isDisabled: ariaIsDisabled
3611
+ } = reactAria.useTab({ key: id, isDisabled }, state, ref);
3612
+ const { isFocusVisible, focusProps } = reactAria.useFocusRing();
3613
+ const finalIsDisabled = isDisabled || ariaIsDisabled;
3614
+ const { onMouseDown: handleRipple, ripples } = useRipple({
3615
+ disabled: finalIsDisabled || disableRipple
3616
+ });
3617
+ const handleClick = react.useCallback(() => {
3618
+ if (!finalIsDisabled) {
3619
+ state.setSelectedKey(id);
3620
+ }
3621
+ }, [state, id, finalIsDisabled]);
3622
+ const handleFocus = react.useCallback(() => {
3623
+ if (!finalIsDisabled) {
3624
+ state.selectionManager.setFocusedKey(id);
3625
+ }
3626
+ }, [state.selectionManager, id, finalIsDisabled]);
3627
+ const mergedProps = utils.mergeProps(tabProps, focusProps, {
3628
+ onMouseDown: disableRipple ? void 0 : handleRipple,
3629
+ onClick: handleClick,
3630
+ onFocus: handleFocus
3631
+ });
3632
+ const badgeDisplay = resolveBadgeDisplay(badge);
3633
+ const isDotBadge = badgeDisplay === "dot";
3634
+ const hasIcon = Boolean(icon);
3635
+ const hasLabel = Boolean(label);
3636
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3637
+ "button",
3638
+ {
3639
+ ...mergedProps,
3640
+ ref,
3641
+ type: "button",
3642
+ "data-key": String(id),
3643
+ tabIndex: finalIsDisabled ? -1 : isSelected ? 0 : -1,
3644
+ className: cn(
3645
+ tabVariants({
3646
+ variant,
3647
+ selected: isSelected,
3648
+ disabled: finalIsDisabled,
3649
+ layout
3650
+ }),
3651
+ isFocusVisible && "outline-primary outline-2 outline-offset-2",
3652
+ hasLabel && hasIcon && "min-h-16",
3653
+ className
3654
+ ),
3655
+ ...htmlProps,
3656
+ children: [
3657
+ !disableRipple && ripples,
3658
+ hasIcon && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn(tabIconVariants({ hasLabel })), children: [
3659
+ icon,
3660
+ badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
3661
+ "span",
3662
+ {
3663
+ "data-badge-type": isDotBadge ? "dot" : "count",
3664
+ "aria-hidden": "true",
3665
+ className: cn(tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })),
3666
+ children: !isDotBadge && badgeDisplay
3667
+ }
3668
+ )
3669
+ ] }),
3670
+ hasLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 truncate", children: [
3671
+ label,
3672
+ !hasIcon && badgeDisplay && /* @__PURE__ */ jsxRuntime.jsx(
3673
+ "span",
3674
+ {
3675
+ "data-badge-type": isDotBadge ? "dot" : "count",
3676
+ "aria-hidden": "true",
3677
+ className: cn(
3678
+ "relative ml-1",
3679
+ tabBadgeVariants({ type: isDotBadge ? "dot" : "count" })
3680
+ ),
3681
+ children: !isDotBadge && badgeDisplay
3682
+ }
3683
+ )
3684
+ ] })
3685
+ ]
3686
+ }
3687
+ );
3688
+ }
3689
+ );
3690
+ Tab.displayName = "Tab";
3691
+ var TabPanel = react.forwardRef(
3692
+ ({ id, children, className }, forwardedRef) => {
3693
+ const { state } = useHeadlessTabsContext("TabPanel");
3694
+ useTabsContext();
3695
+ const internalRef = react.useRef(null);
3696
+ const ref = forwardedRef ?? internalRef;
3697
+ const { tabPanelProps } = reactAria.useTabPanel({}, state, ref);
3698
+ if (state.selectedKey !== id) {
3699
+ return null;
3700
+ }
3701
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabPanelProps, ref, className: cn(tabPanelVariants(), className), children });
3702
+ }
3703
+ );
3704
+ TabPanel.displayName = "TabPanel";
3705
+ var NavigationBarContext = react.createContext(null);
3706
+ function useNavigationBarContext() {
3707
+ const ctx = react.useContext(NavigationBarContext);
3708
+ if (ctx === null) {
3709
+ throw new Error("HeadlessNavigationBarItem must be rendered inside HeadlessNavigationBar.");
3710
+ }
3711
+ return ctx;
3712
+ }
3713
+ var HeadlessNavigationBar = react.forwardRef(
3714
+ ({
3715
+ items,
3716
+ selectedKey,
3717
+ defaultSelectedKey,
3718
+ onSelectionChange,
3719
+ "aria-label": ariaLabel,
3720
+ className,
3721
+ renderItem
3722
+ }, ref) => {
3723
+ const collectionChildren = items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(reactStately.Item, { textValue: item.label ?? item["aria-label"] ?? item.key, children: item.key }, item.key));
3724
+ const disabledKeys = items.filter((item) => item.isDisabled).map((item) => item.key);
3725
+ const state = reactStately.useTabListState({
3726
+ children: collectionChildren,
3727
+ ...selectedKey !== void 0 ? { selectedKey } : {},
3728
+ ...defaultSelectedKey !== void 0 ? { defaultSelectedKey } : {},
3729
+ ...onSelectionChange ? { onSelectionChange } : {},
3730
+ disabledKeys
3731
+ });
3732
+ const tabListRef = react.useRef(null);
3733
+ const { tabListProps } = reactAria.useTabList({ "aria-label": ariaLabel }, state, tabListRef);
3734
+ return /* @__PURE__ */ jsxRuntime.jsx(
3735
+ "nav",
3736
+ {
3737
+ ref,
3738
+ role: "navigation",
3739
+ "aria-label": ariaLabel,
3740
+ className,
3741
+ children: /* @__PURE__ */ jsxRuntime.jsx(NavigationBarContext.Provider, { value: { state, hideLabels: false, disableRipple: false }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ...tabListProps, ref: tabListRef, className: "flex h-full w-full items-stretch", children: [...state.collection].map((collectionItem) => {
3742
+ const itemConfig = items.find((i) => String(i.key) === String(collectionItem.key));
3743
+ if (!itemConfig) return null;
3744
+ return renderItem(itemConfig);
3745
+ }) }) })
3746
+ }
3747
+ );
3748
+ }
3749
+ );
3750
+ HeadlessNavigationBar.displayName = "HeadlessNavigationBar";
3751
+ var HeadlessNavigationBarItem = react.forwardRef(({ itemKey, children, className, "aria-label": ariaLabel }, ref) => {
3752
+ const { state } = useNavigationBarContext();
3753
+ const internalRef = react.useRef(null);
3754
+ const resolvedRef = ref ?? internalRef;
3755
+ const { tabProps, isSelected } = reactAria.useTab({ key: itemKey }, state, resolvedRef);
3756
+ const { "aria-controls": _controls, ...tabPropsWithoutControls } = tabProps;
3757
+ const { focusProps, isFocusVisible } = reactAria.useFocusRing();
3758
+ const content = typeof children === "function" ? children({ isSelected, isFocusVisible }) : children;
3759
+ return /* @__PURE__ */ jsxRuntime.jsx(
3760
+ "button",
3761
+ {
3762
+ type: "button",
3763
+ ...utils.mergeProps(tabPropsWithoutControls, focusProps),
3764
+ ref: resolvedRef,
3765
+ className,
3766
+ "aria-label": ariaLabel,
3767
+ "data-focus-visible": isFocusVisible || void 0,
3768
+ "data-selected": isSelected,
3769
+ children: content
3770
+ }
3771
+ );
3772
+ });
3773
+ HeadlessNavigationBarItem.displayName = "HeadlessNavigationBarItem";
3774
+ var navigationBarVariants = classVarianceAuthority.cva([
3775
+ // Layout
3776
+ "fixed bottom-0 left-0 right-0 z-10",
3777
+ "w-full",
3778
+ "flex flex-row items-stretch",
3779
+ // MD3 surface
3780
+ "bg-surface-container",
3781
+ // MD3 height: 80dp
3782
+ "h-20",
3783
+ // Safe-area bottom (for mobile devices)
3784
+ "pb-safe"
3785
+ ]);
3786
+ var navigationBarItemVariants = classVarianceAuthority.cva(
3787
+ [
3788
+ // Layout
3789
+ "relative flex flex-1 flex-col items-center justify-center",
3790
+ "cursor-pointer select-none outline-none",
3791
+ // State layer pseudo-element (covers the full item area)
3792
+ "before:absolute before:inset-0 before:rounded-none before:transition-opacity before:duration-short2 before:ease-standard",
3793
+ // Focus-visible ring
3794
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary",
3795
+ // Transition for color changes
3796
+ "transition-colors duration-short2 ease-standard"
3797
+ ],
3798
+ {
3799
+ variants: {
3800
+ /**
3801
+ * Whether this item is the currently selected destination.
3802
+ * Controls icon and label colors per MD3 spec.
3803
+ */
3804
+ isActive: {
3805
+ true: [
3806
+ // Active icon color applied via text-color on the icon wrapper
3807
+ "[&>[data-icon]]:text-on-secondary-container",
3808
+ // Active label color
3809
+ "[&>[data-label]]:text-on-surface",
3810
+ // State layer color for active item
3811
+ "before:bg-on-surface-variant"
3812
+ ],
3813
+ false: [
3814
+ // Inactive icon color
3815
+ "[&>[data-icon]]:text-on-surface-variant",
3816
+ // Inactive label color
3817
+ "[&>[data-label]]:text-on-surface-variant",
3818
+ // State layer color for inactive item
3819
+ "before:bg-on-surface-variant"
3820
+ ]
3821
+ },
3822
+ /**
3823
+ * Whether the item is disabled.
3824
+ * Applies `opacity-38` per MD3 disabled state.
3825
+ */
3826
+ isDisabled: {
3827
+ true: ["cursor-not-allowed opacity-38 pointer-events-none"],
3828
+ false: []
3829
+ },
3830
+ /**
3831
+ * Hover and press state layer opacities.
3832
+ * Applied via compound variants on hover/active pseudo-classes.
3833
+ */
3834
+ isHovered: {
3835
+ true: ["before:opacity-8"],
3836
+ false: ["before:opacity-0"]
3837
+ },
3838
+ isPressed: {
3839
+ true: ["before:opacity-12"],
3840
+ false: []
3841
+ }
3842
+ },
3843
+ compoundVariants: [
3844
+ // When not hovered or pressed, state layer is invisible
3845
+ {
3846
+ isHovered: false,
3847
+ isPressed: false,
3848
+ className: "before:opacity-0"
3849
+ }
3850
+ ],
3851
+ defaultVariants: {
3852
+ isActive: false,
3853
+ isDisabled: false,
3854
+ isHovered: false,
3855
+ isPressed: false
3856
+ }
3857
+ }
3858
+ );
3859
+ var indicatorPillVariants = classVarianceAuthority.cva(
3860
+ [
3861
+ "absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2",
3862
+ "w-16 h-8",
3863
+ "rounded-full bg-secondary-container",
3864
+ "transition-[transform,opacity] duration-medium2 ease-emphasized",
3865
+ "origin-center"
3866
+ ],
3867
+ {
3868
+ variants: {
3869
+ isActive: {
3870
+ true: ["scale-x-100 opacity-100"],
3871
+ false: ["scale-x-0 opacity-0"]
3872
+ }
3873
+ },
3874
+ defaultVariants: {
3875
+ isActive: false
3876
+ }
3877
+ }
3878
+ );
3879
+ var badgeVariants = classVarianceAuthority.cva(
3880
+ [
3881
+ "absolute",
3882
+ "flex items-center justify-center",
3883
+ "bg-error text-on-error",
3884
+ "font-medium leading-none",
3885
+ // MD3 label-small
3886
+ "text-[0.6875rem]"
3887
+ ],
3888
+ {
3889
+ variants: {
3890
+ isDot: {
3891
+ true: [
3892
+ // Dot: 6dp diameter, top-right of icon
3893
+ "top-0 right-0.5 z-10",
3894
+ "w-1.5 h-1.5 min-w-0 rounded-full"
3895
+ ],
3896
+ false: [
3897
+ // Numeric: pill shape, top-right of icon
3898
+ "-top-1 left-3 z-10",
3899
+ "min-w-[1rem] h-4 px-1 rounded-full"
3900
+ ]
3901
+ }
3902
+ },
3903
+ defaultVariants: {
3904
+ isDot: false
3905
+ }
3906
+ }
3907
+ );
3908
+ var iconWrapperVariants = classVarianceAuthority.cva([
3909
+ "relative z-10 flex items-center justify-center",
3910
+ "w-6 h-6"
3911
+ ]);
3912
+ var labelVariants = classVarianceAuthority.cva([
3913
+ "mt-1 select-none",
3914
+ "text-label-medium",
3915
+ "transition-colors duration-short2 ease-standard",
3916
+ "truncate max-w-full"
3917
+ ]);
3918
+ var MIN_ITEMS = 3;
3919
+ var MAX_ITEMS = 5;
3920
+ function validateItemCount(count) {
3921
+ if (process.env.NODE_ENV !== "production" && (count < MIN_ITEMS || count > MAX_ITEMS)) {
3922
+ console.warn(
3923
+ `[NavigationBar] MD3 Navigation Bar requires between ${MIN_ITEMS} and ${MAX_ITEMS} destination items. Received ${count}. See: https://m3.material.io/components/navigation-bar/overview`
3924
+ );
3925
+ }
3926
+ }
3927
+ function getBadgeText(badge) {
3928
+ if (badge === void 0 || badge === 0) return null;
3929
+ if (badge === true) return null;
3930
+ if (badge > 999) return "999+";
3931
+ return String(badge);
3932
+ }
3933
+ function isBadgeVisible(badge) {
3934
+ if (badge === void 0) return false;
3935
+ if (badge === 0) return false;
3936
+ return true;
3937
+ }
3938
+ function ItemVisual({ config, isActive, hideLabels, disableRipple }) {
3939
+ const isItemDisabled = config.isDisabled === true;
3940
+ const { onMouseDown, ripples } = useRipple({
3941
+ ...disableRipple || isItemDisabled ? { disabled: true } : {}
3942
+ });
3943
+ const showBadge = isBadgeVisible(config.badge);
3944
+ const isDot = config.badge === true;
3945
+ const badgeText = getBadgeText(config.badge);
3946
+ return (
3947
+ // Overflow-hidden wrapper required for ripple containment.
3948
+ // pointer-events-none is intentional: the parent <button> handles all interaction.
3949
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
3950
+ /* @__PURE__ */ jsxRuntime.jsxs(
3951
+ "span",
3952
+ {
3953
+ onMouseDown,
3954
+ className: "pointer-events-none relative flex h-full w-full flex-col items-center justify-center overflow-hidden",
3955
+ children: [
3956
+ ripples,
3957
+ /* @__PURE__ */ jsxRuntime.jsx(
3958
+ "span",
3959
+ {
3960
+ "data-indicator-pill": true,
3961
+ "data-active": isActive,
3962
+ "aria-hidden": "true",
3963
+ className: cn(indicatorPillVariants({ isActive }), !hideLabels && "-mt-3.5")
3964
+ }
3965
+ ),
3966
+ /* @__PURE__ */ jsxRuntime.jsxs(
3967
+ "span",
3968
+ {
3969
+ "data-icon": true,
3970
+ className: cn(
3971
+ iconWrapperVariants(),
3972
+ isActive ? "text-on-secondary-container" : "text-on-surface-variant"
3973
+ ),
3974
+ children: [
3975
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 flex h-6 w-6 items-center justify-center", children: config.icon }),
3976
+ showBadge && /* @__PURE__ */ jsxRuntime.jsx(
3977
+ "span",
3978
+ {
3979
+ "data-badge": true,
3980
+ "data-badge-dot": isDot || void 0,
3981
+ "aria-label": isDot ? "notification" : badgeText ? `${badgeText} notifications` : void 0,
3982
+ "aria-live": "polite",
3983
+ className: cn(badgeVariants({ isDot })),
3984
+ children: isDot ? null : badgeText
3985
+ }
3986
+ )
3987
+ ]
3988
+ }
3989
+ ),
3990
+ !hideLabels && config.label && /* @__PURE__ */ jsxRuntime.jsx(
3991
+ "span",
3992
+ {
3993
+ "data-label": true,
3994
+ className: cn(labelVariants(), isActive ? "text-on-surface" : "text-on-surface-variant"),
3995
+ children: config.label
3996
+ }
3997
+ )
3998
+ ]
3999
+ }
4000
+ )
4001
+ );
4002
+ }
4003
+ var NavigationBar = react.forwardRef(
4004
+ ({
4005
+ items,
4006
+ activeKey,
4007
+ defaultActiveKey,
4008
+ onActiveChange,
4009
+ hideLabels = false,
4010
+ "aria-label": ariaLabel,
4011
+ disableRipple = false,
4012
+ className
4013
+ }, ref) => {
4014
+ validateItemCount(items.length);
4015
+ return /* @__PURE__ */ jsxRuntime.jsx(
4016
+ HeadlessNavigationBar,
4017
+ {
4018
+ ref,
4019
+ items,
4020
+ ...activeKey !== void 0 ? { selectedKey: activeKey } : {},
4021
+ ...defaultActiveKey !== void 0 ? { defaultSelectedKey: defaultActiveKey } : {},
4022
+ ...onActiveChange ? { onSelectionChange: onActiveChange } : {},
4023
+ "aria-label": ariaLabel,
4024
+ className: cn(navigationBarVariants(), className),
4025
+ renderItem: (config) => (
4026
+ // HeadlessNavigationBarItem renders the <button role="tab"> with all
4027
+ // ARIA semantics. ItemVisual renders the icon/pill/badge/label inside
4028
+ // — no nested <button> elements.
4029
+ /* @__PURE__ */ jsxRuntime.jsx(
4030
+ HeadlessNavigationBarItem,
4031
+ {
4032
+ itemKey: config.key,
4033
+ ...config["aria-label"] !== void 0 ? { "aria-label": config["aria-label"] } : {},
4034
+ className: cn(
4035
+ "relative flex flex-1 flex-col items-center justify-center",
4036
+ "cursor-pointer outline-none select-none",
4037
+ "duration-short2 ease-standard transition-colors",
4038
+ // State layer pseudo-element
4039
+ "before:absolute before:inset-0 before:rounded-none",
4040
+ "before:bg-on-surface-variant before:opacity-0",
4041
+ "before:duration-short2 before:ease-standard before:transition-opacity",
4042
+ "hover:before:opacity-8",
4043
+ "active:before:opacity-12",
4044
+ // Focus ring
4045
+ "focus-visible:outline-primary focus-visible:outline-2 focus-visible:outline-offset-2",
4046
+ // Disabled styling
4047
+ config.isDisabled && "pointer-events-none cursor-not-allowed opacity-38"
4048
+ ),
4049
+ children: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsx(
4050
+ ItemVisual,
4051
+ {
4052
+ config,
4053
+ isActive: isSelected,
4054
+ hideLabels,
4055
+ disableRipple
4056
+ }
4057
+ )
4058
+ },
4059
+ config.key
4060
+ )
4061
+ )
4062
+ }
4063
+ );
4064
+ }
4065
+ );
4066
+ NavigationBar.displayName = "NavigationBar";
4067
+ function getBadgeContent(badge) {
4068
+ if (badge === true) return null;
4069
+ if (badge === 0) return "";
4070
+ if (badge > 999) return "999+";
4071
+ return String(badge);
4072
+ }
4073
+ function isBadgeVisible2(badge) {
4074
+ if (badge === void 0) return false;
4075
+ if (badge === 0) return false;
4076
+ return true;
4077
+ }
4078
+ var NavigationBarItem = react.forwardRef(
4079
+ ({
4080
+ itemKey: _itemKey,
4081
+ icon,
4082
+ label,
4083
+ badge,
4084
+ isActive = false,
4085
+ hideLabels = false,
4086
+ isDisabled = false,
4087
+ disableRipple = false,
4088
+ className,
4089
+ "aria-label": ariaLabel,
4090
+ // Spread remaining props (e.g. tabProps from useTab)
4091
+ ...rest
4092
+ }, ref) => {
4093
+ const { onMouseDown, ripples } = useRipple({
4094
+ ...disableRipple || isDisabled ? { disabled: true } : {}
4095
+ });
4096
+ const showBadge = isBadgeVisible2(badge);
4097
+ const isDot = badge === true;
4098
+ const badgeContent = badge !== void 0 ? getBadgeContent(badge) : null;
4099
+ return /* @__PURE__ */ jsxRuntime.jsx(
4100
+ "button",
4101
+ {
4102
+ type: "button",
4103
+ ref,
4104
+ onMouseDown,
4105
+ disabled: isDisabled,
4106
+ "aria-label": ariaLabel,
4107
+ className: cn(
4108
+ navigationBarItemVariants({
4109
+ isActive,
4110
+ isDisabled
4111
+ }),
4112
+ className
4113
+ ),
4114
+ ...rest,
4115
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-full w-full flex-col items-center justify-center overflow-hidden rounded-none", children: [
4116
+ ripples,
4117
+ /* @__PURE__ */ jsxRuntime.jsx(
4118
+ "span",
4119
+ {
4120
+ "data-indicator-pill": true,
4121
+ "data-active": isActive,
4122
+ className: cn(indicatorPillVariants({ isActive }), !hideLabels && "-mt-3.5"),
4123
+ "aria-hidden": "true"
4124
+ }
4125
+ ),
4126
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-icon": true, className: cn(iconWrapperVariants()), children: [
4127
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 flex h-6 w-6 items-center justify-center", children: icon }),
4128
+ showBadge && /* @__PURE__ */ jsxRuntime.jsx(
4129
+ "span",
4130
+ {
4131
+ "data-badge": true,
4132
+ "data-badge-dot": isDot || void 0,
4133
+ "aria-label": isDot ? "notification" : badgeContent ? `${badgeContent} notifications` : void 0,
4134
+ "aria-live": "polite",
4135
+ className: cn(badgeVariants({ isDot })),
4136
+ children: isDot ? null : badgeContent
4137
+ }
4138
+ )
4139
+ ] }),
4140
+ !hideLabels && label && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-label": true, className: cn(labelVariants()), children: label })
4141
+ ] })
4142
+ }
4143
+ );
4144
+ }
4145
+ );
4146
+ NavigationBarItem.displayName = "NavigationBarItem";
4147
+ var DrawerContext = react.createContext(null);
4148
+ var HeadlessDrawer = react.forwardRef(
4149
+ ({
4150
+ variant = "standard",
4151
+ open,
4152
+ defaultOpen = false,
4153
+ onOpenChange,
4154
+ "aria-label": ariaLabel,
4155
+ children,
4156
+ className,
4157
+ scrimClassName,
4158
+ disableRipple = false
4159
+ }, ref) => {
4160
+ const state = reactStately.useOverlayTriggerState({
4161
+ ...open !== void 0 ? { isOpen: open } : {},
4162
+ ...defaultOpen !== void 0 ? { defaultOpen } : {},
4163
+ ...onOpenChange !== void 0 ? { onOpenChange } : {}
4164
+ });
4165
+ const isOpen = state.isOpen;
4166
+ const close = react.useCallback(() => {
4167
+ state.close();
4168
+ }, [state]);
4169
+ const contextValue = {
4170
+ isOpen,
4171
+ close,
4172
+ disableRipple
4173
+ };
4174
+ if (variant === "modal") {
4175
+ return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx("nav", { ref, role: "navigation", "aria-label": ariaLabel, children: isOpen && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4176
+ /* @__PURE__ */ jsxRuntime.jsx(
4177
+ "div",
4178
+ {
4179
+ "data-testid": "drawer-scrim",
4180
+ className: scrimClassName,
4181
+ onClick: () => state.close(),
4182
+ "aria-hidden": "true"
4183
+ }
4184
+ ),
4185
+ /* @__PURE__ */ jsxRuntime.jsx(reactAria.FocusScope, { contain: true, restoreFocus: true, autoFocus: true, children: /* @__PURE__ */ jsxRuntime.jsx(
4186
+ ModalDrawerPanel,
4187
+ {
4188
+ ariaLabel,
4189
+ onClose: () => state.close(),
4190
+ className,
4191
+ children
4192
+ }
4193
+ ) })
4194
+ ] }) }) });
4195
+ }
4196
+ return /* @__PURE__ */ jsxRuntime.jsx(DrawerContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
4197
+ "nav",
4198
+ {
4199
+ ref,
4200
+ role: "navigation",
4201
+ "aria-label": ariaLabel,
4202
+ className,
4203
+ children
4204
+ }
4205
+ ) });
4206
+ }
4207
+ );
4208
+ HeadlessDrawer.displayName = "HeadlessDrawer";
4209
+ var ModalDrawerPanel = ({
4210
+ ariaLabel,
4211
+ onClose,
4212
+ className,
4213
+ children
4214
+ }) => {
4215
+ const panelRef = react.useRef(null);
4216
+ reactAria.usePreventScroll();
4217
+ const { dialogProps } = reactAria.useDialog({ "aria-label": ariaLabel }, panelRef);
4218
+ const { overlayProps } = reactAria.useOverlay(
4219
+ {
4220
+ isOpen: true,
4221
+ onClose,
4222
+ isDismissable: true,
4223
+ shouldCloseOnBlur: false
4224
+ },
4225
+ panelRef
4226
+ );
4227
+ return /* @__PURE__ */ jsxRuntime.jsx(
4228
+ "div",
4229
+ {
4230
+ ...utils.mergeProps(overlayProps, dialogProps),
4231
+ ref: panelRef,
4232
+ className,
4233
+ "aria-modal": "true",
4234
+ children
4235
+ }
4236
+ );
4237
+ };
4238
+ ModalDrawerPanel.displayName = "ModalDrawerPanel";
4239
+ var HeadlessDrawerItem = react.forwardRef(
4240
+ ({
4241
+ href,
4242
+ isActive = false,
4243
+ children,
4244
+ className,
4245
+ isDisabled,
4246
+ onMouseDown,
4247
+ onPress,
4248
+ onPressStart,
4249
+ onPressEnd,
4250
+ onPressChange,
4251
+ onPressUp,
4252
+ ...restProps
4253
+ }, forwardedRef) => {
4254
+ const internalRef = react.useRef(null);
4255
+ const { isFocusVisible, focusProps } = reactAria.useFocusRing();
4256
+ if (href) {
4257
+ const linkRef = forwardedRef ?? internalRef;
4258
+ const { linkProps } = reactAria.useLink(
4259
+ {
4260
+ href,
4261
+ ...isDisabled !== void 0 ? { isDisabled } : {},
4262
+ ...onPress !== void 0 ? { onPress } : {}
4263
+ },
4264
+ linkRef
4265
+ );
4266
+ return /* @__PURE__ */ jsxRuntime.jsx(
4267
+ "a",
4268
+ {
4269
+ ...utils.mergeProps(linkProps, focusProps, { onMouseDown }),
4270
+ ref: linkRef,
4271
+ href,
4272
+ className,
4273
+ "aria-current": isActive ? "page" : void 0,
4274
+ "data-focus-visible": isFocusVisible || void 0,
4275
+ "data-active": isActive || void 0,
4276
+ children
4277
+ }
4278
+ );
4279
+ }
4280
+ const buttonRef = forwardedRef ?? internalRef;
4281
+ const { buttonProps } = reactAria.useButton(
4282
+ {
4283
+ ...restProps,
4284
+ ...isDisabled !== void 0 ? { isDisabled } : {},
4285
+ ...onPress !== void 0 ? { onPress } : {},
4286
+ ...onPressStart !== void 0 ? { onPressStart } : {},
4287
+ ...onPressEnd !== void 0 ? { onPressEnd } : {},
4288
+ ...onPressChange !== void 0 ? { onPressChange } : {},
4289
+ ...onPressUp !== void 0 ? { onPressUp } : {},
4290
+ elementType: "button"
4291
+ },
4292
+ buttonRef
4293
+ );
4294
+ return /* @__PURE__ */ jsxRuntime.jsx(
4295
+ "button",
4296
+ {
4297
+ type: "button",
4298
+ ...utils.mergeProps(buttonProps, focusProps, { onMouseDown }),
4299
+ ref: buttonRef,
4300
+ className,
4301
+ "aria-current": isActive ? "page" : void 0,
4302
+ "data-focus-visible": isFocusVisible || void 0,
4303
+ "data-active": isActive || void 0,
4304
+ children
4305
+ }
4306
+ );
4307
+ }
4308
+ );
4309
+ HeadlessDrawerItem.displayName = "HeadlessDrawerItem";
4310
+ var drawerVariants = classVarianceAuthority.cva(
4311
+ [
4312
+ // Layout
4313
+ "fixed top-0 left-0 h-full w-drawer",
4314
+ "flex flex-col overflow-y-auto",
4315
+ // Stacking and shape
4316
+ "z-50",
4317
+ "rounded-r-xl",
4318
+ // Slide animation (transition applies to all open/closed state changes)
4319
+ "transition-transform duration-medium4 ease-emphasized-decelerate",
4320
+ // Focus outline removal (focus management handled by FocusScope / React Aria)
4321
+ "outline-none",
4322
+ // Padding for content spacing
4323
+ "px-3"
4324
+ ],
4325
+ {
4326
+ variants: {
4327
+ /**
4328
+ * Structural variant — drives surface color and elevation.
4329
+ * - `standard`: inline nav panel, lower-elevation surface
4330
+ * - `modal`: overlay dialog with elevation shadow
4331
+ */
4332
+ variant: {
4333
+ standard: ["bg-surface-container-low"],
4334
+ modal: ["bg-surface-container", "shadow-elevation-1"]
4335
+ },
4336
+ /**
4337
+ * Open/closed state — drives translation.
4338
+ * - `true`: drawer visible (`translate-x-0`)
4339
+ * - `false`: drawer off-screen (`-translate-x-full`)
4340
+ */
4341
+ open: {
4342
+ true: ["translate-x-0"],
4343
+ false: ["-translate-x-full"]
4344
+ }
4345
+ },
4346
+ defaultVariants: {
4347
+ variant: "standard",
4348
+ open: false
4349
+ }
4350
+ }
4351
+ );
4352
+ var drawerItemVariants = classVarianceAuthority.cva(
4353
+ [
4354
+ // Layout
4355
+ "relative flex w-full items-center gap-3",
4356
+ "h-14 px-4",
4357
+ "rounded-full",
4358
+ // Typography
4359
+ "text-label-large",
4360
+ // Interaction
4361
+ "cursor-pointer select-none outline-none",
4362
+ // State layer pseudo-element
4363
+ "before:absolute before:inset-0 before:rounded-full",
4364
+ "before:transition-opacity before:duration-short2 before:ease-standard",
4365
+ "before:opacity-0",
4366
+ // Hover and focus visible state layers
4367
+ "hover:before:opacity-8",
4368
+ "focus-visible:before:opacity-12",
4369
+ // Active pressed state
4370
+ "active:before:opacity-12",
4371
+ // Transition for color changes
4372
+ "transition-colors duration-short2 ease-standard"
4373
+ ],
4374
+ {
4375
+ variants: {
4376
+ /**
4377
+ * Whether this item is the currently active destination.
4378
+ * Controls background, text color, and icon color per MD3 spec.
4379
+ */
4380
+ isActive: {
4381
+ true: [
4382
+ "bg-secondary-container",
4383
+ "text-on-secondary-container",
4384
+ "before:bg-on-secondary-container"
4385
+ ],
4386
+ false: ["bg-transparent", "text-on-surface-variant", "before:bg-on-surface-variant"]
4387
+ },
4388
+ /**
4389
+ * Whether the item is disabled.
4390
+ * Applies `opacity-38` per MD3 disabled state spec.
4391
+ */
4392
+ isDisabled: {
4393
+ true: ["opacity-38 cursor-not-allowed pointer-events-none"],
4394
+ false: []
4395
+ }
4396
+ },
4397
+ defaultVariants: {
4398
+ isActive: false,
4399
+ isDisabled: false
4400
+ }
4401
+ }
4402
+ );
4403
+ var scrimVariants = classVarianceAuthority.cva([
4404
+ "fixed inset-0 z-40",
4405
+ "bg-scrim opacity-32",
4406
+ "transition-opacity duration-medium2 ease-standard"
4407
+ ]);
4408
+ var drawerSectionVariants = classVarianceAuthority.cva(["flex flex-col w-full"]);
4409
+ var drawerSectionHeaderVariants = classVarianceAuthority.cva([
4410
+ "px-4 pt-4 pb-2",
4411
+ "text-title-small text-on-surface-variant",
4412
+ "select-none"
4413
+ ]);
4414
+ var drawerDividerVariants = classVarianceAuthority.cva(["border-t border-outline-variant", "mx-4 my-2"]);
4415
+ var Drawer = react.forwardRef(
4416
+ ({
4417
+ variant = "standard",
4418
+ open,
4419
+ defaultOpen = false,
4420
+ onOpenChange,
4421
+ "aria-label": ariaLabel,
4422
+ children,
4423
+ className,
4424
+ disableRipple = false,
4425
+ ...restProps
4426
+ }, ref) => {
4427
+ const isOpen = open ?? defaultOpen;
4428
+ const drawerPanelClass = cn(
4429
+ drawerVariants({
4430
+ variant,
4431
+ open: isOpen
4432
+ }),
4433
+ className
4434
+ );
4435
+ const scrimClass = scrimVariants();
4436
+ return /* @__PURE__ */ jsxRuntime.jsx(
4437
+ HeadlessDrawer,
4438
+ {
4439
+ ref,
4440
+ variant,
4441
+ ...open !== void 0 ? { open } : {},
4442
+ ...defaultOpen !== void 0 ? { defaultOpen } : {},
4443
+ ...onOpenChange !== void 0 ? { onOpenChange } : {},
4444
+ "aria-label": ariaLabel,
4445
+ className: drawerPanelClass,
4446
+ scrimClassName: scrimClass,
4447
+ disableRipple,
4448
+ ...restProps,
4449
+ children
4450
+ }
4451
+ );
4452
+ }
4453
+ );
4454
+ Drawer.displayName = "Drawer";
4455
+ var DrawerItem = react.forwardRef(
4456
+ ({
4457
+ href,
4458
+ icon,
4459
+ label,
4460
+ badge,
4461
+ secondaryText,
4462
+ isActive = false,
4463
+ isDisabled = false,
4464
+ disableRipple = false,
4465
+ className,
4466
+ onPress,
4467
+ onPressStart,
4468
+ onPressEnd,
4469
+ onPressChange,
4470
+ onPressUp,
4471
+ ...restProps
4472
+ }, ref) => {
4473
+ const isItemDisabled = isDisabled;
4474
+ const { onMouseDown: handleRipple, ripples } = useRipple({
4475
+ disabled: isItemDisabled || disableRipple
4476
+ });
4477
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4478
+ HeadlessDrawerItem,
4479
+ {
4480
+ ...restProps,
4481
+ ref,
4482
+ ...href !== void 0 ? { href } : {},
4483
+ isActive,
4484
+ ...isItemDisabled !== void 0 ? { isDisabled: isItemDisabled } : {},
4485
+ ...onPress !== void 0 ? { onPress } : {},
4486
+ ...onPressStart !== void 0 ? { onPressStart } : {},
4487
+ ...onPressEnd !== void 0 ? { onPressEnd } : {},
4488
+ ...onPressChange !== void 0 ? { onPressChange } : {},
4489
+ ...onPressUp !== void 0 ? { onPressUp } : {},
4490
+ onMouseDown: handleRipple,
4491
+ className: cn(
4492
+ drawerItemVariants({
4493
+ isActive,
4494
+ isDisabled: isItemDisabled
4495
+ }),
4496
+ className
4497
+ ),
4498
+ children: [
4499
+ ripples,
4500
+ icon && /* @__PURE__ */ jsxRuntime.jsx(
4501
+ "span",
4502
+ {
4503
+ className: "relative z-10 flex shrink-0 items-center justify-center",
4504
+ "aria-hidden": "true",
4505
+ children: icon
4506
+ }
4507
+ ),
4508
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 flex min-w-0 flex-1 flex-col text-left", children: [
4509
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: label }),
4510
+ secondaryText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-body-small truncate opacity-70", children: secondaryText })
4511
+ ] }),
4512
+ badge && /* @__PURE__ */ jsxRuntime.jsx(
4513
+ "span",
4514
+ {
4515
+ className: "relative z-10 ml-auto flex shrink-0 items-center pr-2",
4516
+ "aria-hidden": "true",
4517
+ children: badge
4518
+ }
4519
+ )
4520
+ ]
4521
+ }
4522
+ );
4523
+ }
4524
+ );
4525
+ DrawerItem.displayName = "DrawerItem";
4526
+ var DrawerSection = react.forwardRef(
4527
+ ({ header, children, showDivider = false, className }, ref) => {
4528
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: cn(drawerSectionVariants(), className), children: [
4529
+ showDivider && /* @__PURE__ */ jsxRuntime.jsx("hr", { role: "separator", "aria-hidden": "true", className: drawerDividerVariants() }),
4530
+ header && /* @__PURE__ */ jsxRuntime.jsx("span", { className: drawerSectionHeaderVariants(), children: header }),
4531
+ children
4532
+ ] });
4533
+ }
4534
+ );
4535
+ DrawerSection.displayName = "DrawerSection";
4536
+ var progressContainerVariants = classVarianceAuthority.cva(["inline-flex", "flex-col", "gap-1"], {
4537
+ variants: {
4538
+ /**
4539
+ * The visual type of the indicator.
4540
+ */
4541
+ type: {
4542
+ linear: "w-full",
4543
+ circular: "items-center justify-center w-auto"
4544
+ }
4545
+ },
4546
+ defaultVariants: {
4547
+ type: "linear"
4548
+ }
4549
+ });
4550
+ var progressTrackVariants = classVarianceAuthority.cva([
4551
+ "relative",
4552
+ "w-full",
4553
+ "h-1",
4554
+ // MD3: 4dp track height
4555
+ "rounded-full",
4556
+ // MD3: full corner radius
4557
+ "overflow-hidden",
4558
+ "bg-surface-container-highest"
4559
+ // MD3: inactive track color
4560
+ ]);
4561
+ var progressIndicatorVariants = classVarianceAuthority.cva([
4562
+ "absolute",
4563
+ "left-0",
4564
+ "top-0",
4565
+ "h-full",
4566
+ "rounded-full",
4567
+ "bg-primary",
4568
+ // MD3: active track color
4569
+ "transition-[width]",
4570
+ "duration-medium4",
4571
+ // MD3: 400ms for value transitions
4572
+ "ease-standard"
4573
+ // MD3: cubic-bezier(0.2, 0, 0, 1)
4574
+ ]);
4575
+ var progressStopIndicatorVariants = classVarianceAuthority.cva([
4576
+ "absolute",
4577
+ "right-0",
4578
+ "top-1/2",
4579
+ "-translate-y-1/2",
4580
+ "w-1",
4581
+ "h-1",
4582
+ "rounded-full",
4583
+ "bg-primary"
4584
+ // MD3: stop indicator uses primary color
4585
+ ]);
4586
+ var progressCircularSizeVariants = classVarianceAuthority.cva(
4587
+ ["relative", "flex", "items-center", "justify-center", "flex-shrink-0"],
4588
+ {
4589
+ variants: {
4590
+ size: {
4591
+ small: "h-6 w-6",
4592
+ // MD3: 24dp
4593
+ medium: "h-12 w-12",
4594
+ // MD3: 48dp (default)
4595
+ large: "h-16 w-16"
4596
+ // MD3: 64dp
4597
+ }
4598
+ },
4599
+ defaultVariants: {
4600
+ size: "medium"
4601
+ }
4602
+ }
4603
+ );
4604
+ var progressLabelVariants = classVarianceAuthority.cva([
4605
+ "text-body-small",
4606
+ // MD3: body-small type scale (12px)
4607
+ "text-on-surface",
4608
+ // MD3: on-surface color role
4609
+ "select-none"
4610
+ ]);
4611
+ var STROKE_WIDTH = 4;
4612
+ var CIRCULAR_SIZE_PX = {
4613
+ small: 24,
4614
+ medium: 48,
4615
+ large: 64
4616
+ };
4617
+ function getCircularGeometry(size) {
4618
+ const diameter = CIRCULAR_SIZE_PX[size];
4619
+ const radius = (diameter - STROKE_WIDTH) / 2;
4620
+ const circumference = 2 * Math.PI * radius;
4621
+ const viewBox = `0 0 ${diameter} ${diameter}`;
4622
+ const cx = diameter / 2;
4623
+ const cy = diameter / 2;
4624
+ return { diameter, radius, circumference, viewBox, cx, cy };
4625
+ }
4626
+ var Progress = react.forwardRef(
4627
+ ({
4628
+ type = "linear",
4629
+ indeterminate = false,
4630
+ size = "medium",
4631
+ className,
4632
+ label,
4633
+ value = 0,
4634
+ minValue = 0,
4635
+ maxValue = 100,
4636
+ ...restProps
4637
+ }, forwardedRef) => {
4638
+ const internalRef = react.useRef(null);
4639
+ const ref = forwardedRef ?? internalRef;
4640
+ const { progressBarProps, labelProps } = reactAria.useProgressBar({
4641
+ label,
4642
+ value,
4643
+ minValue,
4644
+ maxValue,
4645
+ isIndeterminate: indeterminate,
4646
+ ...restProps
4647
+ });
4648
+ const percentage = indeterminate ? 0 : Math.min(100, Math.max(0, (value - minValue) / (maxValue - minValue) * 100));
4649
+ if (process.env.NODE_ENV !== "production") {
4650
+ const ariaProps = restProps;
4651
+ if (!label && !ariaProps["aria-label"] && !ariaProps["aria-labelledby"]) {
4652
+ console.warn(
4653
+ "[Progress] Progress indicator should have a visible label prop or aria-label for accessibility."
4654
+ );
4655
+ }
4656
+ }
4657
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4658
+ "div",
4659
+ {
4660
+ ...progressBarProps,
4661
+ ref,
4662
+ className: cn(progressContainerVariants({ type }), className),
4663
+ children: [
4664
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { ...labelProps, className: cn(progressLabelVariants()), children: label }),
4665
+ type === "linear" ? /* @__PURE__ */ jsxRuntime.jsx(LinearProgress, { percentage, indeterminate }) : /* @__PURE__ */ jsxRuntime.jsx(CircularProgress, { percentage, indeterminate, size })
4666
+ ]
4667
+ }
4668
+ );
4669
+ }
4670
+ );
4671
+ Progress.displayName = "Progress";
4672
+ function LinearProgress({ percentage, indeterminate }) {
4673
+ if (indeterminate) {
4674
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-progress-track": "", className: cn(progressTrackVariants()), children: /* @__PURE__ */ jsxRuntime.jsxs(
4675
+ "div",
4676
+ {
4677
+ "data-progress-indeterminate": "",
4678
+ className: "absolute inset-0 overflow-hidden rounded-full",
4679
+ children: [
4680
+ /* @__PURE__ */ jsxRuntime.jsx(
4681
+ "div",
4682
+ {
4683
+ className: cn(
4684
+ "bg-primary absolute top-0 h-full rounded-full",
4685
+ "animate-progress-linear-indeterminate-1"
4686
+ )
4687
+ }
4688
+ ),
4689
+ /* @__PURE__ */ jsxRuntime.jsx(
4690
+ "div",
4691
+ {
4692
+ className: cn(
4693
+ "bg-primary absolute top-0 h-full rounded-full",
4694
+ "animate-progress-linear-indeterminate-2"
4695
+ )
4696
+ }
4697
+ )
4698
+ ]
4699
+ }
4700
+ ) });
4701
+ }
4702
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-progress-track": "", className: cn(progressTrackVariants()), children: [
4703
+ /* @__PURE__ */ jsxRuntime.jsx(
4704
+ "div",
4705
+ {
4706
+ "data-progress-indicator": "",
4707
+ className: cn(progressIndicatorVariants()),
4708
+ style: { width: `${percentage}%` }
4709
+ }
4710
+ ),
4711
+ /* @__PURE__ */ jsxRuntime.jsx(
4712
+ "div",
4713
+ {
4714
+ "data-stop-indicator": "",
4715
+ className: cn(progressStopIndicatorVariants()),
4716
+ "aria-hidden": "true"
4717
+ }
4718
+ )
4719
+ ] });
4720
+ }
4721
+ function CircularProgress({
4722
+ percentage,
4723
+ indeterminate,
4724
+ size
4725
+ }) {
4726
+ const { radius, circumference, viewBox, cx, cy } = getCircularGeometry(size);
4727
+ const strokeDashoffset = (1 - percentage / 100) * circumference;
4728
+ if (indeterminate) {
4729
+ return /* @__PURE__ */ jsxRuntime.jsx(
4730
+ "div",
4731
+ {
4732
+ "data-progress-size": size,
4733
+ "data-progress-indeterminate": "",
4734
+ className: cn(progressCircularSizeVariants({ size })),
4735
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4736
+ "svg",
4737
+ {
4738
+ viewBox,
4739
+ className: "animate-progress-circular-rotate h-full w-full",
4740
+ "aria-hidden": "true",
4741
+ children: [
4742
+ /* @__PURE__ */ jsxRuntime.jsx(
4743
+ "circle",
4744
+ {
4745
+ cx,
4746
+ cy,
4747
+ r: radius,
4748
+ fill: "none",
4749
+ stroke: "currentColor",
4750
+ strokeWidth: STROKE_WIDTH,
4751
+ className: "text-surface-container-highest"
4752
+ }
4753
+ ),
4754
+ /* @__PURE__ */ jsxRuntime.jsx(
4755
+ "circle",
4756
+ {
4757
+ cx,
4758
+ cy,
4759
+ r: radius,
4760
+ fill: "none",
4761
+ stroke: "currentColor",
4762
+ strokeWidth: STROKE_WIDTH,
4763
+ className: "animate-progress-circular-dash text-primary",
4764
+ strokeLinecap: "round"
4765
+ }
4766
+ )
4767
+ ]
4768
+ }
4769
+ )
4770
+ }
4771
+ );
4772
+ }
4773
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "data-progress-size": size, className: cn(progressCircularSizeVariants({ size })), children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox, className: "h-full w-full -rotate-90", "aria-hidden": "true", children: [
4774
+ /* @__PURE__ */ jsxRuntime.jsx(
4775
+ "circle",
4776
+ {
4777
+ cx,
4778
+ cy,
4779
+ r: radius,
4780
+ fill: "none",
4781
+ stroke: "currentColor",
4782
+ strokeWidth: STROKE_WIDTH,
4783
+ className: "text-surface-container-highest"
4784
+ }
4785
+ ),
4786
+ /* @__PURE__ */ jsxRuntime.jsx(
4787
+ "circle",
4788
+ {
4789
+ cx,
4790
+ cy,
4791
+ r: radius,
4792
+ fill: "none",
4793
+ stroke: "currentColor",
4794
+ strokeWidth: STROKE_WIDTH,
4795
+ strokeLinecap: "round",
4796
+ className: "text-primary duration-medium4 ease-standard transition-[stroke-dashoffset]",
4797
+ strokeDasharray: circumference,
4798
+ strokeDashoffset
4799
+ }
4800
+ )
4801
+ ] }) });
4802
+ }
4803
+ var ProgressHeadless = react.forwardRef(
4804
+ ({
4805
+ type = "linear",
4806
+ indeterminate = false,
4807
+ size = "medium",
4808
+ className,
4809
+ children,
4810
+ renderProgress,
4811
+ label,
4812
+ value = 0,
4813
+ minValue = 0,
4814
+ maxValue = 100,
4815
+ ...restProps
4816
+ }, forwardedRef) => {
4817
+ const internalRef = react.useRef(null);
4818
+ const ref = forwardedRef ?? internalRef;
4819
+ const { progressBarProps, labelProps } = reactAria.useProgressBar({
4820
+ label,
4821
+ value,
4822
+ minValue,
4823
+ maxValue,
4824
+ isIndeterminate: indeterminate,
4825
+ ...restProps
4826
+ });
4827
+ const percentage = indeterminate ? 0 : (value - minValue) / (maxValue - minValue) * 100;
4828
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...progressBarProps, ref, className, children: [
4829
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { ...labelProps, children: label }),
4830
+ renderProgress?.({
4831
+ percentage,
4832
+ isIndeterminate: indeterminate,
4833
+ type,
4834
+ size
4835
+ }),
4836
+ children
4837
+ ] });
4838
+ }
4839
+ );
4840
+ ProgressHeadless.displayName = "ProgressHeadless";
4841
+ var MenuContext = react.createContext(null);
4842
+ function useMenuContext() {
4843
+ return react.useContext(MenuContext);
4844
+ }
4845
+ function TriggerBridge({ children }) {
4846
+ const ctx = reactAriaComponents.useSlottedContext(reactAriaComponents.ButtonContext);
4847
+ const localRef = react.useRef(null);
4848
+ const { ref: contextRef, ...ctxProps } = ctx ?? {};
4849
+ const mergedCallbackRef = react.useCallback(
4850
+ (node) => {
4851
+ localRef.current = node;
4852
+ if (!contextRef) return;
4853
+ if (typeof contextRef === "function") {
4854
+ contextRef(node);
4855
+ } else {
4856
+ contextRef.current = node;
4857
+ }
4858
+ },
4859
+ [contextRef]
4860
+ );
4861
+ const { buttonProps } = reactAria.useButton({ ...ctxProps, elementType: "button" }, localRef);
4862
+ if (!react.isValidElement(children)) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
4863
+ return react.cloneElement(
4864
+ children,
4865
+ { ...buttonProps, ref: mergedCallbackRef }
4866
+ );
4867
+ }
4868
+ function HeadlessMenuTrigger({
4869
+ children,
4870
+ placement = "bottom start",
4871
+ shouldFlip = true,
4872
+ ...rest
4873
+ }) {
4874
+ const childrenArray = Array.isArray(children) ? children : [children];
4875
+ const [triggerChild, menuChild] = childrenArray;
4876
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactAriaComponents.MenuTrigger, { ...rest, children: [
4877
+ /* @__PURE__ */ jsxRuntime.jsx(TriggerBridge, { children: triggerChild }),
4878
+ /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Popover, { placement, shouldFlip, offset: 4, children: menuChild })
4879
+ ] });
4880
+ }
4881
+ function HeadlessMenu({
4882
+ className,
4883
+ children,
4884
+ "aria-label": ariaLabel,
4885
+ ...props
4886
+ }) {
4887
+ const menuRef = react.useRef(null);
4888
+ react.useLayoutEffect(() => {
4889
+ if (ariaLabel && menuRef.current) {
4890
+ menuRef.current.removeAttribute("aria-labelledby");
4891
+ }
4892
+ });
4893
+ return /* @__PURE__ */ jsxRuntime.jsx(
4894
+ reactAriaComponents.Menu,
4895
+ {
4896
+ ...props,
4897
+ ref: menuRef,
4898
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
4899
+ className: className ?? "",
4900
+ children
4901
+ }
4902
+ );
4903
+ }
4904
+ HeadlessMenuTrigger.Menu = HeadlessMenu;
4905
+ var HeadlessMenuItem = react.forwardRef(
4906
+ function HeadlessMenuItem2({ children, className, ...props }, ref) {
4907
+ return /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.MenuItem, { ...props, ref, className: className ?? "", children });
4908
+ }
4909
+ );
4910
+ function HeadlessMenuSection({
4911
+ children,
4912
+ "aria-label": ariaLabel,
4913
+ className
4914
+ }) {
4915
+ return /* @__PURE__ */ jsxRuntime.jsx(
4916
+ reactAriaComponents.MenuSection,
4917
+ {
4918
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
4919
+ className: className ?? "",
4920
+ children
4921
+ }
4922
+ );
4923
+ }
4924
+ function HeadlessMenuDivider({
4925
+ className,
4926
+ ...props
4927
+ }) {
4928
+ return /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Separator, { ...props, className: className ?? "" });
4929
+ }
4930
+ var menuContainerVariants = classVarianceAuthority.cva(
4931
+ [
4932
+ // Elevation
4933
+ "shadow-elevation-2",
4934
+ // Width constraints per MD3 spec (112dp min / 280dp max)
4935
+ "min-w-28 max-w-70",
4936
+ // Layout
4937
+ "py-2",
4938
+ // Scroll: show scrollbar when content overflows; max height avoids clipping
4939
+ "overflow-y-auto",
4940
+ "max-h-[calc(var(--visual-viewport-height,100vh)-2rem)]",
4941
+ // Stacking
4942
+ "z-50",
4943
+ // Focus outline handled by React Aria
4944
+ "outline-none",
4945
+ // GPU compositing — promotes menu to its own compositor layer so
4946
+ // scale + opacity animations run without triggering layout reflow.
4947
+ "will-change-[transform,opacity]",
4948
+ // Pointer events blocked during animation to prevent accidental clicks
4949
+ // on menu items while the panel is still animating in or out.
4950
+ "data-[entering]:pointer-events-none data-[exiting]:pointer-events-none",
4951
+ // ── Enter animation ────────────────────────────────────────────────────
4952
+ // @keyframes menu-enter (defined in styles.css): scale(0.8)+opacity:0 →
4953
+ // scale(1)+opacity:1 in 120ms with cubic-bezier(0,0,0.2,1) (standard
4954
+ // decelerate — matches Angular Material's _mat-menu-enter keyframe).
4955
+ "data-[entering]:animate-[menu-enter_120ms_cubic-bezier(0,0,0.2,1)_both]",
4956
+ // ── Exit animation ─────────────────────────────────────────────────────
4957
+ // @keyframes menu-exit (defined in styles.css): opacity:1 → opacity:0
4958
+ // in 100ms after 25ms delay, linear — matches Angular Material's
4959
+ // _mat-menu-exit keyframe (fade-only, no reverse scale).
4960
+ "data-[exiting]:animate-[menu-exit_100ms_25ms_linear_both]",
4961
+ // ── Transform origin (placement-aware) ────────────────────────────────
4962
+ // RAC sets data-placement="bottom|top|left|right" on the Popover element.
4963
+ // Default (bottom): origin at top edge (menu expands downward).
4964
+ "origin-top",
4965
+ // top: origin at bottom edge (menu expands upward)
4966
+ "data-[placement=top]:origin-bottom",
4967
+ // left: origin at right edge
4968
+ "data-[placement=left]:origin-right",
4969
+ // right: origin at left edge
4970
+ "data-[placement=right]:origin-left",
4971
+ // ── Reduced motion ────────────────────────────────────────────────────
4972
+ // Skip both animations entirely for users who prefer reduced motion.
4973
+ "motion-reduce:data-[entering]:animate-none motion-reduce:data-[exiting]:animate-none"
4974
+ ],
4975
+ {
4976
+ variants: {
4977
+ /**
4978
+ * Color scheme — drives the container background.
4979
+ * baseline+standard uses a separate compound variant.
4980
+ */
4981
+ colorScheme: {
4982
+ standard: [],
4983
+ vibrant: []
4984
+ },
4985
+ /**
4986
+ * Visual style — drives corner radius and baseline vs vertical background.
4987
+ */
4988
+ menuStyle: {
4989
+ baseline: ["rounded-xs", "bg-surface-container"],
4990
+ vertical: ["rounded-lg", "bg-surface-container-low"]
4991
+ }
4992
+ },
4993
+ compoundVariants: [
4994
+ // Vertical + vibrant: tertiary container background
4995
+ {
4996
+ menuStyle: "vertical",
4997
+ colorScheme: "vibrant",
4998
+ class: ["bg-tertiary-container"]
4999
+ }
5000
+ ],
5001
+ defaultVariants: {
5002
+ colorScheme: "standard",
5003
+ menuStyle: "baseline"
5004
+ }
5005
+ }
5006
+ );
5007
+ var menuItemVariants = classVarianceAuthority.cva(
5008
+ [
5009
+ // Layout — height set by density context in MenuItem component
5010
+ "relative flex w-full items-center",
5011
+ "px-3 gap-3",
5012
+ // Typography: Body Large per MD3 baseline spec
5013
+ "text-body-large",
5014
+ // Interaction
5015
+ "cursor-pointer select-none outline-none",
5016
+ // State layer pseudo-element
5017
+ "before:absolute before:inset-0 before:rounded-[inherit]",
5018
+ "before:transition-opacity before:duration-short2 before:ease-standard",
5019
+ "before:opacity-0",
5020
+ // Hover state layer
5021
+ "hover:before:opacity-8",
5022
+ // Focus visible state layer
5023
+ "focus-visible:before:opacity-12",
5024
+ // Active pressed state layer
5025
+ "active:before:opacity-12",
5026
+ // Color transition for selection
5027
+ "transition-colors duration-short2 ease-standard"
5028
+ ],
5029
+ {
5030
+ variants: {
5031
+ /**
5032
+ * Disabled state: reduces opacity and blocks interaction.
5033
+ */
5034
+ isDisabled: {
5035
+ true: ["opacity-38 cursor-not-allowed pointer-events-none"],
5036
+ false: []
5037
+ },
5038
+ /**
5039
+ * Selected state: background and text color driven by compound variants.
5040
+ */
5041
+ isSelected: {
5042
+ true: [],
5043
+ false: []
5044
+ },
5045
+ /**
5046
+ * Color scheme: drives default text and state layer colors.
5047
+ * - standard: on-surface text, on-surface state layer
5048
+ * - vibrant (vertical only): on-tertiary-container text + state layer
5049
+ */
5050
+ colorScheme: {
5051
+ standard: ["text-on-surface", "before:bg-on-surface"],
5052
+ vibrant: ["text-on-tertiary-container", "before:bg-on-tertiary-container"]
5053
+ },
5054
+ /**
5055
+ * Visual style: drives corner radius on items (vertical uses rounded-lg
5056
+ * inherited from container, items stay flat inside).
5057
+ */
5058
+ menuStyle: {
5059
+ baseline: [],
5060
+ vertical: []
5061
+ }
5062
+ },
5063
+ compoundVariants: [
5064
+ // ── Baseline selection (both colorSchemes) ──────────────────────────
5065
+ {
5066
+ isSelected: true,
5067
+ menuStyle: "baseline",
5068
+ class: [
5069
+ "bg-surface-container-highest"
5070
+ // text-on-surface already applied by standard colorScheme variant
5071
+ ]
5072
+ },
5073
+ // ── Vertical + Standard selection ───────────────────────────────────
5074
+ {
5075
+ isSelected: true,
5076
+ menuStyle: "vertical",
5077
+ colorScheme: "standard",
5078
+ class: [
5079
+ "bg-tertiary-container",
5080
+ "text-on-tertiary-container",
5081
+ "before:bg-on-tertiary-container"
5082
+ ]
5083
+ },
5084
+ // ── Vertical + Vibrant selection ─────────────────────────────────────
5085
+ {
5086
+ isSelected: true,
5087
+ menuStyle: "vertical",
5088
+ colorScheme: "vibrant",
5089
+ class: ["bg-tertiary", "text-on-tertiary", "before:bg-on-tertiary"]
5090
+ }
5091
+ ],
5092
+ defaultVariants: {
5093
+ isDisabled: false,
5094
+ isSelected: false,
5095
+ colorScheme: "standard",
5096
+ menuStyle: "baseline"
5097
+ }
5098
+ }
5099
+ );
5100
+ var menuSectionVariants = classVarianceAuthority.cva(["flex flex-col w-full"]);
5101
+ var menuSectionHeaderVariants = classVarianceAuthority.cva([
5102
+ "px-3 pt-2 pb-1",
5103
+ "text-title-small text-on-surface-variant",
5104
+ "select-none"
5105
+ ]);
5106
+ var menuDividerVariants = classVarianceAuthority.cva(["border-t border-outline-variant", "my-2 mx-0"]);
5107
+ classVarianceAuthority.cva(["h-2 w-full"]);
5108
+ var menuItemTrailingTextVariants = classVarianceAuthority.cva([
5109
+ "ml-auto shrink-0 text-label-large text-on-surface-variant",
5110
+ "select-none"
5111
+ ]);
5112
+ var menuItemDescriptionVariants = classVarianceAuthority.cva([
5113
+ "text-body-medium text-on-surface-variant",
5114
+ "select-none"
5115
+ ]);
5116
+ var Menu = react.forwardRef(function Menu2({
5117
+ children,
5118
+ className,
5119
+ colorScheme = "standard",
5120
+ menuStyle = "baseline",
5121
+ density = 0,
5122
+ disableRipple = false,
5123
+ selectionMode,
5124
+ selectedKeys,
5125
+ onSelectionChange,
5126
+ ...props
5127
+ }, _ref) {
5128
+ const close = () => {
5129
+ };
5130
+ const contextValue = {
5131
+ close,
5132
+ disableRipple,
5133
+ colorScheme,
5134
+ menuStyle,
5135
+ density,
5136
+ ...selectionMode !== void 0 ? { selectionMode } : {},
5137
+ ...selectedKeys !== void 0 ? { selectedKeys } : {}
5138
+ };
5139
+ return /* @__PURE__ */ jsxRuntime.jsx(MenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
5140
+ HeadlessMenu,
5141
+ {
5142
+ ...props,
5143
+ ...selectionMode !== void 0 ? { selectionMode } : {},
5144
+ ...selectedKeys !== void 0 ? { selectedKeys } : {},
5145
+ ...onSelectionChange !== void 0 ? { onSelectionChange } : {},
5146
+ className: cn(menuContainerVariants({ colorScheme, menuStyle }), className),
5147
+ children
5148
+ }
5149
+ ) });
5150
+ });
5151
+ function MenuTrigger({
5152
+ children,
5153
+ placement = "bottom start",
5154
+ shouldFlip = true,
5155
+ ...rest
5156
+ }) {
5157
+ return /* @__PURE__ */ jsxRuntime.jsx(HeadlessMenuTrigger, { placement, shouldFlip, ...rest, children });
5158
+ }
5159
+ MenuTrigger.Menu = Menu;
5160
+ function CheckIcon() {
5161
+ return /* @__PURE__ */ jsxRuntime.jsx(
5162
+ "svg",
5163
+ {
5164
+ "data-testid": "check-icon",
5165
+ xmlns: "http://www.w3.org/2000/svg",
5166
+ viewBox: "0 0 24 24",
5167
+ fill: "currentColor",
5168
+ "aria-hidden": "true",
5169
+ focusable: "false",
5170
+ className: "h-full w-full",
5171
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" })
5172
+ }
5173
+ );
5174
+ }
5175
+ var DENSITY_HEIGHT = {
5176
+ 0: "h-12",
5177
+ [-1]: "h-11",
5178
+ [-2]: "h-10",
5179
+ [-3]: "h-9"
5180
+ };
5181
+ var MenuItem = react.forwardRef(function MenuItem2({
5182
+ children,
5183
+ leadingIcon,
5184
+ trailingIcon,
5185
+ trailingText,
5186
+ description,
5187
+ badge,
5188
+ className,
5189
+ disableRipple: itemDisableRipple,
5190
+ ...props
5191
+ }, ref) {
5192
+ const ctx = useMenuContext();
5193
+ const disableRipple = itemDisableRipple ?? ctx?.disableRipple ?? false;
5194
+ const colorScheme = ctx?.colorScheme ?? "standard";
5195
+ const menuStyle = ctx?.menuStyle ?? "baseline";
5196
+ const density = ctx?.density ?? 0;
5197
+ const selectionMode = ctx?.selectionMode;
5198
+ const heightClass = DENSITY_HEIGHT[density];
5199
+ const isSelectionMenu = selectionMode != null;
5200
+ const { ripples, onMouseDown } = useRipple({ disabled: disableRipple });
5201
+ const computeClassName = ({ isDisabled, isSelected }) => cn(
5202
+ menuItemVariants({
5203
+ isDisabled,
5204
+ isSelected: isSelected ?? false,
5205
+ colorScheme,
5206
+ menuStyle
5207
+ }),
5208
+ // Height: auto when description is present (multi-line), otherwise density
5209
+ description ? "min-h-12 py-2 h-auto items-start" : heightClass,
5210
+ className
5211
+ );
5212
+ return /* @__PURE__ */ jsxRuntime.jsx(
5213
+ HeadlessMenuItem,
5214
+ {
5215
+ ...props,
5216
+ ref,
5217
+ className: computeClassName,
5218
+ onMouseDown,
5219
+ children: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5220
+ !disableRipple && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute inset-0 z-0 overflow-hidden rounded-[inherit]", children: ripples }),
5221
+ (leadingIcon != null || isSelectionMenu) && /* @__PURE__ */ jsxRuntime.jsx(
5222
+ "span",
5223
+ {
5224
+ className: "text-on-surface-variant relative z-10 flex h-6 w-6 shrink-0 items-center justify-center",
5225
+ "aria-hidden": "true",
5226
+ children: isSelectionMenu && leadingIcon == null ? isSelected ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null : leadingIcon
5227
+ }
5228
+ ),
5229
+ description != null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 flex min-w-0 flex-1 flex-col", children: [
5230
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-body-large", children }),
5231
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: menuItemDescriptionVariants(), children: description })
5232
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-body-large relative z-10 min-w-0 flex-1", children }),
5233
+ badge != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 shrink-0", children: badge }),
5234
+ trailingIcon != null && trailingText == null && /* @__PURE__ */ jsxRuntime.jsx(
5235
+ "span",
5236
+ {
5237
+ className: "text-on-surface-variant relative z-10 ml-auto flex h-6 w-6 shrink-0 items-center justify-center",
5238
+ "aria-hidden": "true",
5239
+ children: trailingIcon
5240
+ }
5241
+ ),
5242
+ trailingText != null && trailingIcon == null && /* @__PURE__ */ jsxRuntime.jsx(
5243
+ "span",
5244
+ {
5245
+ className: cn(menuItemTrailingTextVariants(), "relative z-10"),
5246
+ "aria-keyshortcuts": trailingText,
5247
+ children: trailingText
5248
+ }
5249
+ )
5250
+ ] })
5251
+ }
5252
+ );
5253
+ });
5254
+ function MenuSection({
5255
+ children,
5256
+ header,
5257
+ showDivider = false,
5258
+ className,
5259
+ "aria-label": ariaLabel
5260
+ }) {
5261
+ const sectionAriaLabel = ariaLabel ?? header;
5262
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5263
+ showDivider && /* @__PURE__ */ jsxRuntime.jsx(HeadlessMenuDivider, { className: menuDividerVariants() }),
5264
+ /* @__PURE__ */ jsxRuntime.jsxs(
5265
+ HeadlessMenuSection,
5266
+ {
5267
+ "aria-label": sectionAriaLabel,
5268
+ className: cn(menuSectionVariants(), className),
5269
+ children: [
5270
+ header && /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Header, { className: menuSectionHeaderVariants(), "aria-hidden": "true", children: header }),
5271
+ children
5272
+ ]
5273
+ }
5274
+ )
5275
+ ] });
5276
+ }
5277
+ function MenuDivider({ className }) {
5278
+ return /* @__PURE__ */ jsxRuntime.jsx(HeadlessMenuDivider, { className: cn(menuDividerVariants(), className) });
5279
+ }
5280
+ var SnackbarHeadless = react.forwardRef(
5281
+ function SnackbarHeadless2({
5282
+ message,
5283
+ supportingText,
5284
+ action,
5285
+ showClose,
5286
+ duration = 4e3,
5287
+ severity = "default",
5288
+ position = "bottom-center",
5289
+ onClose,
5290
+ children,
5291
+ className,
5292
+ getAnimationClassName
5293
+ }, ref) {
5294
+ const [animationState, setAnimationState] = react.useState("entering");
5295
+ const remainingRef = react.useRef(duration);
5296
+ const startedAtRef = react.useRef(Date.now());
5297
+ const dismissTimerRef = react.useRef(null);
5298
+ const exitFallbackRef = react.useRef(null);
5299
+ const pausedRef = react.useRef(false);
5300
+ const closedRef = react.useRef(false);
5301
+ const clearDismissTimer = react.useCallback(() => {
5302
+ if (dismissTimerRef.current !== null) {
5303
+ clearTimeout(dismissTimerRef.current);
5304
+ dismissTimerRef.current = null;
5305
+ }
5306
+ }, []);
5307
+ const triggerExit = react.useCallback(() => {
5308
+ if (closedRef.current) return;
5309
+ clearDismissTimer();
5310
+ setAnimationState("exiting");
5311
+ exitFallbackRef.current = setTimeout(() => {
5312
+ if (!closedRef.current) {
5313
+ closedRef.current = true;
5314
+ setAnimationState("exited");
5315
+ onClose?.();
5316
+ }
5317
+ }, 250);
5318
+ }, [clearDismissTimer, onClose]);
5319
+ const startDismissTimer = react.useCallback(
5320
+ (ms) => {
5321
+ if (ms <= 0) return;
5322
+ clearDismissTimer();
5323
+ startedAtRef.current = Date.now();
5324
+ dismissTimerRef.current = setTimeout(triggerExit, ms);
5325
+ },
5326
+ [clearDismissTimer, triggerExit]
5327
+ );
5328
+ react.useEffect(() => {
5329
+ let frameId;
5330
+ frameId = requestAnimationFrame(() => {
5331
+ frameId = requestAnimationFrame(() => {
5332
+ setAnimationState("visible");
5333
+ });
5334
+ });
5335
+ return () => cancelAnimationFrame(frameId);
5336
+ }, []);
5337
+ react.useEffect(() => {
5338
+ if (animationState !== "visible") return;
5339
+ if (duration <= 0) return;
5340
+ remainingRef.current = duration;
5341
+ startDismissTimer(duration);
5342
+ return clearDismissTimer;
5343
+ }, [animationState, duration, startDismissTimer, clearDismissTimer]);
5344
+ react.useEffect(
5345
+ () => () => {
5346
+ clearDismissTimer();
5347
+ if (exitFallbackRef.current !== null) {
5348
+ clearTimeout(exitFallbackRef.current);
5349
+ exitFallbackRef.current = null;
5350
+ }
5351
+ },
5352
+ [clearDismissTimer]
5353
+ );
5354
+ const handleTransitionEnd = react.useCallback(() => {
5355
+ if (animationState === "exiting" && !closedRef.current) {
5356
+ if (exitFallbackRef.current !== null) {
5357
+ clearTimeout(exitFallbackRef.current);
5358
+ exitFallbackRef.current = null;
5359
+ }
5360
+ closedRef.current = true;
5361
+ setAnimationState("exited");
5362
+ onClose?.();
5363
+ }
5364
+ }, [animationState, onClose]);
5365
+ const handleMouseEnter = react.useCallback(() => {
5366
+ if (pausedRef.current || animationState !== "visible") return;
5367
+ pausedRef.current = true;
5368
+ const elapsed = Date.now() - startedAtRef.current;
5369
+ remainingRef.current = Math.max(remainingRef.current - elapsed, 0);
5370
+ clearDismissTimer();
5371
+ }, [animationState, clearDismissTimer]);
5372
+ const handleMouseLeave = react.useCallback(() => {
5373
+ if (!pausedRef.current || animationState !== "visible") return;
5374
+ pausedRef.current = false;
5375
+ if (duration > 0) startDismissTimer(remainingRef.current);
5376
+ }, [animationState, duration, startDismissTimer]);
5377
+ const handleFocusIn = react.useCallback(() => {
5378
+ if (pausedRef.current || animationState !== "visible") return;
5379
+ pausedRef.current = true;
5380
+ const elapsed = Date.now() - startedAtRef.current;
5381
+ remainingRef.current = Math.max(remainingRef.current - elapsed, 0);
5382
+ clearDismissTimer();
5383
+ }, [animationState, clearDismissTimer]);
5384
+ const handleFocusOut = react.useCallback(() => {
5385
+ if (!pausedRef.current || animationState !== "visible") return;
5386
+ pausedRef.current = false;
5387
+ if (duration > 0) startDismissTimer(remainingRef.current);
5388
+ }, [animationState, duration, startDismissTimer]);
5389
+ const handleClose = react.useCallback(() => {
5390
+ triggerExit();
5391
+ }, [triggerExit]);
5392
+ const ariaProps = severity === "error" ? { role: "alert", "aria-live": "assertive", "aria-atomic": "true" } : { role: "status", "aria-live": "polite", "aria-atomic": "true" };
5393
+ const computedClassName = cn(className, getAnimationClassName?.(animationState, position));
5394
+ const childContent = typeof children === "function" ? children({ animationState, onClose: handleClose }) : children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5395
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: message }),
5396
+ supportingText && /* @__PURE__ */ jsxRuntime.jsx("span", { children: supportingText }),
5397
+ action && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: action.onAction, children: action.label }),
5398
+ showClose && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", "aria-label": "Close", onClick: handleClose, children: "\u2715" })
5399
+ ] });
5400
+ return (
5401
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
5402
+ /* @__PURE__ */ jsxRuntime.jsx(
5403
+ "div",
5404
+ {
5405
+ ref,
5406
+ className: computedClassName,
5407
+ ...ariaProps,
5408
+ onMouseEnter: handleMouseEnter,
5409
+ onMouseLeave: handleMouseLeave,
5410
+ onFocus: handleFocusIn,
5411
+ onBlur: handleFocusOut,
5412
+ onTransitionEnd: handleTransitionEnd,
5413
+ "data-animation-state": animationState,
5414
+ children: childContent
5415
+ }
5416
+ )
5417
+ );
5418
+ }
5419
+ );
5420
+ SnackbarHeadless.displayName = "SnackbarHeadless";
5421
+ var snackbarStackContainerVariants = classVarianceAuthority.cva(
5422
+ ["fixed", "z-50", "flex", "gap-2", "pointer-events-none"],
5423
+ {
5424
+ variants: {
5425
+ position: {
5426
+ "bottom-center": [
5427
+ "bottom-4",
5428
+ "left-1/2",
5429
+ "-translate-x-1/2",
5430
+ "flex-col-reverse",
5431
+ "items-center"
5432
+ ],
5433
+ "bottom-left": ["bottom-4", "left-4", "flex-col-reverse", "items-start"],
5434
+ "bottom-right": ["bottom-4", "right-4", "flex-col-reverse", "items-end"],
5435
+ "top-center": ["top-4", "left-1/2", "-translate-x-1/2", "flex-col", "items-center"],
5436
+ "top-left": ["top-4", "left-4", "flex-col", "items-start"],
5437
+ "top-right": ["top-4", "right-4", "flex-col", "items-end"]
5438
+ }
5439
+ },
5440
+ defaultVariants: { position: "bottom-center" }
5441
+ }
5442
+ );
5443
+ var snackbarBaseVariants = classVarianceAuthority.cva(
5444
+ [
5445
+ // Sizing (MD3 spec: 288dp min, 568dp max)
5446
+ "min-w-72",
5447
+ "max-w-snackbar-max",
5448
+ "w-max",
5449
+ "min-h-12",
5450
+ // Restore pointer events so hover/focus timer pause works
5451
+ "pointer-events-auto",
5452
+ // Surface
5453
+ "bg-inverse-surface",
5454
+ // Shape: MD3 extra-small corner = 4dp
5455
+ "rounded-xs",
5456
+ // Elevation level 3
5457
+ "shadow-elevation-3",
5458
+ // Layout
5459
+ "flex",
5460
+ "items-center",
5461
+ "gap-x-1",
5462
+ "pl-4 pr-2",
5463
+ // Typography
5464
+ "text-body-medium",
5465
+ "text-inverse-on-surface",
5466
+ // Transition (properties used by both entry and exit)
5467
+ "transition-[opacity,transform]",
5468
+ "will-change-[opacity,transform]"
5469
+ ],
5470
+ {
5471
+ variants: {
5472
+ /**
5473
+ * Whether the Snackbar has supporting text (two-line layout).
5474
+ * Adjusts vertical padding to MD3 spec for two-line configuration.
5475
+ */
5476
+ twoLine: {
5477
+ true: "py-1",
5478
+ false: "py-1"
5479
+ }
5480
+ },
5481
+ defaultVariants: {
5482
+ twoLine: false
5483
+ }
5484
+ }
5485
+ );
5486
+ classVarianceAuthority.cva("", {
5487
+ variants: {
5488
+ position: {
5489
+ "bottom-center": ["bottom-4", "left-1/2", "-translate-x-1/2"],
5490
+ "bottom-left": ["bottom-4", "left-4"],
5491
+ "bottom-right": ["bottom-4", "right-4"],
5492
+ "top-center": ["top-4", "left-1/2", "-translate-x-1/2"],
5493
+ "top-left": ["top-4", "left-4"],
5494
+ "top-right": ["top-4", "right-4"]
5495
+ }
5496
+ },
5497
+ defaultVariants: {
5498
+ position: "bottom-center"
5499
+ }
5500
+ });
5501
+ var snackbarAnimationVariants = classVarianceAuthority.cva("", {
5502
+ variants: {
5503
+ animationState: {
5504
+ entering: ["opacity-0", "scale-75"],
5505
+ visible: ["scale-100", "opacity-100", "duration-medium1", "ease-emphasized-decelerate"],
5506
+ exiting: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"],
5507
+ exited: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"]
5508
+ },
5509
+ enterDirection: {
5510
+ up: ["origin-bottom"],
5511
+ down: ["origin-top"]
5512
+ }
5513
+ },
5514
+ defaultVariants: {
5515
+ animationState: "entering",
5516
+ enterDirection: "up"
5517
+ }
5518
+ });
5519
+ classVarianceAuthority.cva([...snackbarBaseVariants()], {
5520
+ variants: {
5521
+ animationState: {
5522
+ entering: ["opacity-0", "scale-75"],
5523
+ visible: ["scale-100", "opacity-100", "duration-medium1", "ease-emphasized-decelerate"],
5524
+ exiting: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"],
5525
+ exited: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"]
5526
+ },
5527
+ enterDirection: {
5528
+ up: ["origin-bottom"],
5529
+ down: ["origin-top"]
5530
+ },
5531
+ position: {
5532
+ "bottom-center": ["bottom-4", "left-1/2", "-translate-x-1/2"],
5533
+ "bottom-left": ["bottom-4", "left-4"],
5534
+ "bottom-right": ["bottom-4", "right-4"],
5535
+ "top-center": ["top-4", "left-1/2", "-translate-x-1/2"],
5536
+ "top-left": ["top-4", "left-4"],
5537
+ "top-right": ["top-4", "right-4"]
5538
+ },
5539
+ twoLine: {
5540
+ true: "py-1",
5541
+ false: "py-1"
5542
+ }
5543
+ },
5544
+ defaultVariants: {
5545
+ animationState: "entering",
5546
+ enterDirection: "up",
5547
+ position: "bottom-center",
5548
+ twoLine: false
5549
+ }
5550
+ });
5551
+ var snackbarMessageVariants = classVarianceAuthority.cva([
5552
+ "flex-1",
5553
+ "text-body-medium",
5554
+ "text-inverse-on-surface"
5555
+ ]);
5556
+ var snackbarSupportingTextVariants = classVarianceAuthority.cva([
5557
+ "text-body-medium",
5558
+ "text-inverse-on-surface",
5559
+ "opacity-80"
5560
+ ]);
5561
+ var snackbarActionVariants = classVarianceAuthority.cva(["shrink-0", "text-inverse-primary"]);
5562
+ var snackbarCloseVariants = classVarianceAuthority.cva(["shrink-0", "text-inverse-on-surface"]);
5563
+ var snackbarContentVariants = classVarianceAuthority.cva(["flex", "flex-col", "flex-1", "min-w-0 py-2 pr-2"]);
5564
+ classVarianceAuthority.cva(["scale-75", "opacity-0"]);
5565
+ function getEnterDirection(position) {
5566
+ return position.startsWith("top") ? "down" : "up";
5567
+ }
5568
+ function CloseIcon() {
5569
+ return /* @__PURE__ */ jsxRuntime.jsx(
5570
+ "svg",
5571
+ {
5572
+ xmlns: "http://www.w3.org/2000/svg",
5573
+ width: "24",
5574
+ height: "24",
5575
+ viewBox: "0 0 24 24",
5576
+ fill: "currentColor",
5577
+ "aria-hidden": "true",
5578
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" })
5579
+ }
5580
+ );
5581
+ }
5582
+ var Snackbar = react.forwardRef(function Snackbar2({
5583
+ message,
5584
+ supportingText,
5585
+ action,
5586
+ showClose = false,
5587
+ duration = 4e3,
5588
+ severity = "default",
5589
+ position = "bottom-center",
5590
+ onClose,
5591
+ className
5592
+ }, ref) {
5593
+ const isTwoLine = Boolean(supportingText);
5594
+ const baseClassName = cn(snackbarBaseVariants({ twoLine: isTwoLine }), className);
5595
+ return /* @__PURE__ */ jsxRuntime.jsx(
5596
+ SnackbarHeadless,
5597
+ {
5598
+ ref,
5599
+ message,
5600
+ ...supportingText !== void 0 && { supportingText },
5601
+ ...action !== void 0 && { action },
5602
+ showClose,
5603
+ duration,
5604
+ severity,
5605
+ position,
5606
+ ...onClose !== void 0 && { onClose },
5607
+ className: baseClassName,
5608
+ getAnimationClassName: (state, pos) => snackbarAnimationVariants({ animationState: state, enterDirection: getEnterDirection(pos) }),
5609
+ children: ({ onClose: triggerClose }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5610
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: snackbarContentVariants(), children: [
5611
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: snackbarMessageVariants(), children: message }),
5612
+ supportingText && /* @__PURE__ */ jsxRuntime.jsx("span", { className: snackbarSupportingTextVariants(), children: supportingText })
5613
+ ] }),
5614
+ action && /* @__PURE__ */ jsxRuntime.jsx("span", { className: snackbarActionVariants(), children: /* @__PURE__ */ jsxRuntime.jsx(
5615
+ Button,
5616
+ {
5617
+ variant: "text",
5618
+ onPress: action.onAction,
5619
+ className: "text-inverse-primary hover:text-inverse-primary",
5620
+ children: action.label
5621
+ }
5622
+ ) }),
5623
+ showClose && /* @__PURE__ */ jsxRuntime.jsx("span", { className: snackbarCloseVariants(), children: /* @__PURE__ */ jsxRuntime.jsx(
5624
+ IconButton,
5625
+ {
5626
+ variant: "standard",
5627
+ "aria-label": "Close",
5628
+ onPress: triggerClose,
5629
+ className: "text-inverse-on-surface hover:text-inverse-on-surface",
5630
+ children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
5631
+ }
5632
+ ) })
5633
+ ] })
5634
+ }
5635
+ );
5636
+ });
5637
+ Snackbar.displayName = "Snackbar";
5638
+ var SnackbarContext = react.createContext(null);
5639
+ function useSnackbar() {
5640
+ const ctx = react.useContext(SnackbarContext);
5641
+ if (!ctx) {
5642
+ throw new Error(
5643
+ "[Snackbar] useSnackbar must be used inside <SnackbarProvider>. Wrap your application (or Storybook decorator) with <SnackbarProvider>."
5644
+ );
5645
+ }
5646
+ return ctx;
5647
+ }
5648
+ function SnackbarProvider({ children, maxVisible = 5 }) {
5649
+ const [queue, setQueue] = react.useState([]);
5650
+ const counterRef = react.useRef(0);
5651
+ const baseId = react.useId();
5652
+ const showSnackbar = react.useCallback(
5653
+ (options) => {
5654
+ const id = `${baseId}-snackbar-${++counterRef.current}`;
5655
+ setQueue((prev) => [...prev, { ...options, id }]);
5656
+ return id;
5657
+ },
5658
+ [baseId]
5659
+ );
5660
+ const closeSnackbar = react.useCallback(() => {
5661
+ setQueue((prev) => {
5662
+ if (prev.length === 0) return prev;
5663
+ return prev.slice(1);
5664
+ });
5665
+ }, []);
5666
+ const removeById = react.useCallback((id) => {
5667
+ setQueue((prev) => prev.filter((item) => item.id !== id));
5668
+ }, []);
5669
+ const contextValue = { showSnackbar, closeSnackbar };
5670
+ const positionGroups = react.useMemo(() => {
5671
+ const groups = /* @__PURE__ */ new Map();
5672
+ const countByPosition = /* @__PURE__ */ new Map();
5673
+ for (const item of queue) {
5674
+ const pos = item.position ?? "bottom-center";
5675
+ const count = countByPosition.get(pos) ?? 0;
5676
+ if (count < maxVisible) {
5677
+ const existing = groups.get(pos) ?? [];
5678
+ groups.set(pos, [...existing, item]);
5679
+ countByPosition.set(pos, count + 1);
5680
+ }
5681
+ }
5682
+ return groups;
5683
+ }, [queue, maxVisible]);
5684
+ return /* @__PURE__ */ jsxRuntime.jsxs(SnackbarContext.Provider, { value: contextValue, children: [
5685
+ children,
5686
+ typeof document !== "undefined" && reactDom.createPortal(
5687
+ /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: Array.from(positionGroups.entries()).map(([position, items]) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: snackbarStackContainerVariants({ position }), children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
5688
+ Snackbar,
5689
+ {
5690
+ message: item.message,
5691
+ ...item.supportingText !== void 0 && {
5692
+ supportingText: item.supportingText
5693
+ },
5694
+ ...item.action !== void 0 && { action: item.action },
5695
+ ...item.showClose !== void 0 && { showClose: item.showClose },
5696
+ ...item.duration !== void 0 && { duration: item.duration },
5697
+ ...item.severity !== void 0 && { severity: item.severity },
5698
+ ...item.position !== void 0 && { position: item.position },
5699
+ ...item.className !== void 0 && { className: item.className },
5700
+ onClose: () => {
5701
+ item.onClose?.();
5702
+ removeById(item.id);
5703
+ }
5704
+ },
5705
+ item.id
5706
+ )) }, position)) }),
5707
+ document.body
5708
+ )
5709
+ ] });
5710
+ }
5711
+ var DialogContext = react.createContext(null);
5712
+ function useDialogContext() {
5713
+ const ctx = react.useContext(DialogContext);
5714
+ if (ctx === null) {
5715
+ throw new Error(
5716
+ "[Dialog] DialogHeadline, DialogContent, and DialogActions must be rendered inside a <Dialog> or <DialogHeadless> component."
5717
+ );
5718
+ }
5719
+ return ctx;
5720
+ }
5721
+ var DialogPanel = ({
5722
+ ariaLabel,
5723
+ headlineId,
5724
+ contentId,
5725
+ onClose,
5726
+ onTransitionEnd,
5727
+ variant,
5728
+ isDismissable,
5729
+ wrapperClassName,
5730
+ className,
5731
+ animationState,
5732
+ getAnimationClassName,
5733
+ children
5734
+ }) => {
5735
+ const panelRef = react.useRef(null);
5736
+ reactAria.usePreventScroll();
5737
+ const { dialogProps } = reactAria.useDialog(
5738
+ {
5739
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
5740
+ "aria-labelledby": headlineId,
5741
+ "aria-describedby": contentId
5742
+ },
5743
+ panelRef
5744
+ );
5745
+ const { overlayProps } = reactAria.useOverlay(
5746
+ {
5747
+ isOpen: true,
5748
+ onClose,
5749
+ isDismissable,
5750
+ shouldCloseOnBlur: false
5751
+ },
5752
+ panelRef
5753
+ );
5754
+ const panelClassName = cn(className, getAnimationClassName?.(animationState));
5755
+ return (
5756
+ // Centering/positioning wrapper — structural only, no ARIA role
5757
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsxRuntime.jsx(
5758
+ "div",
5759
+ {
5760
+ ...utils.mergeProps(overlayProps, dialogProps),
5761
+ ref: panelRef,
5762
+ "aria-modal": "true",
5763
+ className: panelClassName,
5764
+ "data-animation-state": animationState,
5765
+ "data-variant": variant,
5766
+ onTransitionEnd,
5767
+ children
5768
+ }
5769
+ ) })
5770
+ );
5771
+ };
5772
+ DialogPanel.displayName = "DialogPanel";
5773
+ var DialogHeadless = react.forwardRef(
5774
+ function DialogHeadless2({
5775
+ variant = "basic",
5776
+ open,
5777
+ defaultOpen = false,
5778
+ onOpenChange,
5779
+ "aria-label": ariaLabel,
5780
+ children,
5781
+ className,
5782
+ scrimClassName,
5783
+ getAnimationClassName
5784
+ }, _ref) {
5785
+ const state = reactStately.useOverlayTriggerState({
5786
+ ...open !== void 0 ? { isOpen: open } : {},
5787
+ ...defaultOpen !== void 0 ? { defaultOpen } : {},
5788
+ ...onOpenChange !== void 0 ? { onOpenChange } : {}
5789
+ });
5790
+ const isOpen = state.isOpen;
5791
+ const close = react.useCallback(() => {
5792
+ state.close();
5793
+ }, [state]);
5794
+ const [animationState, setAnimationState] = react.useState("exited");
5795
+ const closedRef = react.useRef(false);
5796
+ const exitFallbackRef = react.useRef(null);
5797
+ react.useEffect(() => {
5798
+ if (!isOpen) return;
5799
+ closedRef.current = false;
5800
+ setAnimationState("entering");
5801
+ const id = setTimeout(() => {
5802
+ setAnimationState("visible");
5803
+ }, 0);
5804
+ return () => clearTimeout(id);
5805
+ }, [isOpen]);
5806
+ react.useEffect(() => {
5807
+ if (isOpen) return;
5808
+ if (animationState === "exited" || animationState === "entering") return;
5809
+ if (animationState === "visible") {
5810
+ setAnimationState("exiting");
5811
+ exitFallbackRef.current = setTimeout(() => {
5812
+ if (!closedRef.current) {
5813
+ closedRef.current = true;
5814
+ setAnimationState("exited");
5815
+ }
5816
+ }, 150);
5817
+ }
5818
+ }, [isOpen, animationState]);
5819
+ react.useEffect(
5820
+ () => () => {
5821
+ if (exitFallbackRef.current !== null) {
5822
+ clearTimeout(exitFallbackRef.current);
5823
+ }
5824
+ },
5825
+ []
5826
+ );
5827
+ const handleTransitionEnd = react.useCallback(() => {
5828
+ if (animationState === "exiting" && !closedRef.current) {
5829
+ if (exitFallbackRef.current !== null) {
5830
+ clearTimeout(exitFallbackRef.current);
5831
+ exitFallbackRef.current = null;
5832
+ }
5833
+ closedRef.current = true;
5834
+ setAnimationState("exited");
5835
+ }
5836
+ }, [animationState]);
5837
+ const baseId = react.useId();
5838
+ const headlineId = `${baseId}-dialog-headline`;
5839
+ const contentId = `${baseId}-dialog-content`;
5840
+ const contextValue = {
5841
+ headlineId,
5842
+ contentId,
5843
+ close,
5844
+ variant
5845
+ };
5846
+ const handleScrimClick = react.useCallback(() => {
5847
+ if (variant === "basic") {
5848
+ close();
5849
+ }
5850
+ }, [variant, close]);
5851
+ if (!isOpen && animationState === "exited") {
5852
+ return null;
5853
+ }
5854
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(DialogContext.Provider, { value: contextValue, children: [
5855
+ /* @__PURE__ */ jsxRuntime.jsx(
5856
+ "div",
5857
+ {
5858
+ "data-testid": "dialog-scrim",
5859
+ className: scrimClassName,
5860
+ onClick: handleScrimClick,
5861
+ "aria-hidden": "true"
5862
+ }
5863
+ ),
5864
+ /* @__PURE__ */ jsxRuntime.jsx(reactAria.FocusScope, { contain: true, restoreFocus: true, autoFocus: true, children: /* @__PURE__ */ jsxRuntime.jsx(
5865
+ DialogPanel,
5866
+ {
5867
+ ariaLabel,
5868
+ headlineId,
5869
+ contentId,
5870
+ onClose: close,
5871
+ onTransitionEnd: handleTransitionEnd,
5872
+ variant,
5873
+ isDismissable: variant === "basic",
5874
+ wrapperClassName: variant === "basic" ? "fixed inset-0 z-50 flex items-center justify-center px-4" : "fixed inset-0 z-50",
5875
+ className,
5876
+ animationState,
5877
+ getAnimationClassName,
5878
+ children
5879
+ }
5880
+ ) })
5881
+ ] });
5882
+ if (typeof document === "undefined") return null;
5883
+ return reactDom.createPortal(content, document.body);
5884
+ }
5885
+ );
5886
+ DialogHeadless.displayName = "DialogHeadless";
5887
+ var dialogScrimVariants = classVarianceAuthority.cva([
5888
+ "fixed",
5889
+ "inset-0",
5890
+ "z-40",
5891
+ "bg-scrim",
5892
+ "opacity-32",
5893
+ "transition-opacity",
5894
+ "duration-medium2",
5895
+ "ease-standard"
5896
+ ]);
5897
+ var dialogPanelVariants = classVarianceAuthority.cva(
5898
+ [
5899
+ // Stacking above scrim
5900
+ "z-50",
5901
+ // Surface
5902
+ "bg-surface-container-high",
5903
+ // Flex column layout for slots
5904
+ "flex",
5905
+ "flex-col",
5906
+ // Transition for animation state changes
5907
+ "transition-[opacity,transform]",
5908
+ "will-change-[opacity,transform]"
5909
+ ],
5910
+ {
5911
+ variants: {
5912
+ variant: {
5913
+ basic: [
5914
+ // Shape: MD3 extra-large = 28dp
5915
+ "rounded-xl",
5916
+ // Elevation level 3
5917
+ "shadow-elevation-3",
5918
+ // Width constraints per MD3 spec (280dp min, 560dp max)
5919
+ "min-w-70",
5920
+ "max-w-dialog-max",
5921
+ "w-full",
5922
+ // Internal spacing
5923
+ "pt-6",
5924
+ "pb-3",
5925
+ "px-6",
5926
+ // Positioned in viewport center
5927
+ "relative"
5928
+ ],
5929
+ fullscreen: [
5930
+ // Full viewport
5931
+ "w-full",
5932
+ "h-full",
5933
+ // No rounded corners on fullscreen
5934
+ "rounded-none",
5935
+ // No elevation shadow on fullscreen
5936
+ "shadow-none",
5937
+ // Positioned to fill portal
5938
+ "relative"
5939
+ ]
5940
+ }
5941
+ },
5942
+ defaultVariants: {
5943
+ variant: "basic"
5944
+ }
5945
+ }
5946
+ );
5947
+ classVarianceAuthority.cva([], {
5948
+ variants: {
5949
+ variant: {
5950
+ basic: ["fixed", "inset-0", "z-50", "flex", "items-center", "justify-center", "px-4"],
5951
+ fullscreen: ["fixed", "inset-0", "z-50"]
5952
+ }
5953
+ },
5954
+ defaultVariants: {
5955
+ variant: "basic"
5956
+ }
5957
+ });
5958
+ var dialogAnimationVariants = classVarianceAuthority.cva("", {
5959
+ variants: {
5960
+ animationState: {
5961
+ entering: [],
5962
+ visible: [],
5963
+ exiting: [],
5964
+ exited: []
5965
+ },
5966
+ variant: {
5967
+ basic: [],
5968
+ fullscreen: []
5969
+ }
5970
+ },
5971
+ compoundVariants: [
5972
+ // Basic: entering — start scaled down + transparent
5973
+ {
5974
+ animationState: "entering",
5975
+ variant: "basic",
5976
+ className: ["scale-90", "opacity-0"]
5977
+ },
5978
+ // Basic: visible — scale to full + fade in
5979
+ {
5980
+ animationState: "visible",
5981
+ variant: "basic",
5982
+ className: ["scale-100", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
5983
+ },
5984
+ // Basic: exiting — fade out (scale stays at 1)
5985
+ {
5986
+ animationState: "exiting",
5987
+ variant: "basic",
5988
+ className: ["scale-100", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
5989
+ },
5990
+ // Basic: exited — fully transparent
5991
+ {
5992
+ animationState: "exited",
5993
+ variant: "basic",
5994
+ className: ["scale-100", "opacity-0"]
5995
+ },
5996
+ // Fullscreen: entering — start below viewport + transparent
5997
+ {
5998
+ animationState: "entering",
5999
+ variant: "fullscreen",
6000
+ className: ["translate-y-full", "opacity-0"]
6001
+ },
6002
+ // Fullscreen: visible — slide up + fade in
6003
+ {
6004
+ animationState: "visible",
6005
+ variant: "fullscreen",
6006
+ className: ["translate-y-0", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
6007
+ },
6008
+ // Fullscreen: exiting — slide down + fade out
6009
+ {
6010
+ animationState: "exiting",
6011
+ variant: "fullscreen",
6012
+ className: ["translate-y-full", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
6013
+ },
6014
+ // Fullscreen: exited — fully off-screen
6015
+ {
6016
+ animationState: "exited",
6017
+ variant: "fullscreen",
6018
+ className: ["translate-y-full", "opacity-0"]
6019
+ }
6020
+ ],
6021
+ defaultVariants: {
6022
+ animationState: "entering",
6023
+ variant: "basic"
6024
+ }
6025
+ });
6026
+ var dialogHeadlineVariants = classVarianceAuthority.cva(["text-headline-small", "text-on-surface"], {
6027
+ variants: {
6028
+ variant: {
6029
+ basic: ["mb-4"],
6030
+ fullscreen: [
6031
+ // Top app bar row in fullscreen: flex, items-center, gap
6032
+ "flex",
6033
+ "items-center",
6034
+ "gap-4",
6035
+ "px-4",
6036
+ "h-14",
6037
+ "shrink-0",
6038
+ "border-b",
6039
+ "border-outline-variant"
6040
+ ]
6041
+ }
6042
+ },
6043
+ defaultVariants: {
6044
+ variant: "basic"
6045
+ }
6046
+ });
6047
+ var dialogHeadlineTitleVariants = classVarianceAuthority.cva([
6048
+ "flex-1",
6049
+ "text-headline-small",
6050
+ "text-on-surface",
6051
+ "truncate"
6052
+ ]);
6053
+ var dialogContentVariants = classVarianceAuthority.cva(
6054
+ ["text-body-medium", "text-on-surface-variant", "overflow-y-auto", "flex-1"],
6055
+ {
6056
+ variants: {
6057
+ variant: {
6058
+ basic: ["mb-6"],
6059
+ fullscreen: ["px-6", "py-4"]
6060
+ }
6061
+ },
6062
+ defaultVariants: {
6063
+ variant: "basic"
6064
+ }
6065
+ }
6066
+ );
6067
+ var dialogActionsVariants = classVarianceAuthority.cva([
6068
+ "flex",
6069
+ "items-center",
6070
+ "justify-end",
6071
+ "gap-2",
6072
+ "pt-3",
6073
+ "shrink-0"
6074
+ ]);
6075
+ var Dialog = react.forwardRef(function Dialog2({
6076
+ variant = "basic",
6077
+ open,
6078
+ defaultOpen = false,
6079
+ onOpenChange,
6080
+ "aria-label": ariaLabel,
6081
+ children,
6082
+ className
6083
+ }, _ref) {
6084
+ const panelClassName = cn(dialogPanelVariants({ variant }), className);
6085
+ const scrimClass = dialogScrimVariants();
6086
+ return /* @__PURE__ */ jsxRuntime.jsx(
6087
+ DialogHeadless,
6088
+ {
6089
+ variant,
6090
+ ...open !== void 0 ? { open } : {},
6091
+ ...defaultOpen !== void 0 ? { defaultOpen } : {},
6092
+ ...onOpenChange !== void 0 ? { onOpenChange } : {},
6093
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
6094
+ className: panelClassName,
6095
+ scrimClassName: scrimClass,
6096
+ getAnimationClassName: (state) => dialogAnimationVariants({ animationState: state, variant }),
6097
+ children
6098
+ }
6099
+ );
6100
+ });
6101
+ Dialog.displayName = "Dialog";
6102
+ var DialogHeadline = react.forwardRef(
6103
+ function DialogHeadline2({ children, className, closeButton, confirmButton }, ref) {
6104
+ const { headlineId, variant } = useDialogContext();
6105
+ if (variant === "fullscreen") {
6106
+ return (
6107
+ // Top app bar row for fullscreen variant
6108
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(dialogHeadlineVariants({ variant: "fullscreen" }), className), children: [
6109
+ closeButton,
6110
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: headlineId, className: dialogHeadlineTitleVariants(), children }),
6111
+ confirmButton
6112
+ ] })
6113
+ );
6114
+ }
6115
+ return /* @__PURE__ */ jsxRuntime.jsx(
6116
+ "h2",
6117
+ {
6118
+ ref,
6119
+ id: headlineId,
6120
+ className: cn(dialogHeadlineVariants({ variant: "basic" }), className),
6121
+ children
6122
+ }
6123
+ );
6124
+ }
6125
+ );
6126
+ DialogHeadline.displayName = "DialogHeadline";
6127
+ var DialogContent = react.forwardRef(function DialogContent2({ children, className }, ref) {
6128
+ const { contentId, variant } = useDialogContext();
6129
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, id: contentId, className: cn(dialogContentVariants({ variant }), className), children });
6130
+ });
6131
+ DialogContent.displayName = "DialogContent";
6132
+ var DialogActions = react.forwardRef(function DialogActions2({ children, className }, ref) {
6133
+ useDialogContext();
6134
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn(dialogActionsVariants(), className), children });
6135
+ });
6136
+ DialogActions.displayName = "DialogActions";
2988
6137
 
2989
6138
  Object.defineProperty(exports, "argbFromHex", {
2990
6139
  enumerable: true,
@@ -2994,20 +6143,61 @@ Object.defineProperty(exports, "hexFromArgb", {
2994
6143
  enumerable: true,
2995
6144
  get: function () { return materialColorUtilities.hexFromArgb; }
2996
6145
  });
6146
+ exports.AppBar = AppBar;
6147
+ exports.AppBarHeadless = AppBarHeadless;
2997
6148
  exports.Button = Button;
2998
6149
  exports.Checkbox = Checkbox;
6150
+ exports.Dialog = Dialog;
6151
+ exports.DialogActions = DialogActions;
6152
+ exports.DialogContent = DialogContent;
6153
+ exports.DialogContext = DialogContext;
6154
+ exports.DialogHeadless = DialogHeadless;
6155
+ exports.DialogHeadline = DialogHeadline;
6156
+ exports.Drawer = Drawer;
6157
+ exports.DrawerItem = DrawerItem;
6158
+ exports.DrawerSection = DrawerSection;
2999
6159
  exports.FAB = FAB;
3000
6160
  exports.FABHeadless = FABHeadless;
6161
+ exports.HeadlessDrawer = HeadlessDrawer;
6162
+ exports.HeadlessDrawerItem = HeadlessDrawerItem;
6163
+ exports.HeadlessMenu = HeadlessMenu;
6164
+ exports.HeadlessMenuDivider = HeadlessMenuDivider;
6165
+ exports.HeadlessMenuItem = HeadlessMenuItem;
6166
+ exports.HeadlessMenuSection = HeadlessMenuSection;
6167
+ exports.HeadlessMenuTrigger = HeadlessMenuTrigger;
6168
+ exports.HeadlessNavigationBar = HeadlessNavigationBar;
6169
+ exports.HeadlessNavigationBarItem = HeadlessNavigationBarItem;
6170
+ exports.HeadlessTab = HeadlessTab;
6171
+ exports.HeadlessTabList = HeadlessTabList;
6172
+ exports.HeadlessTabPanel = HeadlessTabPanel;
3001
6173
  exports.IconButton = IconButton;
3002
6174
  exports.IconButtonHeadless = IconButtonHeadless;
6175
+ exports.Menu = Menu;
6176
+ exports.MenuContext = MenuContext;
6177
+ exports.MenuDivider = MenuDivider;
6178
+ exports.MenuItem = MenuItem;
6179
+ exports.MenuSection = MenuSection;
6180
+ exports.MenuTrigger = MenuTrigger;
6181
+ exports.NavigationBar = NavigationBar;
6182
+ exports.NavigationBarItem = NavigationBarItem;
6183
+ exports.Progress = Progress;
6184
+ exports.ProgressHeadless = ProgressHeadless;
3003
6185
  exports.Radio = Radio;
3004
6186
  exports.RadioGroup = RadioGroup;
3005
6187
  exports.RadioGroupHeadless = RadioGroupHeadless;
3006
6188
  exports.RadioHeadless = RadioHeadless;
3007
6189
  exports.STATE_LAYER_OPACITY = STATE_LAYER_OPACITY;
6190
+ exports.Snackbar = Snackbar;
6191
+ exports.SnackbarContext = SnackbarContext;
6192
+ exports.SnackbarHeadless = SnackbarHeadless;
6193
+ exports.SnackbarProvider = SnackbarProvider;
3008
6194
  exports.Switch = Switch;
3009
6195
  exports.TYPOGRAPHY_ELEMENT_MAP = TYPOGRAPHY_ELEMENT_MAP;
3010
6196
  exports.TYPOGRAPHY_USAGE = TYPOGRAPHY_USAGE;
6197
+ exports.Tab = Tab;
6198
+ exports.TabList = TabList;
6199
+ exports.TabPanel = TabPanel;
6200
+ exports.Tabs = Tabs;
3011
6201
  exports.TextField = TextField;
3012
6202
  exports.applyStateLayer = applyStateLayer;
3013
6203
  exports.cn = cn;
@@ -3025,6 +6215,9 @@ exports.pxToRem = pxToRem;
3025
6215
  exports.remToPx = remToPx;
3026
6216
  exports.rgbToHex = rgbToHex;
3027
6217
  exports.truncateText = truncateText;
6218
+ exports.useDialogContext = useDialogContext;
6219
+ exports.useMenuContext = useMenuContext;
6220
+ exports.useSnackbar = useSnackbar;
3028
6221
  exports.withOpacity = withOpacity;
3029
6222
  //# sourceMappingURL=index.cjs.map
3030
6223
  //# sourceMappingURL=index.cjs.map