@tinybigui/react 0.21.1 → 0.23.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
@@ -472,6 +472,10 @@ var buttonVariants = classVarianceAuthority.cva(
472
472
  * Filled/Tonal hover→level-1, focus/pressed→level-0
473
473
  * Elevated base→level-1, hover→level-2, focus/pressed→level-1
474
474
  * Outlined/Text no elevation
475
+ *
476
+ * Self-targeting `data-[x]:` is used for elevation because these classes
477
+ * sit on the root element (the group host) — group-data descendant
478
+ * selectors cannot match an element against itself.
475
479
  */
476
480
  variant: {
477
481
  /**
@@ -480,17 +484,17 @@ var buttonVariants = classVarianceAuthority.cva(
480
484
  * Elevation: 0 base → 1 hover → 0 focus → 0 pressed
481
485
  */
482
486
  filled: [
483
- "bg-primary text-on-primary shadow-none",
487
+ "text-on-primary shadow-none",
484
488
  // Hover: gains level-1 elevation
485
- "group-data-[hovered]/button:shadow-elevation-1",
489
+ "data-[hovered]:shadow-elevation-1",
486
490
  // Focus/pressed: shadow must explicitly return to level-0
487
491
  // (doubled attribute selector → higher specificity than hover)
488
- "group-data-[focus-visible]/button:shadow-none",
489
- "group-data-[pressed]/button:group-data-[pressed]/button:shadow-none",
490
- // Disabled overrides
491
- "group-data-[disabled]/button:bg-on-surface/12",
492
- "group-data-[disabled]/button:text-on-surface/38",
493
- "group-data-[disabled]/button:shadow-none"
492
+ "data-[focus-visible]:data-[focus-visible]:shadow-none",
493
+ "data-[pressed]:data-[pressed]:data-[pressed]:shadow-none",
494
+ // Disabled overrides — root owns text color + shadow only;
495
+ // disabled bg override lives on buttonContainerVariants (filled variant)
496
+ "data-[disabled]:text-on-surface/38",
497
+ "data-[disabled]:shadow-none"
494
498
  ],
495
499
  /**
496
500
  * Outlined — medium emphasis. Transparent with border.
@@ -498,10 +502,10 @@ var buttonVariants = classVarianceAuthority.cva(
498
502
  * Elevation: always 0
499
503
  */
500
504
  outlined: [
501
- "bg-transparent border border-outline text-primary",
505
+ "border border-outline text-primary",
502
506
  // Disabled overrides
503
- "group-data-[disabled]/button:border-on-surface/12",
504
- "group-data-[disabled]/button:text-on-surface/38"
507
+ "data-[disabled]:border-on-surface/12",
508
+ "data-[disabled]:text-on-surface/38"
505
509
  ],
506
510
  /**
507
511
  * Tonal — secondary emphasis.
@@ -509,16 +513,15 @@ var buttonVariants = classVarianceAuthority.cva(
509
513
  * Elevation: 0 base → 1 hover → 0 focus → 0 pressed
510
514
  */
511
515
  tonal: [
512
- "bg-secondary-container text-on-secondary-container shadow-none",
516
+ "text-on-secondary-container shadow-none",
513
517
  // Hover: gains level-1 elevation (same as filled)
514
- "group-data-[hovered]/button:shadow-elevation-1",
518
+ "data-[hovered]:shadow-elevation-1",
515
519
  // Focus/pressed: return to level-0
516
- "group-data-[focus-visible]/button:shadow-none",
517
- "group-data-[pressed]/button:group-data-[pressed]/button:shadow-none",
520
+ "data-[focus-visible]:data-[focus-visible]:shadow-none",
521
+ "data-[pressed]:data-[pressed]:data-[pressed]:shadow-none",
518
522
  // Disabled overrides
519
- "group-data-[disabled]/button:bg-on-surface/12",
520
- "group-data-[disabled]/button:text-on-surface/38",
521
- "group-data-[disabled]/button:shadow-none"
523
+ "data-[disabled]:text-on-surface/38",
524
+ "data-[disabled]:shadow-none"
522
525
  ],
523
526
  /**
524
527
  * Elevated — separation via shadow.
@@ -526,17 +529,16 @@ var buttonVariants = classVarianceAuthority.cva(
526
529
  * Elevation: 1 base → 2 hover → 1 focus → 1 pressed
527
530
  */
528
531
  elevated: [
529
- "bg-surface-container-low text-primary shadow-elevation-1",
532
+ "text-primary shadow-elevation-1",
530
533
  // Hover: gains extra elevation
531
- "group-data-[hovered]/button:shadow-elevation-2",
534
+ "data-[hovered]:shadow-elevation-2",
532
535
  // Focus/pressed: return to base level-1
533
536
  // (doubled selector wins over single hover selector at same cascade position)
534
- "group-data-[focus-visible]/button:shadow-elevation-1",
535
- "group-data-[pressed]/button:group-data-[pressed]/button:shadow-elevation-1",
537
+ "data-[focus-visible]:data-[focus-visible]:shadow-elevation-1",
538
+ "data-[pressed]:data-[pressed]:data-[pressed]:shadow-elevation-1",
536
539
  // Disabled overrides
537
- "group-data-[disabled]/button:bg-on-surface/12",
538
- "group-data-[disabled]/button:text-on-surface/38",
539
- "group-data-[disabled]/button:shadow-none"
540
+ "data-[disabled]:text-on-surface/38",
541
+ "data-[disabled]:shadow-none"
540
542
  ],
541
543
  /**
542
544
  * Text — lowest emphasis.
@@ -544,9 +546,9 @@ var buttonVariants = classVarianceAuthority.cva(
544
546
  * Elevation: always 0
545
547
  */
546
548
  text: [
547
- "bg-transparent text-primary",
549
+ "text-primary",
548
550
  // Disabled overrides
549
- "group-data-[disabled]/button:text-on-surface/38"
551
+ "data-[disabled]:text-on-surface/38"
550
552
  ]
551
553
  },
552
554
  /**
@@ -584,6 +586,29 @@ var buttonVariants = classVarianceAuthority.cva(
584
586
  }
585
587
  }
586
588
  );
589
+ var buttonContainerVariants = classVarianceAuthority.cva(
590
+ [
591
+ "absolute inset-0 rounded-[inherit] pointer-events-none",
592
+ // Effects transition for background-color — no overshoot
593
+ "transition-[background-color] duration-spring-standard-fast-effects ease-spring-standard-fast-effects"
594
+ ],
595
+ {
596
+ variants: {
597
+ variant: {
598
+ // MD3 disabled: filled containers replace bg with on-surface/12.
599
+ // group-data descendant selector targets this child span (not the root host).
600
+ filled: "bg-primary group-data-[disabled]/button:bg-on-surface/12",
601
+ // outlined/text: container stays transparent when disabled — only border + label fade.
602
+ outlined: "bg-transparent",
603
+ // MD3 disabled: tonal and elevated containers also replace bg with on-surface/12.
604
+ tonal: "bg-secondary-container group-data-[disabled]/button:bg-on-surface/12",
605
+ elevated: "bg-surface-container-low group-data-[disabled]/button:bg-on-surface/12",
606
+ text: "bg-transparent"
607
+ }
608
+ },
609
+ defaultVariants: { variant: "filled" }
610
+ }
611
+ );
587
612
  var buttonStateLayerVariants = classVarianceAuthority.cva(
588
613
  [
589
614
  "absolute inset-0 rounded-[inherit] overflow-hidden pointer-events-none opacity-0",
@@ -886,6 +911,7 @@ var Button = React.forwardRef(
886
911
  className
887
912
  ),
888
913
  children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonContainerVariants({ variant })), "aria-hidden": "true" }),
889
915
  ripples,
890
916
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonStateLayerVariants({ variant })), "aria-hidden": "true" }),
891
917
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(buttonFocusRingVariants()), "aria-hidden": "true" }),
@@ -5952,293 +5978,526 @@ var CardActions = React.forwardRef(function CardActions2({ children, className }
5952
5978
  return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("flex items-center justify-end gap-2 p-4 pt-0", className), children });
5953
5979
  });
5954
5980
  CardActions.displayName = "CardActions";
5955
- var MenuContext = React.createContext(null);
5956
- function useMenuContext() {
5957
- return React.useContext(MenuContext);
5958
- }
5959
- function TriggerBridge({ children }) {
5960
- const ctx = reactAriaComponents.useSlottedContext(reactAriaComponents.ButtonContext);
5961
- const localRef = React.useRef(null);
5962
- const { ref: contextRef, ...ctxProps } = ctx ?? {};
5963
- const mergedCallbackRef = React.useCallback(
5964
- (node) => {
5965
- localRef.current = node;
5966
- if (!contextRef) return;
5967
- if (typeof contextRef === "function") {
5968
- contextRef(node);
5969
- } else {
5970
- contextRef.current = node;
5971
- }
5972
- },
5973
- [contextRef]
5974
- );
5975
- const { buttonProps } = reactAria.useButton({ ...ctxProps, elementType: "button" }, localRef);
5976
- if (!React.isValidElement(children)) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
5977
- return React.cloneElement(
5978
- children,
5979
- { ...buttonProps, ref: mergedCallbackRef }
5980
- );
5981
- }
5982
- function HeadlessMenuTrigger({
5983
- children,
5984
- placement = "bottom start",
5985
- shouldFlip = true,
5986
- ...rest
5987
- }) {
5988
- const childrenArray = Array.isArray(children) ? children : [children];
5989
- const [triggerChild, menuChild] = childrenArray;
5990
- return /* @__PURE__ */ jsxRuntime.jsxs(reactAriaComponents.MenuTrigger, { ...rest, children: [
5991
- /* @__PURE__ */ jsxRuntime.jsx(TriggerBridge, { children: triggerChild }),
5992
- /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Popover, { placement, shouldFlip, offset: 4, children: menuChild })
5993
- ] });
5994
- }
5995
- function HeadlessMenu({
5996
- className,
5997
- children,
5998
- "aria-label": ariaLabel,
5999
- ...props
6000
- }) {
6001
- const menuRef = React.useRef(null);
6002
- React.useLayoutEffect(() => {
6003
- if (ariaLabel && menuRef.current) {
6004
- menuRef.current.removeAttribute("aria-labelledby");
6005
- }
6006
- });
6007
- return /* @__PURE__ */ jsxRuntime.jsx(
6008
- reactAriaComponents.Menu,
6009
- {
6010
- ...props,
6011
- ref: menuRef,
6012
- ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
6013
- className: className ?? "",
6014
- children
6015
- }
6016
- );
6017
- }
6018
- HeadlessMenuTrigger.Menu = HeadlessMenu;
6019
- var HeadlessMenuItem = React.forwardRef(
6020
- function HeadlessMenuItem2({ children, className, ...props }, ref) {
6021
- return /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.MenuItem, { ...props, ref, className: className ?? "", children });
6022
- }
6023
- );
6024
- function HeadlessMenuSection({
6025
- children,
6026
- "aria-label": ariaLabel,
6027
- className
6028
- }) {
6029
- return /* @__PURE__ */ jsxRuntime.jsx(
6030
- reactAriaComponents.MenuSection,
6031
- {
6032
- ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
6033
- className: className ?? "",
6034
- children
6035
- }
6036
- );
6037
- }
6038
- function HeadlessMenuDivider({
6039
- className,
6040
- ...props
6041
- }) {
6042
- return /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Separator, { ...props, className: className ?? "" });
6043
- }
6044
5981
  var menuContainerVariants = classVarianceAuthority.cva(
6045
5982
  [
6046
- // Elevation
6047
- "shadow-elevation-2",
6048
- // Width constraints per MD3 spec (112dp min / 280dp max)
5983
+ // Width constraints: 112dp min / 280dp max per MD3 spec
6049
5984
  "min-w-28 max-w-70",
6050
- // Layout
6051
- "py-2",
6052
- // Scroll: show scrollbar when content overflows; max height avoids clipping
6053
- "overflow-y-auto",
5985
+ "flex flex-col",
5986
+ // Scroll behaviour
6054
5987
  "max-h-[calc(var(--visual-viewport-height,100vh)-2rem)]",
6055
5988
  // Stacking
6056
5989
  "z-50",
6057
- // Focus outline handled by React Aria
6058
- "outline-none",
6059
- // GPU compositing — promotes menu to its own compositor layer so
6060
- // scale + opacity animations run without triggering layout reflow.
6061
- "will-change-[transform,opacity]",
6062
- // Pointer events blocked during animation to prevent accidental clicks
6063
- // on menu items while the panel is still animating in or out.
6064
- "data-[entering]:pointer-events-none data-[exiting]:pointer-events-none",
6065
- // ── Enter animation ────────────────────────────────────────────────────
6066
- // @keyframes menu-enter (defined in styles.css): scale(0.8)+opacity:0 →
6067
- // scale(1)+opacity:1 in 120ms with cubic-bezier(0,0,0.2,1) (standard
6068
- // decelerate — matches Angular Material's _mat-menu-enter keyframe).
6069
- "data-[entering]:animate-[menu-enter_120ms_cubic-bezier(0,0,0.2,1)_both]",
6070
- // ── Exit animation ─────────────────────────────────────────────────────
6071
- // @keyframes menu-exit (defined in styles.css): opacity:1 → opacity:0
6072
- // in 100ms after 25ms delay, linear — matches Angular Material's
6073
- // _mat-menu-exit keyframe (fade-only, no reverse scale).
6074
- "data-[exiting]:animate-[menu-exit_100ms_25ms_linear_both]",
6075
- // ── Transform origin (placement-aware) ────────────────────────────────
6076
- // RAC sets data-placement="bottom|top|left|right" on the Popover element.
6077
- // Default (bottom): origin at top edge (menu expands downward).
6078
- "origin-top",
6079
- // top: origin at bottom edge (menu expands upward)
6080
- "data-[placement=top]:origin-bottom",
6081
- // left: origin at right edge
6082
- "data-[placement=left]:origin-right",
6083
- // right: origin at left edge
6084
- "data-[placement=right]:origin-left",
6085
- // ── Reduced motion ────────────────────────────────────────────────────
6086
- // Skip both animations entirely for users who prefer reduced motion.
6087
- "motion-reduce:data-[entering]:animate-none motion-reduce:data-[exiting]:animate-none"
5990
+ "gap-0.5",
5991
+ // Focus outline delegated to React Aria
5992
+ "outline-none"
6088
5993
  ],
6089
5994
  {
6090
5995
  variants: {
6091
5996
  /**
6092
- * Color scheme — drives the container background.
6093
- * baseline+standard uses a separate compound variant.
5997
+ * Color scheme — drives item/segment background and content colors.
5998
+ * standard: surface-container-low item background.
5999
+ * vibrant: tertiary-container item background.
6094
6000
  */
6095
6001
  colorScheme: {
6096
6002
  standard: [],
6097
6003
  vibrant: []
6098
6004
  },
6099
6005
  /**
6100
- * Visual style — drives corner radius and baseline vs vertical background.
6006
+ * Visual style — drives corner radius and container background.
6007
+ *
6008
+ * baseline: solid surface-container, 4dp corners, 8dp vertical padding.
6009
+ * vertical: transparent container, 16dp corners, no container padding —
6010
+ * items own their segment surface, gaps reveal the page background.
6101
6011
  */
6102
6012
  menuStyle: {
6103
- baseline: ["rounded-xs", "bg-surface-container"],
6104
- vertical: ["rounded-lg", "bg-surface-container-low"]
6013
+ baseline: ["rounded-xs", "bg-surface-container", "py-2"],
6014
+ vertical: ["bg-transparent"]
6105
6015
  }
6106
6016
  },
6107
- compoundVariants: [
6108
- // Vertical + vibrant: tertiary container background
6109
- {
6110
- menuStyle: "vertical",
6111
- colorScheme: "vibrant",
6112
- class: ["bg-tertiary-container"]
6113
- }
6114
- ],
6115
6017
  defaultVariants: {
6116
6018
  colorScheme: "standard",
6117
6019
  menuStyle: "baseline"
6118
6020
  }
6119
6021
  }
6120
6022
  );
