@tinybigui/react 0.2.0 → 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.js CHANGED
@@ -1,15 +1,49 @@
1
1
  import { clsx } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
2
+ import { extendTailwindMerge } from 'tailwind-merge';
3
3
  import { argbFromHex, themeFromSourceColor } from '@material/material-color-utilities';
4
4
  export { argbFromHex, hexFromArgb } from '@material/material-color-utilities';
5
- import { forwardRef, useRef, useEffect, createContext, useContext, useMemo, useCallback, useState, useLayoutEffect, Children, isValidElement } from 'react';
5
+ import { forwardRef, useRef, useEffect, createContext, useContext, useMemo, useCallback, useState, useLayoutEffect, useId, Children, isValidElement, cloneElement } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
7
  import { cva } from 'class-variance-authority';
8
- import { useButton, useTextField, useFocusRing, useCheckbox, VisuallyHidden, mergeProps as mergeProps$1, useSwitch, useRadioGroup, useRadio, useTabList, useTab, useTabPanel, FocusScope, usePreventScroll, useDialog, useOverlay, useLink } from 'react-aria';
8
+ import { useButton, useTextField, useFocusRing, useCheckbox, VisuallyHidden, mergeProps as mergeProps$1, useSwitch, useRadioGroup, useRadio, useTabList, useTab, useTabPanel, FocusScope, usePreventScroll, useDialog, useOverlay, useLink, useProgressBar } from 'react-aria';
9
9
  import { mergeProps, filterDOMProps } from '@react-aria/utils';
10
10
  import { useToggleState, useRadioGroupState, useTabListState, Item, useOverlayTriggerState } from 'react-stately';
11
+ import { MenuItem as MenuItem$1, Menu as Menu$1, MenuTrigger as MenuTrigger$1, Popover, MenuSection as MenuSection$1, Separator, Header, useSlottedContext, ButtonContext } from 'react-aria-components';
12
+ import { createPortal } from 'react-dom';
11
13
 
12
14
  // src/utils/cn.ts
15
+ var twMerge = extendTailwindMerge({
16
+ extend: {
17
+ classGroups: {
18
+ "font-size": [
19
+ {
20
+ text: [
21
+ // MD3 Display scale
22
+ "display-large",
23
+ "display-medium",
24
+ "display-small",
25
+ // MD3 Headline scale
26
+ "headline-large",
27
+ "headline-medium",
28
+ "headline-small",
29
+ // MD3 Title scale
30
+ "title-large",
31
+ "title-medium",
32
+ "title-small",
33
+ // MD3 Body scale
34
+ "body-large",
35
+ "body-medium",
36
+ "body-small",
37
+ // MD3 Label scale
38
+ "label-large",
39
+ "label-medium",
40
+ "label-small"
41
+ ]
42
+ }
43
+ ]
44
+ }
45
+ }
46
+ });
13
47
  function cn(...inputs) {
14
48
  return twMerge(clsx(inputs));
15
49
  }
@@ -4498,7 +4532,1608 @@ var DrawerSection = forwardRef(
4498
4532
  }
4499
4533
  );
4500
4534
  DrawerSection.displayName = "DrawerSection";
4535
+ var progressContainerVariants = cva(["inline-flex", "flex-col", "gap-1"], {
4536
+ variants: {
4537
+ /**
4538
+ * The visual type of the indicator.
4539
+ */
4540
+ type: {
4541
+ linear: "w-full",
4542
+ circular: "items-center justify-center w-auto"
4543
+ }
4544
+ },
4545
+ defaultVariants: {
4546
+ type: "linear"
4547
+ }
4548
+ });
4549
+ var progressTrackVariants = cva([
4550
+ "relative",
4551
+ "w-full",
4552
+ "h-1",
4553
+ // MD3: 4dp track height
4554
+ "rounded-full",
4555
+ // MD3: full corner radius
4556
+ "overflow-hidden",
4557
+ "bg-surface-container-highest"
4558
+ // MD3: inactive track color
4559
+ ]);
4560
+ var progressIndicatorVariants = cva([
4561
+ "absolute",
4562
+ "left-0",
4563
+ "top-0",
4564
+ "h-full",
4565
+ "rounded-full",
4566
+ "bg-primary",
4567
+ // MD3: active track color
4568
+ "transition-[width]",
4569
+ "duration-medium4",
4570
+ // MD3: 400ms for value transitions
4571
+ "ease-standard"
4572
+ // MD3: cubic-bezier(0.2, 0, 0, 1)
4573
+ ]);
4574
+ var progressStopIndicatorVariants = cva([
4575
+ "absolute",
4576
+ "right-0",
4577
+ "top-1/2",
4578
+ "-translate-y-1/2",
4579
+ "w-1",
4580
+ "h-1",
4581
+ "rounded-full",
4582
+ "bg-primary"
4583
+ // MD3: stop indicator uses primary color
4584
+ ]);
4585
+ var progressCircularSizeVariants = cva(
4586
+ ["relative", "flex", "items-center", "justify-center", "flex-shrink-0"],
4587
+ {
4588
+ variants: {
4589
+ size: {
4590
+ small: "h-6 w-6",
4591
+ // MD3: 24dp
4592
+ medium: "h-12 w-12",
4593
+ // MD3: 48dp (default)
4594
+ large: "h-16 w-16"
4595
+ // MD3: 64dp
4596
+ }
4597
+ },
4598
+ defaultVariants: {
4599
+ size: "medium"
4600
+ }
4601
+ }
4602
+ );
4603
+ var progressLabelVariants = cva([
4604
+ "text-body-small",
4605
+ // MD3: body-small type scale (12px)
4606
+ "text-on-surface",
4607
+ // MD3: on-surface color role
4608
+ "select-none"
4609
+ ]);
4610
+ var STROKE_WIDTH = 4;
4611
+ var CIRCULAR_SIZE_PX = {
4612
+ small: 24,
4613
+ medium: 48,
4614
+ large: 64
4615
+ };
4616
+ function getCircularGeometry(size) {
4617
+ const diameter = CIRCULAR_SIZE_PX[size];
4618
+ const radius = (diameter - STROKE_WIDTH) / 2;
4619
+ const circumference = 2 * Math.PI * radius;
4620
+ const viewBox = `0 0 ${diameter} ${diameter}`;
4621
+ const cx = diameter / 2;
4622
+ const cy = diameter / 2;
4623
+ return { diameter, radius, circumference, viewBox, cx, cy };
4624
+ }
4625
+ var Progress = forwardRef(
4626
+ ({
4627
+ type = "linear",
4628
+ indeterminate = false,
4629
+ size = "medium",
4630
+ className,
4631
+ label,
4632
+ value = 0,
4633
+ minValue = 0,
4634
+ maxValue = 100,
4635
+ ...restProps
4636
+ }, forwardedRef) => {
4637
+ const internalRef = useRef(null);
4638
+ const ref = forwardedRef ?? internalRef;
4639
+ const { progressBarProps, labelProps } = useProgressBar({
4640
+ label,
4641
+ value,
4642
+ minValue,
4643
+ maxValue,
4644
+ isIndeterminate: indeterminate,
4645
+ ...restProps
4646
+ });
4647
+ const percentage = indeterminate ? 0 : Math.min(100, Math.max(0, (value - minValue) / (maxValue - minValue) * 100));
4648
+ if (process.env.NODE_ENV !== "production") {
4649
+ const ariaProps = restProps;
4650
+ if (!label && !ariaProps["aria-label"] && !ariaProps["aria-labelledby"]) {
4651
+ console.warn(
4652
+ "[Progress] Progress indicator should have a visible label prop or aria-label for accessibility."
4653
+ );
4654
+ }
4655
+ }
4656
+ return /* @__PURE__ */ jsxs(
4657
+ "div",
4658
+ {
4659
+ ...progressBarProps,
4660
+ ref,
4661
+ className: cn(progressContainerVariants({ type }), className),
4662
+ children: [
4663
+ label && /* @__PURE__ */ jsx("span", { ...labelProps, className: cn(progressLabelVariants()), children: label }),
4664
+ type === "linear" ? /* @__PURE__ */ jsx(LinearProgress, { percentage, indeterminate }) : /* @__PURE__ */ jsx(CircularProgress, { percentage, indeterminate, size })
4665
+ ]
4666
+ }
4667
+ );
4668
+ }
4669
+ );
4670
+ Progress.displayName = "Progress";
4671
+ function LinearProgress({ percentage, indeterminate }) {
4672
+ if (indeterminate) {
4673
+ return /* @__PURE__ */ jsx("div", { "data-progress-track": "", className: cn(progressTrackVariants()), children: /* @__PURE__ */ jsxs(
4674
+ "div",
4675
+ {
4676
+ "data-progress-indeterminate": "",
4677
+ className: "absolute inset-0 overflow-hidden rounded-full",
4678
+ children: [
4679
+ /* @__PURE__ */ jsx(
4680
+ "div",
4681
+ {
4682
+ className: cn(
4683
+ "bg-primary absolute top-0 h-full rounded-full",
4684
+ "animate-progress-linear-indeterminate-1"
4685
+ )
4686
+ }
4687
+ ),
4688
+ /* @__PURE__ */ jsx(
4689
+ "div",
4690
+ {
4691
+ className: cn(
4692
+ "bg-primary absolute top-0 h-full rounded-full",
4693
+ "animate-progress-linear-indeterminate-2"
4694
+ )
4695
+ }
4696
+ )
4697
+ ]
4698
+ }
4699
+ ) });
4700
+ }
4701
+ return /* @__PURE__ */ jsxs("div", { "data-progress-track": "", className: cn(progressTrackVariants()), children: [
4702
+ /* @__PURE__ */ jsx(
4703
+ "div",
4704
+ {
4705
+ "data-progress-indicator": "",
4706
+ className: cn(progressIndicatorVariants()),
4707
+ style: { width: `${percentage}%` }
4708
+ }
4709
+ ),
4710
+ /* @__PURE__ */ jsx(
4711
+ "div",
4712
+ {
4713
+ "data-stop-indicator": "",
4714
+ className: cn(progressStopIndicatorVariants()),
4715
+ "aria-hidden": "true"
4716
+ }
4717
+ )
4718
+ ] });
4719
+ }
4720
+ function CircularProgress({
4721
+ percentage,
4722
+ indeterminate,
4723
+ size
4724
+ }) {
4725
+ const { radius, circumference, viewBox, cx, cy } = getCircularGeometry(size);
4726
+ const strokeDashoffset = (1 - percentage / 100) * circumference;
4727
+ if (indeterminate) {
4728
+ return /* @__PURE__ */ jsx(
4729
+ "div",
4730
+ {
4731
+ "data-progress-size": size,
4732
+ "data-progress-indeterminate": "",
4733
+ className: cn(progressCircularSizeVariants({ size })),
4734
+ children: /* @__PURE__ */ jsxs(
4735
+ "svg",
4736
+ {
4737
+ viewBox,
4738
+ className: "animate-progress-circular-rotate h-full w-full",
4739
+ "aria-hidden": "true",
4740
+ children: [
4741
+ /* @__PURE__ */ jsx(
4742
+ "circle",
4743
+ {
4744
+ cx,
4745
+ cy,
4746
+ r: radius,
4747
+ fill: "none",
4748
+ stroke: "currentColor",
4749
+ strokeWidth: STROKE_WIDTH,
4750
+ className: "text-surface-container-highest"
4751
+ }
4752
+ ),
4753
+ /* @__PURE__ */ jsx(
4754
+ "circle",
4755
+ {
4756
+ cx,
4757
+ cy,
4758
+ r: radius,
4759
+ fill: "none",
4760
+ stroke: "currentColor",
4761
+ strokeWidth: STROKE_WIDTH,
4762
+ className: "animate-progress-circular-dash text-primary",
4763
+ strokeLinecap: "round"
4764
+ }
4765
+ )
4766
+ ]
4767
+ }
4768
+ )
4769
+ }
4770
+ );
4771
+ }
4772
+ return /* @__PURE__ */ jsx("div", { "data-progress-size": size, className: cn(progressCircularSizeVariants({ size })), children: /* @__PURE__ */ jsxs("svg", { viewBox, className: "h-full w-full -rotate-90", "aria-hidden": "true", children: [
4773
+ /* @__PURE__ */ jsx(
4774
+ "circle",
4775
+ {
4776
+ cx,
4777
+ cy,
4778
+ r: radius,
4779
+ fill: "none",
4780
+ stroke: "currentColor",
4781
+ strokeWidth: STROKE_WIDTH,
4782
+ className: "text-surface-container-highest"
4783
+ }
4784
+ ),
4785
+ /* @__PURE__ */ jsx(
4786
+ "circle",
4787
+ {
4788
+ cx,
4789
+ cy,
4790
+ r: radius,
4791
+ fill: "none",
4792
+ stroke: "currentColor",
4793
+ strokeWidth: STROKE_WIDTH,
4794
+ strokeLinecap: "round",
4795
+ className: "text-primary duration-medium4 ease-standard transition-[stroke-dashoffset]",
4796
+ strokeDasharray: circumference,
4797
+ strokeDashoffset
4798
+ }
4799
+ )
4800
+ ] }) });
4801
+ }
4802
+ var ProgressHeadless = forwardRef(
4803
+ ({
4804
+ type = "linear",
4805
+ indeterminate = false,
4806
+ size = "medium",
4807
+ className,
4808
+ children,
4809
+ renderProgress,
4810
+ label,
4811
+ value = 0,
4812
+ minValue = 0,
4813
+ maxValue = 100,
4814
+ ...restProps
4815
+ }, forwardedRef) => {
4816
+ const internalRef = useRef(null);
4817
+ const ref = forwardedRef ?? internalRef;
4818
+ const { progressBarProps, labelProps } = useProgressBar({
4819
+ label,
4820
+ value,
4821
+ minValue,
4822
+ maxValue,
4823
+ isIndeterminate: indeterminate,
4824
+ ...restProps
4825
+ });
4826
+ const percentage = indeterminate ? 0 : (value - minValue) / (maxValue - minValue) * 100;
4827
+ return /* @__PURE__ */ jsxs("div", { ...progressBarProps, ref, className, children: [
4828
+ label && /* @__PURE__ */ jsx("span", { ...labelProps, children: label }),
4829
+ renderProgress?.({
4830
+ percentage,
4831
+ isIndeterminate: indeterminate,
4832
+ type,
4833
+ size
4834
+ }),
4835
+ children
4836
+ ] });
4837
+ }
4838
+ );
4839
+ ProgressHeadless.displayName = "ProgressHeadless";
4840
+ var MenuContext = createContext(null);
4841
+ function useMenuContext() {
4842
+ return useContext(MenuContext);
4843
+ }
4844
+ function TriggerBridge({ children }) {
4845
+ const ctx = useSlottedContext(ButtonContext);
4846
+ const localRef = useRef(null);
4847
+ const { ref: contextRef, ...ctxProps } = ctx ?? {};
4848
+ const mergedCallbackRef = useCallback(
4849
+ (node) => {
4850
+ localRef.current = node;
4851
+ if (!contextRef) return;
4852
+ if (typeof contextRef === "function") {
4853
+ contextRef(node);
4854
+ } else {
4855
+ contextRef.current = node;
4856
+ }
4857
+ },
4858
+ [contextRef]
4859
+ );
4860
+ const { buttonProps } = useButton({ ...ctxProps, elementType: "button" }, localRef);
4861
+ if (!isValidElement(children)) return /* @__PURE__ */ jsx(Fragment, { children });
4862
+ return cloneElement(
4863
+ children,
4864
+ { ...buttonProps, ref: mergedCallbackRef }
4865
+ );
4866
+ }
4867
+ function HeadlessMenuTrigger({
4868
+ children,
4869
+ placement = "bottom start",
4870
+ shouldFlip = true,
4871
+ ...rest
4872
+ }) {
4873
+ const childrenArray = Array.isArray(children) ? children : [children];
4874
+ const [triggerChild, menuChild] = childrenArray;
4875
+ return /* @__PURE__ */ jsxs(MenuTrigger$1, { ...rest, children: [
4876
+ /* @__PURE__ */ jsx(TriggerBridge, { children: triggerChild }),
4877
+ /* @__PURE__ */ jsx(Popover, { placement, shouldFlip, offset: 4, children: menuChild })
4878
+ ] });
4879
+ }
4880
+ function HeadlessMenu({
4881
+ className,
4882
+ children,
4883
+ "aria-label": ariaLabel,
4884
+ ...props
4885
+ }) {
4886
+ const menuRef = useRef(null);
4887
+ useLayoutEffect(() => {
4888
+ if (ariaLabel && menuRef.current) {
4889
+ menuRef.current.removeAttribute("aria-labelledby");
4890
+ }
4891
+ });
4892
+ return /* @__PURE__ */ jsx(
4893
+ Menu$1,
4894
+ {
4895
+ ...props,
4896
+ ref: menuRef,
4897
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
4898
+ className: className ?? "",
4899
+ children
4900
+ }
4901
+ );
4902
+ }
4903
+ HeadlessMenuTrigger.Menu = HeadlessMenu;
4904
+ var HeadlessMenuItem = forwardRef(
4905
+ function HeadlessMenuItem2({ children, className, ...props }, ref) {
4906
+ return /* @__PURE__ */ jsx(MenuItem$1, { ...props, ref, className: className ?? "", children });
4907
+ }
4908
+ );
4909
+ function HeadlessMenuSection({
4910
+ children,
4911
+ "aria-label": ariaLabel,
4912
+ className
4913
+ }) {
4914
+ return /* @__PURE__ */ jsx(
4915
+ MenuSection$1,
4916
+ {
4917
+ ...ariaLabel !== void 0 ? { "aria-label": ariaLabel } : {},
4918
+ className: className ?? "",
4919
+ children
4920
+ }
4921
+ );
4922
+ }
4923
+ function HeadlessMenuDivider({
4924
+ className,
4925
+ ...props
4926
+ }) {
4927
+ return /* @__PURE__ */ jsx(Separator, { ...props, className: className ?? "" });
4928
+ }
4929
+ var menuContainerVariants = cva(
4930
+ [
4931
+ // Elevation
4932
+ "shadow-elevation-2",
4933
+ // Width constraints per MD3 spec (112dp min / 280dp max)
4934
+ "min-w-28 max-w-70",
4935
+ // Layout
4936
+ "py-2",
4937
+ // Scroll: show scrollbar when content overflows; max height avoids clipping
4938
+ "overflow-y-auto",
4939
+ "max-h-[calc(var(--visual-viewport-height,100vh)-2rem)]",
4940
+ // Stacking
4941
+ "z-50",
4942
+ // Focus outline handled by React Aria
4943
+ "outline-none",
4944
+ // GPU compositing — promotes menu to its own compositor layer so
4945
+ // scale + opacity animations run without triggering layout reflow.
4946
+ "will-change-[transform,opacity]",
4947
+ // Pointer events blocked during animation to prevent accidental clicks
4948
+ // on menu items while the panel is still animating in or out.
4949
+ "data-[entering]:pointer-events-none data-[exiting]:pointer-events-none",
4950
+ // ── Enter animation ────────────────────────────────────────────────────
4951
+ // @keyframes menu-enter (defined in styles.css): scale(0.8)+opacity:0 →
4952
+ // scale(1)+opacity:1 in 120ms with cubic-bezier(0,0,0.2,1) (standard
4953
+ // decelerate — matches Angular Material's _mat-menu-enter keyframe).
4954
+ "data-[entering]:animate-[menu-enter_120ms_cubic-bezier(0,0,0.2,1)_both]",
4955
+ // ── Exit animation ─────────────────────────────────────────────────────
4956
+ // @keyframes menu-exit (defined in styles.css): opacity:1 → opacity:0
4957
+ // in 100ms after 25ms delay, linear — matches Angular Material's
4958
+ // _mat-menu-exit keyframe (fade-only, no reverse scale).
4959
+ "data-[exiting]:animate-[menu-exit_100ms_25ms_linear_both]",
4960
+ // ── Transform origin (placement-aware) ────────────────────────────────
4961
+ // RAC sets data-placement="bottom|top|left|right" on the Popover element.
4962
+ // Default (bottom): origin at top edge (menu expands downward).
4963
+ "origin-top",
4964
+ // top: origin at bottom edge (menu expands upward)
4965
+ "data-[placement=top]:origin-bottom",
4966
+ // left: origin at right edge
4967
+ "data-[placement=left]:origin-right",
4968
+ // right: origin at left edge
4969
+ "data-[placement=right]:origin-left",
4970
+ // ── Reduced motion ────────────────────────────────────────────────────
4971
+ // Skip both animations entirely for users who prefer reduced motion.
4972
+ "motion-reduce:data-[entering]:animate-none motion-reduce:data-[exiting]:animate-none"
4973
+ ],
4974
+ {
4975
+ variants: {
4976
+ /**
4977
+ * Color scheme — drives the container background.
4978
+ * baseline+standard uses a separate compound variant.
4979
+ */
4980
+ colorScheme: {
4981
+ standard: [],
4982
+ vibrant: []
4983
+ },
4984
+ /**
4985
+ * Visual style — drives corner radius and baseline vs vertical background.
4986
+ */
4987
+ menuStyle: {
4988
+ baseline: ["rounded-xs", "bg-surface-container"],
4989
+ vertical: ["rounded-lg", "bg-surface-container-low"]
4990
+ }
4991
+ },
4992
+ compoundVariants: [
4993
+ // Vertical + vibrant: tertiary container background
4994
+ {
4995
+ menuStyle: "vertical",
4996
+ colorScheme: "vibrant",
4997
+ class: ["bg-tertiary-container"]
4998
+ }
4999
+ ],
5000
+ defaultVariants: {
5001
+ colorScheme: "standard",
5002
+ menuStyle: "baseline"
5003
+ }
5004
+ }
5005
+ );
5006
+ var menuItemVariants = cva(
5007
+ [
5008
+ // Layout — height set by density context in MenuItem component
5009
+ "relative flex w-full items-center",
5010
+ "px-3 gap-3",
5011
+ // Typography: Body Large per MD3 baseline spec
5012
+ "text-body-large",
5013
+ // Interaction
5014
+ "cursor-pointer select-none outline-none",
5015
+ // State layer pseudo-element
5016
+ "before:absolute before:inset-0 before:rounded-[inherit]",
5017
+ "before:transition-opacity before:duration-short2 before:ease-standard",
5018
+ "before:opacity-0",
5019
+ // Hover state layer
5020
+ "hover:before:opacity-8",
5021
+ // Focus visible state layer
5022
+ "focus-visible:before:opacity-12",
5023
+ // Active pressed state layer
5024
+ "active:before:opacity-12",
5025
+ // Color transition for selection
5026
+ "transition-colors duration-short2 ease-standard"
5027
+ ],
5028
+ {
5029
+ variants: {
5030
+ /**
5031
+ * Disabled state: reduces opacity and blocks interaction.
5032
+ */
5033
+ isDisabled: {
5034
+ true: ["opacity-38 cursor-not-allowed pointer-events-none"],
5035
+ false: []
5036
+ },
5037
+ /**
5038
+ * Selected state: background and text color driven by compound variants.
5039
+ */
5040
+ isSelected: {
5041
+ true: [],
5042
+ false: []
5043
+ },
5044
+ /**
5045
+ * Color scheme: drives default text and state layer colors.
5046
+ * - standard: on-surface text, on-surface state layer
5047
+ * - vibrant (vertical only): on-tertiary-container text + state layer
5048
+ */
5049
+ colorScheme: {
5050
+ standard: ["text-on-surface", "before:bg-on-surface"],
5051
+ vibrant: ["text-on-tertiary-container", "before:bg-on-tertiary-container"]
5052
+ },
5053
+ /**
5054
+ * Visual style: drives corner radius on items (vertical uses rounded-lg
5055
+ * inherited from container, items stay flat inside).
5056
+ */
5057
+ menuStyle: {
5058
+ baseline: [],
5059
+ vertical: []
5060
+ }
5061
+ },
5062
+ compoundVariants: [
5063
+ // ── Baseline selection (both colorSchemes) ──────────────────────────
5064
+ {
5065
+ isSelected: true,
5066
+ menuStyle: "baseline",
5067
+ class: [
5068
+ "bg-surface-container-highest"
5069
+ // text-on-surface already applied by standard colorScheme variant
5070
+ ]
5071
+ },
5072
+ // ── Vertical + Standard selection ───────────────────────────────────
5073
+ {
5074
+ isSelected: true,
5075
+ menuStyle: "vertical",
5076
+ colorScheme: "standard",
5077
+ class: [
5078
+ "bg-tertiary-container",
5079
+ "text-on-tertiary-container",
5080
+ "before:bg-on-tertiary-container"
5081
+ ]
5082
+ },
5083
+ // ── Vertical + Vibrant selection ─────────────────────────────────────
5084
+ {
5085
+ isSelected: true,
5086
+ menuStyle: "vertical",
5087
+ colorScheme: "vibrant",
5088
+ class: ["bg-tertiary", "text-on-tertiary", "before:bg-on-tertiary"]
5089
+ }
5090
+ ],
5091
+ defaultVariants: {
5092
+ isDisabled: false,
5093
+ isSelected: false,
5094
+ colorScheme: "standard",
5095
+ menuStyle: "baseline"
5096
+ }
5097
+ }
5098
+ );
5099
+ var menuSectionVariants = cva(["flex flex-col w-full"]);
5100
+ var menuSectionHeaderVariants = cva([
5101
+ "px-3 pt-2 pb-1",
5102
+ "text-title-small text-on-surface-variant",
5103
+ "select-none"
5104
+ ]);
5105
+ var menuDividerVariants = cva(["border-t border-outline-variant", "my-2 mx-0"]);
5106
+ cva(["h-2 w-full"]);
5107
+ var menuItemTrailingTextVariants = cva([
5108
+ "ml-auto shrink-0 text-label-large text-on-surface-variant",
5109
+ "select-none"
5110
+ ]);
5111
+ var menuItemDescriptionVariants = cva([
5112
+ "text-body-medium text-on-surface-variant",
5113
+ "select-none"
5114
+ ]);
5115
+ var Menu = forwardRef(function Menu2({
5116
+ children,
5117
+ className,
5118
+ colorScheme = "standard",
5119
+ menuStyle = "baseline",
5120
+ density = 0,
5121
+ disableRipple = false,
5122
+ selectionMode,
5123
+ selectedKeys,
5124
+ onSelectionChange,
5125
+ ...props
5126
+ }, _ref) {
5127
+ const close = () => {
5128
+ };
5129
+ const contextValue = {
5130
+ close,
5131
+ disableRipple,
5132
+ colorScheme,
5133
+ menuStyle,
5134
+ density,
5135
+ ...selectionMode !== void 0 ? { selectionMode } : {},
5136
+ ...selectedKeys !== void 0 ? { selectedKeys } : {}
5137
+ };
5138
+ return /* @__PURE__ */ jsx(MenuContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
5139
+ HeadlessMenu,
5140
+ {
5141
+ ...props,
5142
+ ...selectionMode !== void 0 ? { selectionMode } : {},
5143
+ ...selectedKeys !== void 0 ? { selectedKeys } : {},
5144
+ ...onSelectionChange !== void 0 ? { onSelectionChange } : {},
5145
+ className: cn(menuContainerVariants({ colorScheme, menuStyle }), className),
5146
+ children
5147
+ }
5148
+ ) });
5149
+ });
5150
+ function MenuTrigger({
5151
+ children,
5152
+ placement = "bottom start",
5153
+ shouldFlip = true,
5154
+ ...rest
5155
+ }) {
5156
+ return /* @__PURE__ */ jsx(HeadlessMenuTrigger, { placement, shouldFlip, ...rest, children });
5157
+ }
5158
+ MenuTrigger.Menu = Menu;
5159
+ function CheckIcon() {
5160
+ return /* @__PURE__ */ jsx(
5161
+ "svg",
5162
+ {
5163
+ "data-testid": "check-icon",
5164
+ xmlns: "http://www.w3.org/2000/svg",
5165
+ viewBox: "0 0 24 24",
5166
+ fill: "currentColor",
5167
+ "aria-hidden": "true",
5168
+ focusable: "false",
5169
+ className: "h-full w-full",
5170
+ children: /* @__PURE__ */ jsx("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" })
5171
+ }
5172
+ );
5173
+ }
5174
+ var DENSITY_HEIGHT = {
5175
+ 0: "h-12",
5176
+ [-1]: "h-11",
5177
+ [-2]: "h-10",
5178
+ [-3]: "h-9"
5179
+ };
5180
+ var MenuItem = forwardRef(function MenuItem2({
5181
+ children,
5182
+ leadingIcon,
5183
+ trailingIcon,
5184
+ trailingText,
5185
+ description,
5186
+ badge,
5187
+ className,
5188
+ disableRipple: itemDisableRipple,
5189
+ ...props
5190
+ }, ref) {
5191
+ const ctx = useMenuContext();
5192
+ const disableRipple = itemDisableRipple ?? ctx?.disableRipple ?? false;
5193
+ const colorScheme = ctx?.colorScheme ?? "standard";
5194
+ const menuStyle = ctx?.menuStyle ?? "baseline";
5195
+ const density = ctx?.density ?? 0;
5196
+ const selectionMode = ctx?.selectionMode;
5197
+ const heightClass = DENSITY_HEIGHT[density];
5198
+ const isSelectionMenu = selectionMode != null;
5199
+ const { ripples, onMouseDown } = useRipple({ disabled: disableRipple });
5200
+ const computeClassName = ({ isDisabled, isSelected }) => cn(
5201
+ menuItemVariants({
5202
+ isDisabled,
5203
+ isSelected: isSelected ?? false,
5204
+ colorScheme,
5205
+ menuStyle
5206
+ }),
5207
+ // Height: auto when description is present (multi-line), otherwise density
5208
+ description ? "min-h-12 py-2 h-auto items-start" : heightClass,
5209
+ className
5210
+ );
5211
+ return /* @__PURE__ */ jsx(
5212
+ HeadlessMenuItem,
5213
+ {
5214
+ ...props,
5215
+ ref,
5216
+ className: computeClassName,
5217
+ onMouseDown,
5218
+ children: ({ isSelected }) => /* @__PURE__ */ jsxs(Fragment, { children: [
5219
+ !disableRipple && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute inset-0 z-0 overflow-hidden rounded-[inherit]", children: ripples }),
5220
+ (leadingIcon != null || isSelectionMenu) && /* @__PURE__ */ jsx(
5221
+ "span",
5222
+ {
5223
+ className: "text-on-surface-variant relative z-10 flex h-6 w-6 shrink-0 items-center justify-center",
5224
+ "aria-hidden": "true",
5225
+ children: isSelectionMenu && leadingIcon == null ? isSelected ? /* @__PURE__ */ jsx(CheckIcon, {}) : null : leadingIcon
5226
+ }
5227
+ ),
5228
+ description != null ? /* @__PURE__ */ jsxs("span", { className: "relative z-10 flex min-w-0 flex-1 flex-col", children: [
5229
+ /* @__PURE__ */ jsx("span", { className: "text-body-large", children }),
5230
+ /* @__PURE__ */ jsx("span", { className: menuItemDescriptionVariants(), children: description })
5231
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-body-large relative z-10 min-w-0 flex-1", children }),
5232
+ badge != null && /* @__PURE__ */ jsx("span", { className: "relative z-10 shrink-0", children: badge }),
5233
+ trailingIcon != null && trailingText == null && /* @__PURE__ */ jsx(
5234
+ "span",
5235
+ {
5236
+ className: "text-on-surface-variant relative z-10 ml-auto flex h-6 w-6 shrink-0 items-center justify-center",
5237
+ "aria-hidden": "true",
5238
+ children: trailingIcon
5239
+ }
5240
+ ),
5241
+ trailingText != null && trailingIcon == null && /* @__PURE__ */ jsx(
5242
+ "span",
5243
+ {
5244
+ className: cn(menuItemTrailingTextVariants(), "relative z-10"),
5245
+ "aria-keyshortcuts": trailingText,
5246
+ children: trailingText
5247
+ }
5248
+ )
5249
+ ] })
5250
+ }
5251
+ );
5252
+ });
5253
+ function MenuSection({
5254
+ children,
5255
+ header,
5256
+ showDivider = false,
5257
+ className,
5258
+ "aria-label": ariaLabel
5259
+ }) {
5260
+ const sectionAriaLabel = ariaLabel ?? header;
5261
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
5262
+ showDivider && /* @__PURE__ */ jsx(HeadlessMenuDivider, { className: menuDividerVariants() }),
5263
+ /* @__PURE__ */ jsxs(
5264
+ HeadlessMenuSection,
5265
+ {
5266
+ "aria-label": sectionAriaLabel,
5267
+ className: cn(menuSectionVariants(), className),
5268
+ children: [
5269
+ header && /* @__PURE__ */ jsx(Header, { className: menuSectionHeaderVariants(), "aria-hidden": "true", children: header }),
5270
+ children
5271
+ ]
5272
+ }
5273
+ )
5274
+ ] });
5275
+ }
5276
+ function MenuDivider({ className }) {
5277
+ return /* @__PURE__ */ jsx(HeadlessMenuDivider, { className: cn(menuDividerVariants(), className) });
5278
+ }
5279
+ var SnackbarHeadless = forwardRef(
5280
+ function SnackbarHeadless2({
5281
+ message,
5282
+ supportingText,
5283
+ action,
5284
+ showClose,
5285
+ duration = 4e3,
5286
+ severity = "default",
5287
+ position = "bottom-center",
5288
+ onClose,
5289
+ children,
5290
+ className,
5291
+ getAnimationClassName
5292
+ }, ref) {
5293
+ const [animationState, setAnimationState] = useState("entering");
5294
+ const remainingRef = useRef(duration);
5295
+ const startedAtRef = useRef(Date.now());
5296
+ const dismissTimerRef = useRef(null);
5297
+ const exitFallbackRef = useRef(null);
5298
+ const pausedRef = useRef(false);
5299
+ const closedRef = useRef(false);
5300
+ const clearDismissTimer = useCallback(() => {
5301
+ if (dismissTimerRef.current !== null) {
5302
+ clearTimeout(dismissTimerRef.current);
5303
+ dismissTimerRef.current = null;
5304
+ }
5305
+ }, []);
5306
+ const triggerExit = useCallback(() => {
5307
+ if (closedRef.current) return;
5308
+ clearDismissTimer();
5309
+ setAnimationState("exiting");
5310
+ exitFallbackRef.current = setTimeout(() => {
5311
+ if (!closedRef.current) {
5312
+ closedRef.current = true;
5313
+ setAnimationState("exited");
5314
+ onClose?.();
5315
+ }
5316
+ }, 250);
5317
+ }, [clearDismissTimer, onClose]);
5318
+ const startDismissTimer = useCallback(
5319
+ (ms) => {
5320
+ if (ms <= 0) return;
5321
+ clearDismissTimer();
5322
+ startedAtRef.current = Date.now();
5323
+ dismissTimerRef.current = setTimeout(triggerExit, ms);
5324
+ },
5325
+ [clearDismissTimer, triggerExit]
5326
+ );
5327
+ useEffect(() => {
5328
+ let frameId;
5329
+ frameId = requestAnimationFrame(() => {
5330
+ frameId = requestAnimationFrame(() => {
5331
+ setAnimationState("visible");
5332
+ });
5333
+ });
5334
+ return () => cancelAnimationFrame(frameId);
5335
+ }, []);
5336
+ useEffect(() => {
5337
+ if (animationState !== "visible") return;
5338
+ if (duration <= 0) return;
5339
+ remainingRef.current = duration;
5340
+ startDismissTimer(duration);
5341
+ return clearDismissTimer;
5342
+ }, [animationState, duration, startDismissTimer, clearDismissTimer]);
5343
+ useEffect(
5344
+ () => () => {
5345
+ clearDismissTimer();
5346
+ if (exitFallbackRef.current !== null) {
5347
+ clearTimeout(exitFallbackRef.current);
5348
+ exitFallbackRef.current = null;
5349
+ }
5350
+ },
5351
+ [clearDismissTimer]
5352
+ );
5353
+ const handleTransitionEnd = useCallback(() => {
5354
+ if (animationState === "exiting" && !closedRef.current) {
5355
+ if (exitFallbackRef.current !== null) {
5356
+ clearTimeout(exitFallbackRef.current);
5357
+ exitFallbackRef.current = null;
5358
+ }
5359
+ closedRef.current = true;
5360
+ setAnimationState("exited");
5361
+ onClose?.();
5362
+ }
5363
+ }, [animationState, onClose]);
5364
+ const handleMouseEnter = useCallback(() => {
5365
+ if (pausedRef.current || animationState !== "visible") return;
5366
+ pausedRef.current = true;
5367
+ const elapsed = Date.now() - startedAtRef.current;
5368
+ remainingRef.current = Math.max(remainingRef.current - elapsed, 0);
5369
+ clearDismissTimer();
5370
+ }, [animationState, clearDismissTimer]);
5371
+ const handleMouseLeave = useCallback(() => {
5372
+ if (!pausedRef.current || animationState !== "visible") return;
5373
+ pausedRef.current = false;
5374
+ if (duration > 0) startDismissTimer(remainingRef.current);
5375
+ }, [animationState, duration, startDismissTimer]);
5376
+ const handleFocusIn = useCallback(() => {
5377
+ if (pausedRef.current || animationState !== "visible") return;
5378
+ pausedRef.current = true;
5379
+ const elapsed = Date.now() - startedAtRef.current;
5380
+ remainingRef.current = Math.max(remainingRef.current - elapsed, 0);
5381
+ clearDismissTimer();
5382
+ }, [animationState, clearDismissTimer]);
5383
+ const handleFocusOut = useCallback(() => {
5384
+ if (!pausedRef.current || animationState !== "visible") return;
5385
+ pausedRef.current = false;
5386
+ if (duration > 0) startDismissTimer(remainingRef.current);
5387
+ }, [animationState, duration, startDismissTimer]);
5388
+ const handleClose = useCallback(() => {
5389
+ triggerExit();
5390
+ }, [triggerExit]);
5391
+ const ariaProps = severity === "error" ? { role: "alert", "aria-live": "assertive", "aria-atomic": "true" } : { role: "status", "aria-live": "polite", "aria-atomic": "true" };
5392
+ const computedClassName = cn(className, getAnimationClassName?.(animationState, position));
5393
+ const childContent = typeof children === "function" ? children({ animationState, onClose: handleClose }) : children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
5394
+ /* @__PURE__ */ jsx("span", { children: message }),
5395
+ supportingText && /* @__PURE__ */ jsx("span", { children: supportingText }),
5396
+ action && /* @__PURE__ */ jsx("button", { type: "button", onClick: action.onAction, children: action.label }),
5397
+ showClose && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Close", onClick: handleClose, children: "\u2715" })
5398
+ ] });
5399
+ return (
5400
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
5401
+ /* @__PURE__ */ jsx(
5402
+ "div",
5403
+ {
5404
+ ref,
5405
+ className: computedClassName,
5406
+ ...ariaProps,
5407
+ onMouseEnter: handleMouseEnter,
5408
+ onMouseLeave: handleMouseLeave,
5409
+ onFocus: handleFocusIn,
5410
+ onBlur: handleFocusOut,
5411
+ onTransitionEnd: handleTransitionEnd,
5412
+ "data-animation-state": animationState,
5413
+ children: childContent
5414
+ }
5415
+ )
5416
+ );
5417
+ }
5418
+ );
5419
+ SnackbarHeadless.displayName = "SnackbarHeadless";
5420
+ var snackbarStackContainerVariants = cva(
5421
+ ["fixed", "z-50", "flex", "gap-2", "pointer-events-none"],
5422
+ {
5423
+ variants: {
5424
+ position: {
5425
+ "bottom-center": [
5426
+ "bottom-4",
5427
+ "left-1/2",
5428
+ "-translate-x-1/2",
5429
+ "flex-col-reverse",
5430
+ "items-center"
5431
+ ],
5432
+ "bottom-left": ["bottom-4", "left-4", "flex-col-reverse", "items-start"],
5433
+ "bottom-right": ["bottom-4", "right-4", "flex-col-reverse", "items-end"],
5434
+ "top-center": ["top-4", "left-1/2", "-translate-x-1/2", "flex-col", "items-center"],
5435
+ "top-left": ["top-4", "left-4", "flex-col", "items-start"],
5436
+ "top-right": ["top-4", "right-4", "flex-col", "items-end"]
5437
+ }
5438
+ },
5439
+ defaultVariants: { position: "bottom-center" }
5440
+ }
5441
+ );
5442
+ var snackbarBaseVariants = cva(
5443
+ [
5444
+ // Sizing (MD3 spec: 288dp min, 568dp max)
5445
+ "min-w-72",
5446
+ "max-w-snackbar-max",
5447
+ "w-max",
5448
+ "min-h-12",
5449
+ // Restore pointer events so hover/focus timer pause works
5450
+ "pointer-events-auto",
5451
+ // Surface
5452
+ "bg-inverse-surface",
5453
+ // Shape: MD3 extra-small corner = 4dp
5454
+ "rounded-xs",
5455
+ // Elevation level 3
5456
+ "shadow-elevation-3",
5457
+ // Layout
5458
+ "flex",
5459
+ "items-center",
5460
+ "gap-x-1",
5461
+ "pl-4 pr-2",
5462
+ // Typography
5463
+ "text-body-medium",
5464
+ "text-inverse-on-surface",
5465
+ // Transition (properties used by both entry and exit)
5466
+ "transition-[opacity,transform]",
5467
+ "will-change-[opacity,transform]"
5468
+ ],
5469
+ {
5470
+ variants: {
5471
+ /**
5472
+ * Whether the Snackbar has supporting text (two-line layout).
5473
+ * Adjusts vertical padding to MD3 spec for two-line configuration.
5474
+ */
5475
+ twoLine: {
5476
+ true: "py-1",
5477
+ false: "py-1"
5478
+ }
5479
+ },
5480
+ defaultVariants: {
5481
+ twoLine: false
5482
+ }
5483
+ }
5484
+ );
5485
+ cva("", {
5486
+ variants: {
5487
+ position: {
5488
+ "bottom-center": ["bottom-4", "left-1/2", "-translate-x-1/2"],
5489
+ "bottom-left": ["bottom-4", "left-4"],
5490
+ "bottom-right": ["bottom-4", "right-4"],
5491
+ "top-center": ["top-4", "left-1/2", "-translate-x-1/2"],
5492
+ "top-left": ["top-4", "left-4"],
5493
+ "top-right": ["top-4", "right-4"]
5494
+ }
5495
+ },
5496
+ defaultVariants: {
5497
+ position: "bottom-center"
5498
+ }
5499
+ });
5500
+ var snackbarAnimationVariants = cva("", {
5501
+ variants: {
5502
+ animationState: {
5503
+ entering: ["opacity-0", "scale-75"],
5504
+ visible: ["scale-100", "opacity-100", "duration-medium1", "ease-emphasized-decelerate"],
5505
+ exiting: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"],
5506
+ exited: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"]
5507
+ },
5508
+ enterDirection: {
5509
+ up: ["origin-bottom"],
5510
+ down: ["origin-top"]
5511
+ }
5512
+ },
5513
+ defaultVariants: {
5514
+ animationState: "entering",
5515
+ enterDirection: "up"
5516
+ }
5517
+ });
5518
+ cva([...snackbarBaseVariants()], {
5519
+ variants: {
5520
+ animationState: {
5521
+ entering: ["opacity-0", "scale-75"],
5522
+ visible: ["scale-100", "opacity-100", "duration-medium1", "ease-emphasized-decelerate"],
5523
+ exiting: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"],
5524
+ exited: ["scale-75", "opacity-0", "duration-short4", "ease-standard-accelerate"]
5525
+ },
5526
+ enterDirection: {
5527
+ up: ["origin-bottom"],
5528
+ down: ["origin-top"]
5529
+ },
5530
+ position: {
5531
+ "bottom-center": ["bottom-4", "left-1/2", "-translate-x-1/2"],
5532
+ "bottom-left": ["bottom-4", "left-4"],
5533
+ "bottom-right": ["bottom-4", "right-4"],
5534
+ "top-center": ["top-4", "left-1/2", "-translate-x-1/2"],
5535
+ "top-left": ["top-4", "left-4"],
5536
+ "top-right": ["top-4", "right-4"]
5537
+ },
5538
+ twoLine: {
5539
+ true: "py-1",
5540
+ false: "py-1"
5541
+ }
5542
+ },
5543
+ defaultVariants: {
5544
+ animationState: "entering",
5545
+ enterDirection: "up",
5546
+ position: "bottom-center",
5547
+ twoLine: false
5548
+ }
5549
+ });
5550
+ var snackbarMessageVariants = cva([
5551
+ "flex-1",
5552
+ "text-body-medium",
5553
+ "text-inverse-on-surface"
5554
+ ]);
5555
+ var snackbarSupportingTextVariants = cva([
5556
+ "text-body-medium",
5557
+ "text-inverse-on-surface",
5558
+ "opacity-80"
5559
+ ]);
5560
+ var snackbarActionVariants = cva(["shrink-0", "text-inverse-primary"]);
5561
+ var snackbarCloseVariants = cva(["shrink-0", "text-inverse-on-surface"]);
5562
+ var snackbarContentVariants = cva(["flex", "flex-col", "flex-1", "min-w-0 py-2 pr-2"]);
5563
+ cva(["scale-75", "opacity-0"]);
5564
+ function getEnterDirection(position) {
5565
+ return position.startsWith("top") ? "down" : "up";
5566
+ }
5567
+ function CloseIcon() {
5568
+ return /* @__PURE__ */ jsx(
5569
+ "svg",
5570
+ {
5571
+ xmlns: "http://www.w3.org/2000/svg",
5572
+ width: "24",
5573
+ height: "24",
5574
+ viewBox: "0 0 24 24",
5575
+ fill: "currentColor",
5576
+ "aria-hidden": "true",
5577
+ children: /* @__PURE__ */ 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" })
5578
+ }
5579
+ );
5580
+ }
5581
+ var Snackbar = forwardRef(function Snackbar2({
5582
+ message,
5583
+ supportingText,
5584
+ action,
5585
+ showClose = false,
5586
+ duration = 4e3,
5587
+ severity = "default",
5588
+ position = "bottom-center",
5589
+ onClose,
5590
+ className
5591
+ }, ref) {
5592
+ const isTwoLine = Boolean(supportingText);
5593
+ const baseClassName = cn(snackbarBaseVariants({ twoLine: isTwoLine }), className);
5594
+ return /* @__PURE__ */ jsx(
5595
+ SnackbarHeadless,
5596
+ {
5597
+ ref,
5598
+ message,
5599
+ ...supportingText !== void 0 && { supportingText },
5600
+ ...action !== void 0 && { action },
5601
+ showClose,
5602
+ duration,
5603
+ severity,
5604
+ position,
5605
+ ...onClose !== void 0 && { onClose },
5606
+ className: baseClassName,
5607
+ getAnimationClassName: (state, pos) => snackbarAnimationVariants({ animationState: state, enterDirection: getEnterDirection(pos) }),
5608
+ children: ({ onClose: triggerClose }) => /* @__PURE__ */ jsxs(Fragment, { children: [
5609
+ /* @__PURE__ */ jsxs("div", { className: snackbarContentVariants(), children: [
5610
+ /* @__PURE__ */ jsx("span", { className: snackbarMessageVariants(), children: message }),
5611
+ supportingText && /* @__PURE__ */ jsx("span", { className: snackbarSupportingTextVariants(), children: supportingText })
5612
+ ] }),
5613
+ action && /* @__PURE__ */ jsx("span", { className: snackbarActionVariants(), children: /* @__PURE__ */ jsx(
5614
+ Button,
5615
+ {
5616
+ variant: "text",
5617
+ onPress: action.onAction,
5618
+ className: "text-inverse-primary hover:text-inverse-primary",
5619
+ children: action.label
5620
+ }
5621
+ ) }),
5622
+ showClose && /* @__PURE__ */ jsx("span", { className: snackbarCloseVariants(), children: /* @__PURE__ */ jsx(
5623
+ IconButton,
5624
+ {
5625
+ variant: "standard",
5626
+ "aria-label": "Close",
5627
+ onPress: triggerClose,
5628
+ className: "text-inverse-on-surface hover:text-inverse-on-surface",
5629
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
5630
+ }
5631
+ ) })
5632
+ ] })
5633
+ }
5634
+ );
5635
+ });
5636
+ Snackbar.displayName = "Snackbar";
5637
+ var SnackbarContext = createContext(null);
5638
+ function useSnackbar() {
5639
+ const ctx = useContext(SnackbarContext);
5640
+ if (!ctx) {
5641
+ throw new Error(
5642
+ "[Snackbar] useSnackbar must be used inside <SnackbarProvider>. Wrap your application (or Storybook decorator) with <SnackbarProvider>."
5643
+ );
5644
+ }
5645
+ return ctx;
5646
+ }
5647
+ function SnackbarProvider({ children, maxVisible = 5 }) {
5648
+ const [queue, setQueue] = useState([]);
5649
+ const counterRef = useRef(0);
5650
+ const baseId = useId();
5651
+ const showSnackbar = useCallback(
5652
+ (options) => {
5653
+ const id = `${baseId}-snackbar-${++counterRef.current}`;
5654
+ setQueue((prev) => [...prev, { ...options, id }]);
5655
+ return id;
5656
+ },
5657
+ [baseId]
5658
+ );
5659
+ const closeSnackbar = useCallback(() => {
5660
+ setQueue((prev) => {
5661
+ if (prev.length === 0) return prev;
5662
+ return prev.slice(1);
5663
+ });
5664
+ }, []);
5665
+ const removeById = useCallback((id) => {
5666
+ setQueue((prev) => prev.filter((item) => item.id !== id));
5667
+ }, []);
5668
+ const contextValue = { showSnackbar, closeSnackbar };
5669
+ const positionGroups = useMemo(() => {
5670
+ const groups = /* @__PURE__ */ new Map();
5671
+ const countByPosition = /* @__PURE__ */ new Map();
5672
+ for (const item of queue) {
5673
+ const pos = item.position ?? "bottom-center";
5674
+ const count = countByPosition.get(pos) ?? 0;
5675
+ if (count < maxVisible) {
5676
+ const existing = groups.get(pos) ?? [];
5677
+ groups.set(pos, [...existing, item]);
5678
+ countByPosition.set(pos, count + 1);
5679
+ }
5680
+ }
5681
+ return groups;
5682
+ }, [queue, maxVisible]);
5683
+ return /* @__PURE__ */ jsxs(SnackbarContext.Provider, { value: contextValue, children: [
5684
+ children,
5685
+ typeof document !== "undefined" && createPortal(
5686
+ /* @__PURE__ */ jsx(Fragment, { children: Array.from(positionGroups.entries()).map(([position, items]) => /* @__PURE__ */ jsx("div", { className: snackbarStackContainerVariants({ position }), children: items.map((item) => /* @__PURE__ */ jsx(
5687
+ Snackbar,
5688
+ {
5689
+ message: item.message,
5690
+ ...item.supportingText !== void 0 && {
5691
+ supportingText: item.supportingText
5692
+ },
5693
+ ...item.action !== void 0 && { action: item.action },
5694
+ ...item.showClose !== void 0 && { showClose: item.showClose },
5695
+ ...item.duration !== void 0 && { duration: item.duration },
5696
+ ...item.severity !== void 0 && { severity: item.severity },
5697
+ ...item.position !== void 0 && { position: item.position },
5698
+ ...item.className !== void 0 && { className: item.className },
5699
+ onClose: () => {
5700
+ item.onClose?.();
5701
+ removeById(item.id);
5702
+ }
5703
+ },
5704
+ item.id
5705
+ )) }, position)) }),
5706
+ document.body
5707
+ )
5708
+ ] });
5709
+ }
5710
+ var DialogContext = createContext(null);
5711
+ function useDialogContext() {
5712
+ const ctx = useContext(DialogContext);
5713
+ if (ctx === null) {
5714
+ throw new Error(
5715
+ "[Dialog] DialogHeadline, DialogContent, and DialogActions must be rendered inside a <Dialog> or <DialogHeadless> component."
5716
+ );
5717
+ }
5718
+ return ctx;
5719
+ }
5720
+ var DialogPanel = ({
5721
+ ariaLabel,
5722
+ headlineId,
5723
+ contentId,
5724
+ onClose,
5725
+ onTransitionEnd,
5726
+ variant,
5727
+ isDismissable,
5728
+ wrapperClassName,
5729
+ className,
5730
+ animationState,
5731
+ getAnimationClassName,
5732
+ children
5733
+ }) => {
5734
+ const panelRef = useRef(null);
5735
+ usePreventScroll();
5736
+ const { dialogProps } = useDialog(
5737
+ {
5738
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
5739
+ "aria-labelledby": headlineId,
5740
+ "aria-describedby": contentId
5741
+ },
5742
+ panelRef
5743
+ );
5744
+ const { overlayProps } = useOverlay(
5745
+ {
5746
+ isOpen: true,
5747
+ onClose,
5748
+ isDismissable,
5749
+ shouldCloseOnBlur: false
5750
+ },
5751
+ panelRef
5752
+ );
5753
+ const panelClassName = cn(className, getAnimationClassName?.(animationState));
5754
+ return (
5755
+ // Centering/positioning wrapper — structural only, no ARIA role
5756
+ /* @__PURE__ */ jsx("div", { className: wrapperClassName, children: /* @__PURE__ */ jsx(
5757
+ "div",
5758
+ {
5759
+ ...mergeProps(overlayProps, dialogProps),
5760
+ ref: panelRef,
5761
+ "aria-modal": "true",
5762
+ className: panelClassName,
5763
+ "data-animation-state": animationState,
5764
+ "data-variant": variant,
5765
+ onTransitionEnd,
5766
+ children
5767
+ }
5768
+ ) })
5769
+ );
5770
+ };
5771
+ DialogPanel.displayName = "DialogPanel";
5772
+ var DialogHeadless = forwardRef(
5773
+ function DialogHeadless2({
5774
+ variant = "basic",
5775
+ open,
5776
+ defaultOpen = false,
5777
+ onOpenChange,
5778
+ "aria-label": ariaLabel,
5779
+ children,
5780
+ className,
5781
+ scrimClassName,
5782
+ getAnimationClassName
5783
+ }, _ref) {
5784
+ const state = useOverlayTriggerState({
5785
+ ...open !== void 0 ? { isOpen: open } : {},
5786
+ ...defaultOpen !== void 0 ? { defaultOpen } : {},
5787
+ ...onOpenChange !== void 0 ? { onOpenChange } : {}
5788
+ });
5789
+ const isOpen = state.isOpen;
5790
+ const close = useCallback(() => {
5791
+ state.close();
5792
+ }, [state]);
5793
+ const [animationState, setAnimationState] = useState("exited");
5794
+ const closedRef = useRef(false);
5795
+ const exitFallbackRef = useRef(null);
5796
+ useEffect(() => {
5797
+ if (!isOpen) return;
5798
+ closedRef.current = false;
5799
+ setAnimationState("entering");
5800
+ const id = setTimeout(() => {
5801
+ setAnimationState("visible");
5802
+ }, 0);
5803
+ return () => clearTimeout(id);
5804
+ }, [isOpen]);
5805
+ useEffect(() => {
5806
+ if (isOpen) return;
5807
+ if (animationState === "exited" || animationState === "entering") return;
5808
+ if (animationState === "visible") {
5809
+ setAnimationState("exiting");
5810
+ exitFallbackRef.current = setTimeout(() => {
5811
+ if (!closedRef.current) {
5812
+ closedRef.current = true;
5813
+ setAnimationState("exited");
5814
+ }
5815
+ }, 150);
5816
+ }
5817
+ }, [isOpen, animationState]);
5818
+ useEffect(
5819
+ () => () => {
5820
+ if (exitFallbackRef.current !== null) {
5821
+ clearTimeout(exitFallbackRef.current);
5822
+ }
5823
+ },
5824
+ []
5825
+ );
5826
+ const handleTransitionEnd = useCallback(() => {
5827
+ if (animationState === "exiting" && !closedRef.current) {
5828
+ if (exitFallbackRef.current !== null) {
5829
+ clearTimeout(exitFallbackRef.current);
5830
+ exitFallbackRef.current = null;
5831
+ }
5832
+ closedRef.current = true;
5833
+ setAnimationState("exited");
5834
+ }
5835
+ }, [animationState]);
5836
+ const baseId = useId();
5837
+ const headlineId = `${baseId}-dialog-headline`;
5838
+ const contentId = `${baseId}-dialog-content`;
5839
+ const contextValue = {
5840
+ headlineId,
5841
+ contentId,
5842
+ close,
5843
+ variant
5844
+ };
5845
+ const handleScrimClick = useCallback(() => {
5846
+ if (variant === "basic") {
5847
+ close();
5848
+ }
5849
+ }, [variant, close]);
5850
+ if (!isOpen && animationState === "exited") {
5851
+ return null;
5852
+ }
5853
+ const content = /* @__PURE__ */ jsxs(DialogContext.Provider, { value: contextValue, children: [
5854
+ /* @__PURE__ */ jsx(
5855
+ "div",
5856
+ {
5857
+ "data-testid": "dialog-scrim",
5858
+ className: scrimClassName,
5859
+ onClick: handleScrimClick,
5860
+ "aria-hidden": "true"
5861
+ }
5862
+ ),
5863
+ /* @__PURE__ */ jsx(FocusScope, { contain: true, restoreFocus: true, autoFocus: true, children: /* @__PURE__ */ jsx(
5864
+ DialogPanel,
5865
+ {
5866
+ ariaLabel,
5867
+ headlineId,
5868
+ contentId,
5869
+ onClose: close,
5870
+ onTransitionEnd: handleTransitionEnd,
5871
+ variant,
5872
+ isDismissable: variant === "basic",
5873
+ wrapperClassName: variant === "basic" ? "fixed inset-0 z-50 flex items-center justify-center px-4" : "fixed inset-0 z-50",
5874
+ className,
5875
+ animationState,
5876
+ getAnimationClassName,
5877
+ children
5878
+ }
5879
+ ) })
5880
+ ] });
5881
+ if (typeof document === "undefined") return null;
5882
+ return createPortal(content, document.body);
5883
+ }
5884
+ );
5885
+ DialogHeadless.displayName = "DialogHeadless";
5886
+ var dialogScrimVariants = cva([
5887
+ "fixed",
5888
+ "inset-0",
5889
+ "z-40",
5890
+ "bg-scrim",
5891
+ "opacity-32",
5892
+ "transition-opacity",
5893
+ "duration-medium2",
5894
+ "ease-standard"
5895
+ ]);
5896
+ var dialogPanelVariants = cva(
5897
+ [
5898
+ // Stacking above scrim
5899
+ "z-50",
5900
+ // Surface
5901
+ "bg-surface-container-high",
5902
+ // Flex column layout for slots
5903
+ "flex",
5904
+ "flex-col",
5905
+ // Transition for animation state changes
5906
+ "transition-[opacity,transform]",
5907
+ "will-change-[opacity,transform]"
5908
+ ],
5909
+ {
5910
+ variants: {
5911
+ variant: {
5912
+ basic: [
5913
+ // Shape: MD3 extra-large = 28dp
5914
+ "rounded-xl",
5915
+ // Elevation level 3
5916
+ "shadow-elevation-3",
5917
+ // Width constraints per MD3 spec (280dp min, 560dp max)
5918
+ "min-w-70",
5919
+ "max-w-dialog-max",
5920
+ "w-full",
5921
+ // Internal spacing
5922
+ "pt-6",
5923
+ "pb-3",
5924
+ "px-6",
5925
+ // Positioned in viewport center
5926
+ "relative"
5927
+ ],
5928
+ fullscreen: [
5929
+ // Full viewport
5930
+ "w-full",
5931
+ "h-full",
5932
+ // No rounded corners on fullscreen
5933
+ "rounded-none",
5934
+ // No elevation shadow on fullscreen
5935
+ "shadow-none",
5936
+ // Positioned to fill portal
5937
+ "relative"
5938
+ ]
5939
+ }
5940
+ },
5941
+ defaultVariants: {
5942
+ variant: "basic"
5943
+ }
5944
+ }
5945
+ );
5946
+ cva([], {
5947
+ variants: {
5948
+ variant: {
5949
+ basic: ["fixed", "inset-0", "z-50", "flex", "items-center", "justify-center", "px-4"],
5950
+ fullscreen: ["fixed", "inset-0", "z-50"]
5951
+ }
5952
+ },
5953
+ defaultVariants: {
5954
+ variant: "basic"
5955
+ }
5956
+ });
5957
+ var dialogAnimationVariants = cva("", {
5958
+ variants: {
5959
+ animationState: {
5960
+ entering: [],
5961
+ visible: [],
5962
+ exiting: [],
5963
+ exited: []
5964
+ },
5965
+ variant: {
5966
+ basic: [],
5967
+ fullscreen: []
5968
+ }
5969
+ },
5970
+ compoundVariants: [
5971
+ // Basic: entering — start scaled down + transparent
5972
+ {
5973
+ animationState: "entering",
5974
+ variant: "basic",
5975
+ className: ["scale-90", "opacity-0"]
5976
+ },
5977
+ // Basic: visible — scale to full + fade in
5978
+ {
5979
+ animationState: "visible",
5980
+ variant: "basic",
5981
+ className: ["scale-100", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
5982
+ },
5983
+ // Basic: exiting — fade out (scale stays at 1)
5984
+ {
5985
+ animationState: "exiting",
5986
+ variant: "basic",
5987
+ className: ["scale-100", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
5988
+ },
5989
+ // Basic: exited — fully transparent
5990
+ {
5991
+ animationState: "exited",
5992
+ variant: "basic",
5993
+ className: ["scale-100", "opacity-0"]
5994
+ },
5995
+ // Fullscreen: entering — start below viewport + transparent
5996
+ {
5997
+ animationState: "entering",
5998
+ variant: "fullscreen",
5999
+ className: ["translate-y-full", "opacity-0"]
6000
+ },
6001
+ // Fullscreen: visible — slide up + fade in
6002
+ {
6003
+ animationState: "visible",
6004
+ variant: "fullscreen",
6005
+ className: ["translate-y-0", "opacity-100", "duration-medium4", "ease-emphasized-decelerate"]
6006
+ },
6007
+ // Fullscreen: exiting — slide down + fade out
6008
+ {
6009
+ animationState: "exiting",
6010
+ variant: "fullscreen",
6011
+ className: ["translate-y-full", "opacity-0", "duration-short2", "ease-emphasized-accelerate"]
6012
+ },
6013
+ // Fullscreen: exited — fully off-screen
6014
+ {
6015
+ animationState: "exited",
6016
+ variant: "fullscreen",
6017
+ className: ["translate-y-full", "opacity-0"]
6018
+ }
6019
+ ],
6020
+ defaultVariants: {
6021
+ animationState: "entering",
6022
+ variant: "basic"
6023
+ }
6024
+ });
6025
+ var dialogHeadlineVariants = cva(["text-headline-small", "text-on-surface"], {
6026
+ variants: {
6027
+ variant: {
6028
+ basic: ["mb-4"],
6029
+ fullscreen: [
6030
+ // Top app bar row in fullscreen: flex, items-center, gap
6031
+ "flex",
6032
+ "items-center",
6033
+ "gap-4",
6034
+ "px-4",
6035
+ "h-14",
6036
+ "shrink-0",
6037
+ "border-b",
6038
+ "border-outline-variant"
6039
+ ]
6040
+ }
6041
+ },
6042
+ defaultVariants: {
6043
+ variant: "basic"
6044
+ }
6045
+ });
6046
+ var dialogHeadlineTitleVariants = cva([
6047
+ "flex-1",
6048
+ "text-headline-small",
6049
+ "text-on-surface",
6050
+ "truncate"
6051
+ ]);
6052
+ var dialogContentVariants = cva(
6053
+ ["text-body-medium", "text-on-surface-variant", "overflow-y-auto", "flex-1"],
6054
+ {
6055
+ variants: {
6056
+ variant: {
6057
+ basic: ["mb-6"],
6058
+ fullscreen: ["px-6", "py-4"]
6059
+ }
6060
+ },
6061
+ defaultVariants: {
6062
+ variant: "basic"
6063
+ }
6064
+ }
6065
+ );
6066
+ var dialogActionsVariants = cva([
6067
+ "flex",
6068
+ "items-center",
6069
+ "justify-end",
6070
+ "gap-2",
6071
+ "pt-3",
6072
+ "shrink-0"
6073
+ ]);
6074
+ var Dialog = forwardRef(function Dialog2({
6075
+ variant = "basic",
6076
+ open,
6077
+ defaultOpen = false,
6078
+ onOpenChange,
6079
+ "aria-label": ariaLabel,
6080
+ children,
6081
+ className
6082
+ }, _ref) {
6083
+ const panelClassName = cn(dialogPanelVariants({ variant }), className);
6084
+ const scrimClass = dialogScrimVariants();
6085
+ return /* @__PURE__ */ jsx(
6086
+ DialogHeadless,
6087
+ {
6088
+ variant,
6089
+ ...open !== void 0 ? { open } : {},
6090
+ ...defaultOpen !== void 0 ? { defaultOpen } : {},
6091
+ ...onOpenChange !== void 0 ? { onOpenChange } : {},
6092
+ ...ariaLabel ? { "aria-label": ariaLabel } : {},
6093
+ className: panelClassName,
6094
+ scrimClassName: scrimClass,
6095
+ getAnimationClassName: (state) => dialogAnimationVariants({ animationState: state, variant }),
6096
+ children
6097
+ }
6098
+ );
6099
+ });
6100
+ Dialog.displayName = "Dialog";
6101
+ var DialogHeadline = forwardRef(
6102
+ function DialogHeadline2({ children, className, closeButton, confirmButton }, ref) {
6103
+ const { headlineId, variant } = useDialogContext();
6104
+ if (variant === "fullscreen") {
6105
+ return (
6106
+ // Top app bar row for fullscreen variant
6107
+ /* @__PURE__ */ jsxs("div", { className: cn(dialogHeadlineVariants({ variant: "fullscreen" }), className), children: [
6108
+ closeButton,
6109
+ /* @__PURE__ */ jsx("h2", { id: headlineId, className: dialogHeadlineTitleVariants(), children }),
6110
+ confirmButton
6111
+ ] })
6112
+ );
6113
+ }
6114
+ return /* @__PURE__ */ jsx(
6115
+ "h2",
6116
+ {
6117
+ ref,
6118
+ id: headlineId,
6119
+ className: cn(dialogHeadlineVariants({ variant: "basic" }), className),
6120
+ children
6121
+ }
6122
+ );
6123
+ }
6124
+ );
6125
+ DialogHeadline.displayName = "DialogHeadline";
6126
+ var DialogContent = forwardRef(function DialogContent2({ children, className }, ref) {
6127
+ const { contentId, variant } = useDialogContext();
6128
+ return /* @__PURE__ */ jsx("div", { ref, id: contentId, className: cn(dialogContentVariants({ variant }), className), children });
6129
+ });
6130
+ DialogContent.displayName = "DialogContent";
6131
+ var DialogActions = forwardRef(function DialogActions2({ children, className }, ref) {
6132
+ useDialogContext();
6133
+ return /* @__PURE__ */ jsx("div", { ref, className: cn(dialogActionsVariants(), className), children });
6134
+ });
6135
+ DialogActions.displayName = "DialogActions";
4501
6136
 
4502
- export { AppBar, AppBarHeadless, Button, Checkbox, Drawer, DrawerItem, DrawerSection, FAB, FABHeadless, HeadlessDrawer, HeadlessDrawerItem, HeadlessNavigationBar, HeadlessNavigationBarItem, HeadlessTab, HeadlessTabList, HeadlessTabPanel, IconButton, IconButtonHeadless, NavigationBar, NavigationBarItem, Radio, RadioGroup, RadioGroupHeadless, RadioHeadless, STATE_LAYER_OPACITY, Switch, TYPOGRAPHY_ELEMENT_MAP, TYPOGRAPHY_USAGE, Tab, TabList, TabPanel, Tabs, TextField, applyStateLayer, cn, generateMD3Theme, getColorValue, getFontFamily, getMD3Color, getResponsiveTypography, getTypographyClassName, getTypographyForElement, getTypographyStyle, getTypographyToken, hexToRgb, pxToRem, remToPx, rgbToHex, truncateText, withOpacity };
6137
+ export { AppBar, AppBarHeadless, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContext, DialogHeadless, DialogHeadline, Drawer, DrawerItem, DrawerSection, FAB, FABHeadless, HeadlessDrawer, HeadlessDrawerItem, HeadlessMenu, HeadlessMenuDivider, HeadlessMenuItem, HeadlessMenuSection, HeadlessMenuTrigger, HeadlessNavigationBar, HeadlessNavigationBarItem, HeadlessTab, HeadlessTabList, HeadlessTabPanel, IconButton, IconButtonHeadless, Menu, MenuContext, MenuDivider, MenuItem, MenuSection, MenuTrigger, NavigationBar, NavigationBarItem, Progress, ProgressHeadless, Radio, RadioGroup, RadioGroupHeadless, RadioHeadless, STATE_LAYER_OPACITY, Snackbar, SnackbarContext, SnackbarHeadless, SnackbarProvider, Switch, TYPOGRAPHY_ELEMENT_MAP, TYPOGRAPHY_USAGE, Tab, TabList, TabPanel, Tabs, TextField, applyStateLayer, cn, generateMD3Theme, getColorValue, getFontFamily, getMD3Color, getResponsiveTypography, getTypographyClassName, getTypographyForElement, getTypographyStyle, getTypographyToken, hexToRgb, pxToRem, remToPx, rgbToHex, truncateText, useDialogContext, useMenuContext, useSnackbar, withOpacity };
4503
6138
  //# sourceMappingURL=index.js.map
4504
6139
  //# sourceMappingURL=index.js.map