6023
+ var menuPopoverVariants = classVarianceAuthority.cva([
6024
+ "will-change-[transform,opacity]",
6025
+ "data-[entering]:pointer-events-none data-[exiting]:pointer-events-none",
6026
+ "data-[entering]:animate-md-scale-in",
6027
+ "data-[exiting]:animate-md-scale-out",
6028
+ "origin-top",
6029
+ "data-[placement=top]:origin-bottom",
6030
+ "data-[placement=left]:origin-right",
6031
+ "data-[placement=right]:origin-left",
6032
+ "motion-reduce:data-[entering]:animate-none motion-reduce:data-[exiting]:animate-none"
6033
+ ]);
6121
6034
  var menuItemVariants = classVarianceAuthority.cva(
6122
6035
  [
6123
6036
  // Layout — height set by density context in MenuItem component
6037
+ // gap is style-specific: baseline = 12dp (gap-3), vertical = 8dp (gap-2)
6124
6038
  "relative flex w-full items-center",
6125
- "px-3 gap-3",
6126
- // Typography: Body Large per MD3 baseline spec
6127
- "text-body-large",
6039
+ // Typography: Label Large per MD3 menu spec
6040
+ "text-label-large",
6128
6041
  // Interaction
6129
6042
  "cursor-pointer select-none outline-none",
6130
- // State layer pseudo-element
6131
- "before:absolute before:inset-0 before:rounded-[inherit]",
6132
- "before:transition-opacity before:duration-short2 before:ease-standard",
6133
- "before:opacity-0",
6134
- // Hover state layer
6135
- "hover:before:opacity-8",
6136
- // Focus visible state layer
6137
- "focus-visible:before:opacity-12",
6138
- // Active pressed state layer
6139
- "active:before:opacity-12",
6140
- // Color transition for selection
6141
- "transition-colors duration-short2 ease-standard"
6043
+ // Color transition (effects — no overshoot)
6044
+ "transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
6045
+ // Disabled — self-targeting data-[x]: selectors (RAC emits data-disabled)
6046
+ "data-[disabled]:pointer-events-none data-[disabled]:cursor-not-allowed"
6142
6047
  ],
6143
6048
  {
6144
6049
  variants: {
6145
6050
  /**
6146
- * Disabled state: reduces opacity and blocks interaction.
6051
+ * Color scheme drives item bg, default text/icon color, and selection colors.
6052
+ *
6053
+ * standard: surface-container-low bg, on-surface text, on-surface-variant icons.
6054
+ * Selected/open: tertiary-container bg highlight, on-tertiary-container content.
6055
+ * vibrant: tertiary-container bg, on-tertiary-container text AND icons.
6056
+ * Selected/open: tertiary bg highlight, on-tertiary content.
6147
6057
  */
6148
- isDisabled: {
6149
- true: ["opacity-38 cursor-not-allowed pointer-events-none"],
6150
- false: []
6058
+ colorScheme: {
6059
+ standard: ["text-on-surface"],
6060
+ vibrant: ["text-on-tertiary-container"]
6151
6061
  },
6152
6062
  /**
6153
- * Selected state: background and text color driven by compound variants.
6063
+ * Visual style drives padding, gap, segment surface, and corner rounding.
6064
+ *
6065
+ * baseline: 12dp h-padding, 12dp icon-to-label gap, no item background (container provides it).
6066
+ * vertical: 12dp h-padding, 8dp icon-to-label gap, item owns segment surface, segmented rounding
6067
+ * via first/last + adjacent-sibling gap selectors.
6154
6068
  */
6155
- isSelected: {
6156
- true: [],
6157
- false: []
6069
+ menuStyle: {
6070
+ baseline: ["px-3", "gap-3"],
6071
+ vertical: [
6072
+ "px-3",
6073
+ "gap-2",
6074
+ // Default: inner item (4dp all corners)
6075
+ "rounded-md"
6076
+ // Last item in the whole menu → 16dp bottom corners
6077
+ ]
6078
+ }
6079
+ },
6080
+ compoundVariants: [
6081
+ // vertical + standard: selected/open text → on-tertiary-container
6082
+ {
6083
+ menuStyle: "vertical",
6084
+ colorScheme: "standard",
6085
+ class: [
6086
+ "data-[selected]:text-on-tertiary-container",
6087
+ "data-[open]:text-on-tertiary-container"
6088
+ ]
6089
+ },
6090
+ // vertical + vibrant: selected/open text → on-tertiary
6091
+ {
6092
+ menuStyle: "vertical",
6093
+ colorScheme: "vibrant",
6094
+ class: ["data-[selected]:text-on-tertiary", "data-[open]:text-on-tertiary"]
6095
+ }
6096
+ ],
6097
+ defaultVariants: {
6098
+ colorScheme: "standard",
6099
+ menuStyle: "baseline"
6100
+ }
6101
+ }
6102
+ );
6103
+ var menuItemHighlightVariants = classVarianceAuthority.cva(
6104
+ [
6105
+ "absolute inset-0 pointer-events-none",
6106
+ // Inherit the item's own corner radius (inner 4dp or outer 16dp)
6107
+ "rounded-[inherit]",
6108
+ // Effects transition for background-color — no overshoot
6109
+ "transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
6110
+ // z-0: below state layer (z-[1]) and content (z-10)
6111
+ "z-0"
6112
+ ],
6113
+ {
6114
+ variants: {
6115
+ menuStyle: {
6116
+ baseline: [],
6117
+ vertical: []
6158
6118
  },
6159
- /**
6160
- * Color scheme: drives default text and state layer colors.
6161
- * - standard: on-surface text, on-surface state layer
6162
- * - vibrant (vertical only): on-tertiary-container text + state layer
6163
- */
6164
6119
  colorScheme: {
6165
- standard: ["text-on-surface", "before:bg-on-surface"],
6166
- vibrant: ["text-on-tertiary-container", "before:bg-on-tertiary-container"]
6120
+ standard: [
6121
+ // baseline selected bg
6122
+ "group-data-[selected]/menuitem:bg-surface-container-highest"
6123
+ ],
6124
+ vibrant: [
6125
+ // baseline + vibrant: use surface-container-highest as fallback
6126
+ "group-data-[selected]/menuitem:bg-surface-container-highest"
6127
+ ]
6128
+ }
6129
+ },
6130
+ compoundVariants: [
6131
+ // vertical + standard: selected/open highlight → tertiary-container
6132
+ {
6133
+ menuStyle: "vertical",
6134
+ colorScheme: "standard",
6135
+ class: [
6136
+ "group-data-[selected]/menuitem:bg-tertiary-container",
6137
+ "group-data-[open]/menuitem:bg-tertiary-container"
6138
+ ]
6139
+ },
6140
+ // vertical + vibrant: selected/open highlight → tertiary
6141
+ {
6142
+ menuStyle: "vertical",
6143
+ colorScheme: "vibrant",
6144
+ class: [
6145
+ "group-data-[selected]/menuitem:bg-tertiary",
6146
+ "group-data-[open]/menuitem:bg-tertiary"
6147
+ ]
6148
+ }
6149
+ ],
6150
+ defaultVariants: {
6151
+ menuStyle: "baseline",
6152
+ colorScheme: "standard"
6153
+ }
6154
+ }
6155
+ );
6156
+ var menuItemStateLayerVariants = classVarianceAuthority.cva(
6157
+ [
6158
+ "absolute inset-0 rounded-[inherit] overflow-hidden pointer-events-none opacity-0",
6159
+ // Effects transition — opacity must NOT overshoot
6160
+ "transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
6161
+ // Hover: 8%
6162
+ "group-data-[hovered]/menuitem:opacity-8",
6163
+ // Pressed: 10%, doubled selector wins over hover at same cascade position
6164
+ "group-data-[pressed]/menuitem:group-data-[pressed]/menuitem:opacity-10",
6165
+ // No state layer when disabled
6166
+ "group-data-[disabled]/menuitem:hidden",
6167
+ // z-[1]: above highlight layer (z-0), below content (z-10)
6168
+ "z-[1]"
6169
+ ],
6170
+ {
6171
+ variants: {
6172
+ colorScheme: {
6173
+ standard: ["bg-on-surface"],
6174
+ vibrant: ["bg-on-tertiary-container"]
6167
6175
  },
6168
- /**
6169
- * Visual style: drives corner radius on items (vertical uses rounded-lg
6170
- * inherited from container, items stay flat inside).
6171
- */
6172
6176
  menuStyle: {
6173
6177
  baseline: [],
6174
6178
  vertical: []
6175
6179
  }
6176
6180
  },
6177
6181
  compoundVariants: [
6178
- // ── Baseline selection (both colorSchemes) ──────────────────────────
6182
+ // vertical + standard: selected/open state layer → on-tertiary-container
6183
+ {
6184
+ menuStyle: "vertical",
6185
+ colorScheme: "standard",
6186
+ class: [
6187
+ "group-data-[selected]/menuitem:bg-on-tertiary-container",
6188
+ "group-data-[open]/menuitem:bg-on-tertiary-container"
6189
+ ]
6190
+ },
6191
+ // vertical + vibrant: selected/open state layer → on-tertiary
6179
6192
  {
6180
- isSelected: true,
6181
- menuStyle: "baseline",
6193
+ menuStyle: "vertical",
6194
+ colorScheme: "vibrant",
6182
6195
  class: [
6183
- "bg-surface-container-highest"
6184
- // text-on-surface already applied by standard colorScheme variant
6196
+ "group-data-[selected]/menuitem:bg-on-tertiary",
6197
+ "group-data-[open]/menuitem:bg-on-tertiary"
6185
6198
  ]
6199
+ }
6200
+ ],
6201
+ defaultVariants: {
6202
+ colorScheme: "standard",
6203
+ menuStyle: "baseline"
6204
+ }
6205
+ }
6206
+ );
6207
+ var menuItemFocusRingVariants = classVarianceAuthority.cva([
6208
+ "pointer-events-none absolute inset-0 rounded-[inherit]",
6209
+ "outline outline-2 -outline-offset-2 outline-secondary",
6210
+ // Effects transition — opacity must not overshoot
6211
+ "transition-opacity duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
6212
+ "opacity-0",
6213
+ "group-data-[focus-visible]/menuitem:opacity-100",
6214
+ // z-[2]: above state layer (z-[1]) and highlight (z-0), below content (z-10)
6215
+ "z-[2]"
6216
+ ]);
6217
+ var menuItemIconVariants = classVarianceAuthority.cva(
6218
+ [
6219
+ "relative z-10 shrink-0 flex items-center justify-center",
6220
+ "transition-colors duration-spring-standard-fast-effects ease-spring-standard-fast-effects",
6221
+ // Disabled: 38% opacity on icon color
6222
+ "group-data-[disabled]/menuitem:text-on-surface/38"
6223
+ ],
6224
+ {
6225
+ variants: {
6226
+ colorScheme: {
6227
+ standard: ["text-on-surface-variant"],
6228
+ vibrant: ["text-on-tertiary-container"]
6186
6229
  },
6187
- // ── Vertical + Standard selection ───────────────────────────────────
6230
+ menuStyle: {
6231
+ baseline: ["h-6 w-6"],
6232
+ vertical: ["h-5 w-5"]
6233
+ }
6234
+ },
6235
+ compoundVariants: [
6236
+ // vertical + standard: selected/open icon → on-tertiary-container
6188
6237
  {
6189
- isSelected: true,
6190
6238
  menuStyle: "vertical",
6191
6239
  colorScheme: "standard",
6192
6240
  class: [
6193
- "bg-tertiary-container",
6194
- "text-on-tertiary-container",
6195
- "before:bg-on-tertiary-container"
6241
+ "group-data-[selected]/menuitem:text-on-tertiary-container",
6242
+ "group-data-[open]/menuitem:text-on-tertiary-container"
6196
6243
  ]
6197
6244
  },
6198
- // ── Vertical + Vibrant selection ─────────────────────────────────────
6245
+ // vertical + vibrant: selected/open icon → on-tertiary
6199
6246
  {
6200
- isSelected: true,
6201
6247
  menuStyle: "vertical",
6202
6248
  colorScheme: "vibrant",
6203
- class: ["bg-tertiary", "text-on-tertiary", "before:bg-on-tertiary"]
6249
+ class: [
6250
+ "group-data-[selected]/menuitem:text-on-tertiary",
6251
+ "group-data-[open]/menuitem:text-on-tertiary"
6252
+ ]
6204
6253
  }
6205
6254
  ],
6206
6255
  defaultVariants: {
6207
- isDisabled: false,
6208
- isSelected: false,
6209
6256
  colorScheme: "standard",
6210
6257
  menuStyle: "baseline"
6211
6258
  }
6212
6259
  }
6213
6260
  );
6214
6261
  var menuSectionVariants = classVarianceAuthority.cva(["flex flex-col w-full"]);
6215
- var menuSectionHeaderVariants = classVarianceAuthority.cva([
6216
- "px-3 pt-2 pb-1",
6217
- "text-title-small text-on-surface-variant",
6218
- "select-none"
6219
- ]);
6220
- var menuDividerVariants = classVarianceAuthority.cva(["border-t border-outline-variant", "my-2 mx-0"]);
6221
- classVarianceAuthority.cva(["h-2 w-full"]);
6222
- var menuItemTrailingTextVariants = classVarianceAuthority.cva([
6223
- "ml-auto shrink-0 text-label-large text-on-surface-variant",
6224
- "select-none"
6225
- ]);
6226
- var menuItemDescriptionVariants = classVarianceAuthority.cva([
6227
- "text-body-medium text-on-surface-variant",
6228
- "select-none"
6229
- ]);
6230
- var Menu = React.forwardRef(function Menu2({
6231
- children,
6232
- className,
6233
- colorScheme = "standard",
6234
- menuStyle = "baseline",
6235
- density = 0,
6236
- disableRipple = false,
6237
- selectionMode,
6238
- selectedKeys,
6239
- onSelectionChange,
6240
- ...props
6241
- }, _ref) {
6262
+ var menuSectionHeaderVariants = classVarianceAuthority.cva(
6263
+ [
6264
+ // 32dp tall region, content vertically centred, 12dp leading padding aligned with items
6265
+ "px-3 h-8 flex items-center",
6266
+ "text-title-small",
6267
+ "select-none"
6268
+ ],
6269
+ {
6270
+ variants: {
6271
+ colorScheme: {
6272
+ standard: ["text-on-surface-variant"],
6273
+ vibrant: ["text-on-tertiary-container"]
6274
+ }
6275
+ },
6276
+ defaultVariants: {
6277
+ colorScheme: "standard"
6278
+ }
6279
+ }
6280
+ );
6281
+ var menuDividerVariants = classVarianceAuthority.cva(["border-t border-outline-variant", "my-0.5 mx-3"]);
6282
+ classVarianceAuthority.cva(["h-0.5 w-full"]);
6283
+ var menuItemTrailingTextVariants = classVarianceAuthority.cva(
6284
+ [
6285
+ "ml-auto shrink-0 text-label-large",
6286
+ "select-none",
6287
+ "group-data-[disabled]/menuitem:text-on-surface/38"
6288
+ ],
6289
+ {
6290
+ variants: {
6291
+ colorScheme: {
6292
+ standard: ["text-on-surface-variant"],
6293
+ vibrant: ["text-on-tertiary-container"]
6294
+ },
6295
+ menuStyle: {
6296
+ baseline: [],
6297
+ vertical: [
6298
+ "group-data-[selected]/menuitem:text-on-tertiary-container",
6299
+ "group-data-[open]/menuitem:text-on-tertiary-container"
6300
+ ]
6301
+ }
6302
+ },
6303
+ compoundVariants: [
6304
+ // vertical + vibrant: selected/open trailing text → on-tertiary
6305
+ {
6306
+ menuStyle: "vertical",
6307
+ colorScheme: "vibrant",
6308
+ class: [
6309
+ "group-data-[selected]/menuitem:text-on-tertiary",
6310
+ "group-data-[open]/menuitem:text-on-tertiary"
6311
+ ]
6312
+ }
6313
+ ],
6314
+ defaultVariants: {
6315
+ colorScheme: "standard",
6316
+ menuStyle: "baseline"
6317
+ }
6318
+ }
6319
+ );
6320
+ var menuItemDescriptionVariants = classVarianceAuthority.cva(
6321
+ ["text-body-medium", "select-none", "group-data-[disabled]/menuitem:text-on-surface/38"],
6322
+ {
6323
+ variants: {
6324
+ colorScheme: {
6325
+ standard: ["text-on-surface-variant"],
6326
+ vibrant: ["text-on-tertiary-container"]
6327
+ },
6328
+ menuStyle: {
6329
+ baseline: [],
6330
+ vertical: [
6331
+ "group-data-[selected]/menuitem:text-on-tertiary-container",
6332
+ "group-data-[open]/menuitem:text-on-tertiary-container"
6333
+ ]
6334
+ }
6335
+ },
6336
+ compoundVariants: [
6337
+ // vertical + vibrant: selected/open description → on-tertiary
6338
+ {
6339
+ menuStyle: "vertical",
6340
+ colorScheme: "vibrant",
6341
+ class: [
6342
+ "group-data-[selected]/menuitem:text-on-tertiary",
6343
+ "group-data-[open]/menuitem:text-on-tertiary"
6344
+ ]
6345
+ }
6346
+ ],
6347
+ defaultVariants: {
6348
+ colorScheme: "standard",
6349
+ menuStyle: "baseline"
6350
+ }
6351
+ }
6352
+ );
6353
+ classVarianceAuthority.cva(
6354
+ [
6355
+ "flex flex-col w-full",
6356
+ "px-1 py-0.5 gap-0.5",
6357
+ "rounded-lg first:rounded-b-sm last:rounded-t-sm",
6358
+ "shadow-elevation-1"
6359
+ ],
6360
+ {
6361
+ variants: {
6362
+ menuStyle: {
6363
+ vertical: ["bg-surface-container-low"],
6364
+ baseline: []
6365
+ },
6366
+ colorScheme: {
6367
+ standard: ["bg-surface-container-low"],
6368
+ vibrant: ["bg-tertiary-container"]
6369
+ }
6370
+ },
6371
+ compoundVariants: [
6372
+ // vertical + standard: item background = surface-container-low
6373
+ {
6374
+ menuStyle: "vertical",
6375
+ colorScheme: "standard",
6376
+ class: ["bg-surface-container-low"]
6377
+ },
6378
+ // vertical + vibrant: item background = tertiary-container
6379
+ {
6380
+ menuStyle: "vertical",
6381
+ colorScheme: "vibrant",
6382
+ class: ["bg-tertiary-container"]
6383
+ }
6384
+ ],
6385
+ defaultVariants: {
6386
+ menuStyle: "vertical",
6387
+ colorScheme: "standard"
6388
+ }
6389
+ }
6390
+ );
6391
+ var MenuContext = React.createContext(null);
6392
+ function useMenuContext() {
6393
+ return React.useContext(MenuContext);
6394
+ }
6395
+ function TriggerBridge({ children }) {
6396
+ const ctx = reactAriaComponents.useSlottedContext(reactAriaComponents.ButtonContext);
6397
+ const localRef = React.useRef(null);
6398
+ const { ref: contextRef, ...ctxProps } = ctx ?? {};
6399
+ const mergedCallbackRef = React.useCallback(
6400
+ (node) => {
6401
+ localRef.current = node;
6402
+ if (!contextRef) return;
6403
+ if (typeof contextRef === "function") {
6404
+ contextRef(node);
6405
+ } else {
6406
+ contextRef.current = node;
6407
+ }
6408
+ },
6409
+ [contextRef]
6410
+ );
6411
+ const { buttonProps } = reactAria.useButton({ ...ctxProps, elementType: "button" }, localRef);
6412
+ if (!React.isValidElement(children)) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
6413
+ return React.cloneElement(
6414
+ children,
6415
+ { ...buttonProps, ref: mergedCallbackRef }
6416
+ );
6417
+ }
6418
+ function HeadlessMenuTrigger({
6419
+ children,
6420
+ placement = "bottom start",
6421
+ shouldFlip = true,
6422
+ ...rest
6423
+ }) {
6424
+ const childrenArray = Array.isArray(children) ? children : [children];
6425
+ const [triggerChild, menuChild] = childrenArray;
6426
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactAriaComponents.MenuTrigger, { ...rest, children: [
6427
+ /* @__PURE__ */ jsxRuntime.jsx(TriggerBridge, { children: triggerChild }),
6428
+ /* @__PURE__ */ jsxRuntime.jsx(
6429
+ reactAriaComponents.Popover,
6430
+ {
6431
+ placement,
6432
+ shouldFlip,
6433
+ offset: 4,
6434
+ className: menuPopoverVariants(),
6435
+ children: menuChild
6436
+ }
6437
+ )
6438
+ ] });
6439
+ }
6440
+ function HeadlessMenu({
6441
+ className,
6442
+ children,
6443
+ "aria-label": ariaLabel,
6444
+ ...props
6445
+ }) {
6446
+ const menuRef = React.useRef(null);
6447
+ React.useLayoutEffect(() => {
6448
+ if (ariaLabel && menuRef.current) {
6449
+ menuRef.current.removeAttribute("aria-labelledby");
6450
+ }
6451
+ });
6452
+ return /* @__PURE__ */ jsxRuntime.jsx(
6453
+ reactAriaComponents.Menu,
6454
+ {
6455
+ ...props,
6456
+ ref: menuRef,
6457
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
6458
+ className: className ?? "",
6459
+ children
6460
+ }
6461
+ );
6462
+ }
6463
+ HeadlessMenuTrigger.Menu = HeadlessMenu;
6464
+ var HeadlessMenuItem = React.forwardRef(
6465
+ function HeadlessMenuItem2({ children, className, ...props }, ref) {
6466
+ return /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.MenuItem, { ...props, ref, className: className ?? "", children });
6467
+ }
6468
+ );
6469
+ function HeadlessMenuSection({
6470
+ children,
6471
+ "aria-label": ariaLabel,
6472
+ className
6473
+ }) {
6474
+ return /* @__PURE__ */ jsxRuntime.jsx(
6475
+ reactAriaComponents.MenuSection,
6476
+ {
6477
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
6478
+ className: className ?? "",
6479
+ children
6480
+ }
6481
+ );
6482
+ }
6483
+ function HeadlessMenuDivider({
6484
+ className,
6485
+ ...props
6486
+ }) {
6487
+ return /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Separator, { ...props, className: className ?? "" });
6488
+ }
6489
+ var Menu = React.forwardRef(function Menu2({
6490
+ children,
6491
+ className,
6492
+ colorScheme = "standard",
6493
+ menuStyle = "baseline",
6494
+ density = 0,
6495
+ disableRipple = false,
6496
+ selectionMode,
6497
+ selectedKeys,
6498
+ onSelectionChange,
6499
+ ...props
6500
+ }, _ref) {
6242
6501
  const close = () => {
6243
6502
  };
6244
6503
  const contextValue = {
@@ -6286,7 +6545,13 @@ function CheckIcon() {
6286
6545
  }
6287
6546
  );
6288
6547
  }
6289
- var DENSITY_HEIGHT = {
6548
+ var BASELINE_DENSITY_HEIGHT = {
6549
+ 0: "h-12",
6550
+ [-1]: "h-11",
6551
+ [-2]: "h-10",
6552
+ [-3]: "h-9"
6553
+ };
6554
+ var VERTICAL_DENSITY_HEIGHT = {
6290
6555
  0: "h-12",
6291
6556
  [-1]: "h-11",
6292
6557
  [-2]: "h-10",
@@ -6309,19 +6574,18 @@ var MenuItem = React.forwardRef(function MenuItem2({
6309
6574
  const menuStyle = ctx?.menuStyle ?? "baseline";
6310
6575
  const density = ctx?.density ?? 0;
6311
6576
  const selectionMode = ctx?.selectionMode;
6312
- const heightClass = DENSITY_HEIGHT[density];
6577
+ const heightClass = menuStyle === "vertical" ? VERTICAL_DENSITY_HEIGHT[density] : BASELINE_DENSITY_HEIGHT[density];
6313
6578
  const isSelectionMenu = selectionMode != null;
6314
6579
  const { ripples, onMouseDown } = useRipple({ disabled: disableRipple });
6315
- const computeClassName = ({ isDisabled, isSelected }) => cn(
6316
- menuItemVariants({
6317
- isDisabled,
6318
- isSelected: isSelected ?? false,
6319
- colorScheme,
6320
- menuStyle
6321
- }),
6580
+ const computeClassName = ({ isSelected }) => cn(
6581
+ menuItemVariants({ colorScheme, menuStyle }),
6582
+ // group/menuitem scope: all slot children read state via group-data-[x]/menuitem
6583
+ "group/menuitem",
6322
6584
  // Height: auto when description is present (multi-line), otherwise density
6323
6585
  description ? "min-h-12 py-2 h-auto items-start" : heightClass,
6324
- className
6586
+ className,
6587
+ // Silence the isSelected lint — value consumed in render-prop below
6588
+ isSelected ? "" : ""
6325
6589
  );
6326
6590
  return /* @__PURE__ */ jsxRuntime.jsx(
6327
6591
  HeadlessMenuItem,
@@ -6331,24 +6595,48 @@ var MenuItem = React.forwardRef(function MenuItem2({
6331
6595
  className: computeClassName,
6332
6596
  onMouseDown,
6333
6597
  children: ({ isSelected }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6334
- !disableRipple && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none absolute inset-0 z-0 overflow-hidden rounded-[inherit]", children: ripples }),
6335
- (leadingIcon != null || isSelectionMenu) && /* @__PURE__ */ jsxRuntime.jsx(
6598
+ /* @__PURE__ */ jsxRuntime.jsx(
6336
6599
  "span",
6337
6600
  {
6338
- className: "text-on-surface-variant relative z-10 flex h-6 w-6 shrink-0 items-center justify-center",
6339
6601
  "aria-hidden": "true",
6340
- children: isSelectionMenu && leadingIcon == null ? isSelected ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null : leadingIcon
6602
+ "data-testid": "menuitem-highlight",
6603
+ className: menuItemHighlightVariants({ colorScheme, menuStyle })
6341
6604
  }
6342
6605
  ),
6606
+ /* @__PURE__ */ jsxRuntime.jsx(
6607
+ "span",
6608
+ {
6609
+ "aria-hidden": "true",
6610
+ className: menuItemStateLayerVariants({ colorScheme, menuStyle })
6611
+ }
6612
+ ),
6613
+ /* @__PURE__ */ jsxRuntime.jsx(
6614
+ "span",
6615
+ {
6616
+ "aria-hidden": "true",
6617
+ "data-testid": "menuitem-focus-ring",
6618
+ className: menuItemFocusRingVariants()
6619
+ }
6620
+ ),
6621
+ !disableRipple && /* @__PURE__ */ jsxRuntime.jsx(
6622
+ "span",
6623
+ {
6624
+ className: cn(
6625
+ "pointer-events-none absolute inset-0 z-[3] overflow-hidden rounded-[inherit]"
6626
+ ),
6627
+ children: ripples
6628
+ }
6629
+ ),
6630
+ (leadingIcon != null || isSelectionMenu) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: menuItemIconVariants({ colorScheme, menuStyle }), "aria-hidden": "true", children: isSelectionMenu && leadingIcon == null ? isSelected ? /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, {}) : null : leadingIcon }),
6343
6631
  description != null ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative z-10 flex min-w-0 flex-1 flex-col", children: [
6344
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-body-large", children }),
6345
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: menuItemDescriptionVariants(), children: description })
6346
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-body-large relative z-10 min-w-0 flex-1", children }),
6632
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-label-large group-data-[disabled]/menuitem:text-on-surface/38", children }),
6633
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: menuItemDescriptionVariants({ colorScheme, menuStyle }), children: description })
6634
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-label-large group-data-[disabled]/menuitem:text-on-surface/38 relative z-10 min-w-0 flex-1", children }),
6347
6635
  badge != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative z-10 shrink-0", children: badge }),
6348
6636
  trailingIcon != null && trailingText == null && /* @__PURE__ */ jsxRuntime.jsx(
6349
6637
  "span",
6350
6638
  {
6351
- className: "text-on-surface-variant relative z-10 ml-auto flex h-6 w-6 shrink-0 items-center justify-center",
6639
+ className: cn(menuItemIconVariants({ colorScheme, menuStyle }), "ml-auto"),
6352
6640
  "aria-hidden": "true",
6353
6641
  children: trailingIcon
6354
6642
  }
@@ -6356,7 +6644,10 @@ var MenuItem = React.forwardRef(function MenuItem2({
6356
6644
  trailingText != null && trailingIcon == null && /* @__PURE__ */ jsxRuntime.jsx(
6357
6645
  "span",
6358
6646
  {
6359
- className: cn(menuItemTrailingTextVariants(), "relative z-10"),
6647
+ className: cn(
6648
+ menuItemTrailingTextVariants({ colorScheme, menuStyle }),
6649
+ "relative z-10"
6650
+ ),
6360
6651
  "aria-keyshortcuts": trailingText,
6361
6652
  children: trailingText
6362
6653
  }
@@ -6372,6 +6663,8 @@ function MenuSection({
6372
6663
  className,
6373
6664
  "aria-label": ariaLabel
6374
6665
  }) {
6666
+ const ctx = useMenuContext();
6667
+ const colorScheme = ctx?.colorScheme ?? "standard";
6375
6668
  const sectionAriaLabel = ariaLabel ?? header;
6376
6669
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6377
6670
  showDivider && /* @__PURE__ */ jsxRuntime.jsx(HeadlessMenuDivider, { className: menuDividerVariants() }),
@@ -6381,7 +6674,7 @@ function MenuSection({
6381
6674
  "aria-label": sectionAriaLabel,
6382
6675
  className: cn(menuSectionVariants(), className),
6383
6676
  children: [
6384
- header && /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Header, { className: menuSectionHeaderVariants(), "aria-hidden": "true", children: header }),
6677
+ header && /* @__PURE__ */ jsxRuntime.jsx(reactAriaComponents.Header, { className: menuSectionHeaderVariants({ colorScheme }), "aria-hidden": "true", children: header }),
6385
6678
  children
6386
6679
  ]
6387
6680
  }
@@ -7721,13 +8014,14 @@ var DialogPanel = ({
7721
8014
  headlineId,
7722
8015
  contentId,
7723
8016
  onClose,
7724
- onTransitionEnd,
8017
+ onAnimationEnd,
7725
8018
  variant,
7726
8019
  isDismissable,
7727
8020
  wrapperClassName,
7728
8021
  className,
7729
8022
  animationState,
7730
8023
  getAnimationClassName,
8024
+ icon,
7731
8025
  children
7732
8026
  }) => {
7733
8027
  const panelRef = React.useRef(null);
@@ -7750,9 +8044,10 @@ var DialogPanel = ({
7750
8044
  panelRef
7751
8045
  );
7752
8046
  const panelClassName = cn(className, getAnimationClassName?.(animationState));
8047
+ const hasIcon = icon !== void 0 && icon !== null && variant === "basic";
7753
8048
  return (
7754
8049
  // Centering/positioning wrapper — structural only, no ARIA role
7755
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsxRuntime.jsx(
8050
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsxRuntime.jsxs(
7756
8051
  "div",
7757
8052
  {
7758
8053
  ...utils.mergeProps(overlayProps, dialogProps),
@@ -7761,8 +8056,12 @@ var DialogPanel = ({
7761
8056
  className: panelClassName,
7762
8057
  "data-animation-state": animationState,
7763
8058
  "data-variant": variant,
7764
- onTransitionEnd,
7765
- children
8059
+ "data-with-icon": hasIcon ? "" : void 0,
8060
+ onAnimationEnd,
8061
+ children: [
8062
+ hasIcon && icon,
8063
+ children
8064
+ ]
7766
8065
  }
7767
8066
  ) })
7768
8067
  );
@@ -7775,9 +8074,11 @@ var DialogHeadless = React.forwardRef(
7775
8074
  defaultOpen = false,
7776
8075
  onOpenChange,
7777
8076
  "aria-label": ariaLabel,
8077
+ icon,
7778
8078
  children,
7779
8079
  className,
7780
- scrimClassName,
8080
+ wrapperClassName,
8081
+ getScrimClassName,
7781
8082
  getAnimationClassName
7782
8083
  }, _ref) {
7783
8084
  const state = reactStately.useOverlayTriggerState({
@@ -7811,7 +8112,7 @@ var DialogHeadless = React.forwardRef(
7811
8112
  closedRef.current = true;
7812
8113
  setAnimationState("exited");
7813
8114
  }
7814
- }, 150);
8115
+ }, 250);
7815
8116
  }
7816
8117
  }, [isOpen, animationState]);
7817
8118
  React.useEffect(
@@ -7822,16 +8123,20 @@ var DialogHeadless = React.forwardRef(
7822
8123
  },
7823
8124
  []
7824
8125
  );
7825
- const handleTransitionEnd = React.useCallback(() => {
7826
- if (animationState === "exiting" && !closedRef.current) {
7827
- if (exitFallbackRef.current !== null) {
7828
- clearTimeout(exitFallbackRef.current);
7829
- exitFallbackRef.current = null;
8126
+ const handleAnimationEnd = React.useCallback(
8127
+ (e) => {
8128
+ if (e.target !== e.currentTarget) return;
8129
+ if (animationState === "exiting" && !closedRef.current) {
8130
+ if (exitFallbackRef.current !== null) {
8131
+ clearTimeout(exitFallbackRef.current);
8132
+ exitFallbackRef.current = null;
8133
+ }
8134
+ closedRef.current = true;
8135
+ setAnimationState("exited");
7830
8136
  }
7831
- closedRef.current = true;
7832
- setAnimationState("exited");
7833
- }
7834
- }, [animationState]);
8137
+ },
8138
+ [animationState]
8139
+ );
7835
8140
  const baseId = React.useId();
7836
8141
  const headlineId = `${baseId}-dialog-headline`;
7837
8142
  const contentId = `${baseId}-dialog-content`;
@@ -7846,6 +8151,8 @@ var DialogHeadless = React.forwardRef(
7846
8151
  close();
7847
8152
  }
7848
8153
  }, [variant, close]);
8154
+ const resolvedWrapperClass = wrapperClassName ?? (variant === "basic" ? "fixed inset-0 z-50 flex items-center justify-center px-4" : "fixed inset-0 z-50");
8155
+ const resolvedScrimClass = getScrimClassName?.(animationState) ?? "fixed inset-0 z-40 bg-scrim/32";
7849
8156
  if (!isOpen && animationState === "exited") {
7850
8157
  return null;
7851
8158
  }
@@ -7854,7 +8161,7 @@ var DialogHeadless = React.forwardRef(
7854
8161
  "div",
7855
8162
  {
7856
8163
  "data-testid": "dialog-scrim",
7857
- className: scrimClassName,
8164
+ className: resolvedScrimClass,
7858
8165
  onClick: handleScrimClick,
7859
8166
  "aria-hidden": "true"
7860
8167
  }
@@ -7866,13 +8173,14 @@ var DialogHeadless = React.forwardRef(
7866
8173
  headlineId,
7867
8174
  contentId,
7868
8175
  onClose: close,
7869
- onTransitionEnd: handleTransitionEnd,
8176
+ onAnimationEnd: handleAnimationEnd,
7870
8177
  variant,
7871
8178
  isDismissable: variant === "basic",
7872
- wrapperClassName: variant === "basic" ? "fixed inset-0 z-50 flex items-center justify-center px-4" : "fixed inset-0 z-50",
8179
+ wrapperClassName: resolvedWrapperClass,
7873
8180
  className,
7874
8181
  animationState,
7875
8182
  getAnimationClassName,
8183
+ icon,
7876
8184
  children
7877
8185
  }
7878
8186
  ) })
@@ -7882,16 +8190,28 @@ var DialogHeadless = React.forwardRef(
7882
8190
  }
7883
8191
  );
7884
8192
  DialogHeadless.displayName = "DialogHeadless";
7885
- var dialogScrimVariants = classVarianceAuthority.cva([
7886
- "fixed",
7887
- "inset-0",
7888
- "z-40",
7889
- "bg-scrim",
7890
- "opacity-32",
7891
- "transition-opacity",
7892
- "duration-medium2",
7893
- "ease-standard"
7894
- ]);
8193
+ var dialogScrimVariants = classVarianceAuthority.cva(
8194
+ [
8195
+ "fixed",
8196
+ "inset-0",
8197
+ "z-40",
8198
+ // MD3 scrim: bg-scrim at 32% opacity — always set so instant-show works in reduced-motion
8199
+ "bg-scrim/32"
8200
+ ],
8201
+ {
8202
+ variants: {
8203
+ animationState: {
8204
+ entering: ["opacity-0"],
8205
+ visible: ["animate-md-fade-in"],
8206
+ exiting: ["animate-md-fade-out"],
8207
+ exited: ["opacity-0", "pointer-events-none"]
8208
+ }
8209
+ },
8210
+ defaultVariants: {
8211
+ animationState: "entering"
8212
+ }
8213
+ }
8214
+ );
7895
8215
  var dialogPanelVariants = classVarianceAuthority.cva(
7896
8216
  [
7897
8217
  // Stacking above scrim
@@ -7901,9 +8221,10 @@ var dialogPanelVariants = classVarianceAuthority.cva(
7901
8221
  // Flex column layout for slots
7902
8222
  "flex",
7903
8223
  "flex-col",
7904
- // Transition for animation state changes
7905
- "transition-[opacity,transform]",
7906
- "will-change-[opacity,transform]"
8224
+ // Compositor hint for keyframe animation
8225
+ "will-change-[opacity,transform]",
8226
+ // group scope — lets child slots consume data-with-icon via group-data-[with-icon]/dialog:
8227
+ "group/dialog"
7907
8228
  ],
7908
8229
  {
7909
8230
  variants: {
@@ -7917,11 +8238,11 @@ var dialogPanelVariants = classVarianceAuthority.cva(
7917
8238
  "min-w-70",
7918
8239
  "max-w-dialog-max",
7919
8240
  "w-full",
7920
- // Internal spacing
8241
+ // Internal spacing: 24dp padding, headline mb-4, content mb-6, actions pt-3
7921
8242
  "pt-6",
7922
8243
  "pb-3",
7923
8244
  "px-6",
7924
- // Positioned in viewport center
8245
+ // Positioning (centering wrapper handles the viewport centering)
7925
8246
  "relative"
7926
8247
  ],
7927
8248
  fullscreen: [
@@ -7932,7 +8253,6 @@ var dialogPanelVariants = classVarianceAuthority.cva(
7932
8253
  "rounded-none",
7933
8254
  // No elevation shadow on fullscreen
7934
8255
  "shadow-none",
7935
- // Positioned to fill portal
7936
8256
  "relative"
7937
8257
  ]
7938
8258
  }
@@ -7942,7 +8262,7 @@ var dialogPanelVariants = classVarianceAuthority.cva(
7942
8262
  }
7943
8263
  }
7944
8264
  );
7945
- classVarianceAuthority.cva([], {
8265
+ var dialogWrapperVariants = classVarianceAuthority.cva([], {
7946
8266
  variants: {
7947
8267
  variant: {
7948
8268
  basic: ["fixed", "inset-0", "z-50", "flex", "items-center", "justify-center", "px-4"],
@@ -7956,9 +8276,13 @@ classVarianceAuthority.cva([], {
7956
8276
  var dialogAnimationVariants = classVarianceAuthority.cva("", {
7957
8277
  variants: {
7958
8278
  animationState: {
8279
+ // initial mount frame before the animation starts — rendered invisible
7959
8280
  entering: [],
8281
+ // entry animation active
7960
8282
  visible: [],
8283
+ // exit animation active
7961
8284
  exiting: [],
8285
+ // fully dismissed; portal gate will remove the element
7962
8286
  exited: []
7963
8287
  },
7964
8288
  variant: {
@@ -7967,53 +8291,55 @@ var dialogAnimationVariants = classVarianceAuthority.cva("", {
7967
8291
  }
7968
8292
  },
7969
8293
  compoundVariants: [
7970
- // Basic: entering — start scaled down + transparent
8294
+ // ── Basic ────────────────────────────────────────────────────────────────
8295
+ // entering: start invisible (animate-md-scale-in keyframe starts from scale(0.85)/opacity:0)
7971
8296
  {
7972
8297
  animationState: "entering",
7973
8298
  variant: "basic",
7974
- className: ["scale-90", "opacity-0"]
8299
+ className: ["opacity-0"]
7975
8300
  },
7976
- // Basic: visible scale to full + fade in
8301
+ // visible: composite scale-in keyframe (expressive-fast-spatial 350ms)
7977
8302
  {
7978
8303
  animationState: "visible",
7979
8304
  variant: "basic",
7980
- className: ["scale-100", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
8305
+ className: ["animate-md-scale-in"]
7981
8306
  },
7982
- // Basic: exiting fade out (scale stays at 1)
8307
+ // exiting: composite scale-out keyframe (emphasized-accelerate 200ms)
7983
8308
  {
7984
8309
  animationState: "exiting",
7985
8310
  variant: "basic",
7986
- className: ["scale-100", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
8311
+ className: ["animate-md-scale-out"]
7987
8312
  },
7988
- // Basic: exited fully transparent
8313
+ // exited: keep hidden until portal gate removes it
7989
8314
  {
7990
8315
  animationState: "exited",
7991
8316
  variant: "basic",
7992
- className: ["scale-100", "opacity-0"]
8317
+ className: ["opacity-0", "pointer-events-none"]
7993
8318
  },
7994
- // Fullscreen: entering — start below viewport + transparent
8319
+ // ── Fullscreen ───────────────────────────────────────────────────────────
8320
+ // entering: start off-screen below (slide-in-bottom starts from translateY(100%)/opacity:0)
7995
8321
  {
7996
8322
  animationState: "entering",
7997
8323
  variant: "fullscreen",
7998
- className: ["translate-y-full", "opacity-0"]
8324
+ className: ["opacity-0"]
7999
8325
  },
8000
- // Fullscreen: visible slide up + fade in
8326
+ // visible: composite slide-in-bottom keyframe (standard-default-spatial 500ms)
8001
8327
  {
8002
8328
  animationState: "visible",
8003
8329
  variant: "fullscreen",
8004
- className: ["translate-y-0", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
8330
+ className: ["animate-md-slide-in-bottom"]
8005
8331
  },
8006
- // Fullscreen: exiting slide down + fade out
8332
+ // exiting: composite slide-out-bottom keyframe (emphasized-accelerate 200ms)
8007
8333
  {
8008
8334
  animationState: "exiting",
8009
8335
  variant: "fullscreen",
8010
- className: ["translate-y-full", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
8336
+ className: ["animate-md-slide-out-bottom"]
8011
8337
  },
8012
- // Fullscreen: exited fully off-screen
8338
+ // exited: keep hidden until portal gate removes it
8013
8339
  {
8014
8340
  animationState: "exited",
8015
8341
  variant: "fullscreen",
8016
- className: ["translate-y-full", "opacity-0"]
8342
+ className: ["opacity-0", "pointer-events-none"]
8017
8343
  }
8018
8344
  ],
8019
8345
  defaultVariants: {
@@ -8021,10 +8347,26 @@ var dialogAnimationVariants = classVarianceAuthority.cva("", {
8021
8347
  variant: "basic"
8022
8348
  }
8023
8349
  });
8350
+ classVarianceAuthority.cva([
8351
+ // Center the icon in the panel
8352
+ "flex",
8353
+ "items-center",
8354
+ "justify-center",
8355
+ // Bottom margin separating icon from headline
8356
+ "mb-4",
8357
+ // MD3 spec: icon color = secondary
8358
+ "text-secondary",
8359
+ // 24dp icon size (children — typically an SVG — should be 24×24)
8360
+ "size-6"
8361
+ ]);
8024
8362
  var dialogHeadlineVariants = classVarianceAuthority.cva(["text-headline-small", "text-on-surface"], {
8025
8363
  variants: {
8026
8364
  variant: {
8027
- basic: ["mb-4"],
8365
+ basic: [
8366
+ "mb-4",
8367
+ // Center headline text when hero icon is present
8368
+ "group-data-[with-icon]/dialog:text-center"
8369
+ ],
8028
8370
  fullscreen: [
8029
8371
  // Top app bar row in fullscreen: flex, items-center, gap
8030
8372
  "flex",
@@ -8049,7 +8391,19 @@ var dialogHeadlineTitleVariants = classVarianceAuthority.cva([
8049
8391
  "truncate"
8050
8392
  ]);
8051
8393
  var dialogContentVariants = classVarianceAuthority.cva(
8052
- ["text-body-medium", "text-on-surface-variant", "overflow-y-auto", "flex-1"],
8394
+ [
8395
+ "text-body-medium",
8396
+ "text-on-surface-variant",
8397
+ "overflow-y-auto",
8398
+ "flex-1",
8399
+ // Center supporting text when hero icon is present
8400
+ "group-data-[with-icon]/dialog:text-center",
8401
+ // Scroll dividers — activated by DialogContent's scroll handler
8402
+ "data-[scroll-divider-top]:border-t",
8403
+ "data-[scroll-divider-top]:border-outline-variant",
8404
+ "data-[scroll-divider-bottom]:border-b",
8405
+ "data-[scroll-divider-bottom]:border-outline-variant"
8406
+ ],
8053
8407
  {
8054
8408
  variants: {
8055
8409
  variant: {
@@ -8076,11 +8430,31 @@ var Dialog = React.forwardRef(function Dialog2({
8076
8430
  defaultOpen = false,
8077
8431
  onOpenChange,
8078
8432
  "aria-label": ariaLabel,
8433
+ icon,
8079
8434
  children,
8080
8435
  className
8081
8436
  }, _ref) {
8082
- const panelClassName = cn(dialogPanelVariants({ variant }), className);
8083
- const scrimClass = dialogScrimVariants();
8437
+ const reducedMotion = useReducedMotion();
8438
+ const panelClassName = cn(
8439
+ dialogPanelVariants({ variant }),
8440
+ reducedMotion && "transition-none",
8441
+ className
8442
+ );
8443
+ const wrapperClassName = dialogWrapperVariants({ variant });
8444
+ const getScrimClassName = React.useCallback(
8445
+ (state) => {
8446
+ if (reducedMotion) return "fixed inset-0 z-40 bg-scrim/32";
8447
+ return dialogScrimVariants({ animationState: state });
8448
+ },
8449
+ [reducedMotion]
8450
+ );
8451
+ const getAnimationClassName = React.useCallback(
8452
+ (state) => {
8453
+ if (reducedMotion) return "";
8454
+ return dialogAnimationVariants({ animationState: state, variant });
8455
+ },
8456
+ [reducedMotion, variant]
8457
+ );
8084
8458
  return /* @__PURE__ */ jsxRuntime.jsx(
8085
8459
  DialogHeadless,
8086
8460
  {
@@ -8089,9 +8463,11 @@ var Dialog = React.forwardRef(function Dialog2({
8089
8463
  ...defaultOpen !== void 0 ? { defaultOpen } : {},
8090
8464
  ...onOpenChange !== void 0 ? { onOpenChange } : {},
8091
8465
  ...ariaLabel ? { "aria-label": ariaLabel } : {},
8466
+ ...icon !== void 0 ? { icon } : {},
8092
8467
  className: panelClassName,
8093
- scrimClassName: scrimClass,
8094
- getAnimationClassName: (state) => dialogAnimationVariants({ animationState: state, variant }),
8468
+ wrapperClassName,
8469
+ getScrimClassName,
8470
+ getAnimationClassName,
8095
8471
  children
8096
8472
  }
8097
8473
  );
@@ -8102,7 +8478,7 @@ var DialogHeadline = React.forwardRef(
8102
8478
  const { headlineId, variant } = useDialogContext();
8103
8479
  if (variant === "fullscreen") {
8104
8480
  return (
8105
- // Top app bar row for fullscreen variant
8481
+ // Top app bar row for fullscreen variant — always has border-b border-outline-variant
8106
8482
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(dialogHeadlineVariants({ variant: "fullscreen" }), className), children: [
8107
8483
  closeButton,
8108
8484
  /* @__PURE__ */ jsxRuntime.jsx("h2", { id: headlineId, className: dialogHeadlineTitleVariants(), children }),
@@ -8110,21 +8486,72 @@ var DialogHeadline = React.forwardRef(
8110
8486
  ] })
8111
8487
  );
8112
8488
  }
8113
- return /* @__PURE__ */ jsxRuntime.jsx(
8114
- "h2",
8115
- {
8116
- ref,
8117
- id: headlineId,
8118
- className: cn(dialogHeadlineVariants({ variant: "basic" }), className),
8119
- children
8120
- }
8489
+ return (
8490
+ // Basic variant: text-headline-small, text-on-surface, mb-4
8491
+ // group-data-[with-icon]/dialog:text-center is applied via the CVA base classes
8492
+ // when the parent panel root has data-with-icon set by DialogHeadless.
8493
+ /* @__PURE__ */ jsxRuntime.jsx(
8494
+ "h2",
8495
+ {
8496
+ ref,
8497
+ id: headlineId,
8498
+ className: cn(dialogHeadlineVariants({ variant: "basic" }), className),
8499
+ children
8500
+ }
8501
+ )
8121
8502
  );
8122
8503
  }
8123
8504
  );
8124
8505
  DialogHeadline.displayName = "DialogHeadline";
8125
- var DialogContent = React.forwardRef(function DialogContent2({ children, className }, ref) {
8506
+ var DialogContent = React.forwardRef(function DialogContent2({ children, className }, forwardedRef) {
8126
8507
  const { contentId, variant } = useDialogContext();
8127
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, id: contentId, className: cn(dialogContentVariants({ variant }), className), children });
8508
+ const internalRef = React.useRef(null);
8509
+ const setRef = React.useCallback(
8510
+ (node) => {
8511
+ internalRef.current = node;
8512
+ if (typeof forwardedRef === "function") {
8513
+ forwardedRef(node);
8514
+ } else if (forwardedRef !== null && forwardedRef !== void 0) {
8515
+ forwardedRef.current = node;
8516
+ }
8517
+ },
8518
+ [forwardedRef]
8519
+ );
8520
+ const updateDividers = React.useCallback(() => {
8521
+ const el = internalRef.current;
8522
+ if (!el) return;
8523
+ const isScrollable = el.scrollHeight > el.clientHeight;
8524
+ if (!isScrollable) {
8525
+ el.removeAttribute("data-scroll-divider-top");
8526
+ el.removeAttribute("data-scroll-divider-bottom");
8527
+ return;
8528
+ }
8529
+ const scrolledFromTop = el.scrollTop > 1;
8530
+ const scrolledToBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 1;
8531
+ if (scrolledFromTop) {
8532
+ el.setAttribute("data-scroll-divider-top", "");
8533
+ } else {
8534
+ el.removeAttribute("data-scroll-divider-top");
8535
+ }
8536
+ if (!scrolledToBottom) {
8537
+ el.setAttribute("data-scroll-divider-bottom", "");
8538
+ } else {
8539
+ el.removeAttribute("data-scroll-divider-bottom");
8540
+ }
8541
+ }, []);
8542
+ React.useEffect(() => {
8543
+ const el = internalRef.current;
8544
+ if (!el) return;
8545
+ updateDividers();
8546
+ el.addEventListener("scroll", updateDividers, { passive: true });
8547
+ const observer = new ResizeObserver(updateDividers);
8548
+ observer.observe(el);
8549
+ return () => {
8550
+ el.removeEventListener("scroll", updateDividers);
8551
+ observer.disconnect();
8552
+ };
8553
+ }, [updateDividers]);
8554
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: setRef, id: contentId, className: cn(dialogContentVariants({ variant }), className), children });
8128
8555
  });
8129
8556
  DialogContent.displayName = "DialogContent";
8130
8557
  var DialogActions = React.forwardRef(function DialogActions2({ children, className }, ref) {