@fanvue/ui 3.13.0 → 3.14.1

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.
Files changed (31) hide show
  1. package/dist/cjs/components/AudioPlayer/AudioPlayer.cjs +357 -0
  2. package/dist/cjs/components/AudioPlayer/AudioPlayer.cjs.map +1 -0
  3. package/dist/cjs/components/Dialog/Dialog.cjs +6 -4
  4. package/dist/cjs/components/Dialog/Dialog.cjs.map +1 -1
  5. package/dist/cjs/components/FloatingActionButton/FloatingActionButton.cjs +59 -0
  6. package/dist/cjs/components/FloatingActionButton/FloatingActionButton.cjs.map +1 -0
  7. package/dist/cjs/components/Icons/GridViewIcon.cjs +50 -0
  8. package/dist/cjs/components/Icons/GridViewIcon.cjs.map +1 -0
  9. package/dist/cjs/components/Icons/ListViewIcon.cjs +50 -0
  10. package/dist/cjs/components/Icons/ListViewIcon.cjs.map +1 -0
  11. package/dist/cjs/components/SegmentedControl/SegmentedControl.cjs +42 -11
  12. package/dist/cjs/components/SegmentedControl/SegmentedControl.cjs.map +1 -1
  13. package/dist/cjs/index.cjs +8 -0
  14. package/dist/cjs/index.cjs.map +1 -1
  15. package/dist/components/AudioPlayer/AudioPlayer.mjs +340 -0
  16. package/dist/components/AudioPlayer/AudioPlayer.mjs.map +1 -0
  17. package/dist/components/Dialog/Dialog.mjs +6 -4
  18. package/dist/components/Dialog/Dialog.mjs.map +1 -1
  19. package/dist/components/FloatingActionButton/FloatingActionButton.mjs +42 -0
  20. package/dist/components/FloatingActionButton/FloatingActionButton.mjs.map +1 -0
  21. package/dist/components/Icons/GridViewIcon.mjs +33 -0
  22. package/dist/components/Icons/GridViewIcon.mjs.map +1 -0
  23. package/dist/components/Icons/ListViewIcon.mjs +33 -0
  24. package/dist/components/Icons/ListViewIcon.mjs.map +1 -0
  25. package/dist/components/SegmentedControl/SegmentedControl.mjs +42 -11
  26. package/dist/components/SegmentedControl/SegmentedControl.mjs.map +1 -1
  27. package/dist/index.d.ts +134 -1
  28. package/dist/index.mjs +8 -0
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/styles/base.css +15 -0
  31. package/package.json +1 -1
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { BaseIcon } from "./BaseIcon.mjs";
5
+ const VARIANTS = {
6
+ 16: {
7
+ outlined: [
8
+ {
9
+ d: "M13.267 9H2.733c-1 0-1.4.427-1.4 1.487v2.693c0 1.06.4 1.487 1.4 1.487h10.534c1 0 1.4-.427 1.4-1.487v-2.693c0-1.06-.4-1.487-1.4-1.487m0-7.667H2.733c-1 0-1.4.427-1.4 1.487v2.693c0 1.06.4 1.487 1.4 1.487h10.534c1 0 1.4-.427 1.4-1.487V2.82c0-1.06-.4-1.487-1.4-1.487"
10
+ }
11
+ ]
12
+ },
13
+ 24: {
14
+ outlined: [
15
+ {
16
+ d: "M19.9 13.5H4.1c-1.5 0-2.1.64-2.1 2.23v4.04C2 21.36 2.6 22 4.1 22h15.8c1.5 0 2.1-.64 2.1-2.23v-4.04c0-1.59-.6-2.23-2.1-2.23m0-11.5H4.1C2.6 2 2 2.64 2 4.23v4.04c0 1.59.6 2.23 2.1 2.23h15.8c1.5 0 2.1-.64 2.1-2.23V4.23C22 2.64 21.4 2 19.9 2"
17
+ }
18
+ ]
19
+ },
20
+ 32: {
21
+ outlined: [
22
+ {
23
+ d: "M26.533 18H5.467c-2 0-2.8.853-2.8 2.973v5.387c0 2.12.8 2.973 2.8 2.973h21.066c2 0 2.8-.853 2.8-2.973v-5.387c0-2.12-.8-2.973-2.8-2.973m0-15.333H5.467c-2 0-2.8.853-2.8 2.973v5.387c0 2.12.8 2.973 2.8 2.973h21.066c2 0 2.8-.854 2.8-2.973V5.64c0-2.12-.8-2.973-2.8-2.973"
24
+ }
25
+ ]
26
+ }
27
+ };
28
+ const ListViewIcon = React.forwardRef((props, ref) => /* @__PURE__ */ jsx(BaseIcon, { ref, variants: VARIANTS, ...props }));
29
+ ListViewIcon.displayName = "ListViewIcon";
30
+ export {
31
+ ListViewIcon
32
+ };
33
+ //# sourceMappingURL=ListViewIcon.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ListViewIcon.mjs","sources":["../../../src/components/Icons/ListViewIcon.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { BaseIcon } from \"./BaseIcon\";\nimport type { BaseIconProps, IconVariants } from \"./types\";\n\nconst VARIANTS: IconVariants = {\n 16: {\n outlined: [\n {\n d: \"M13.267 9H2.733c-1 0-1.4.427-1.4 1.487v2.693c0 1.06.4 1.487 1.4 1.487h10.534c1 0 1.4-.427 1.4-1.487v-2.693c0-1.06-.4-1.487-1.4-1.487m0-7.667H2.733c-1 0-1.4.427-1.4 1.487v2.693c0 1.06.4 1.487 1.4 1.487h10.534c1 0 1.4-.427 1.4-1.487V2.82c0-1.06-.4-1.487-1.4-1.487\",\n },\n ],\n },\n 24: {\n outlined: [\n {\n d: \"M19.9 13.5H4.1c-1.5 0-2.1.64-2.1 2.23v4.04C2 21.36 2.6 22 4.1 22h15.8c1.5 0 2.1-.64 2.1-2.23v-4.04c0-1.59-.6-2.23-2.1-2.23m0-11.5H4.1C2.6 2 2 2.64 2 4.23v4.04c0 1.59.6 2.23 2.1 2.23h15.8c1.5 0 2.1-.64 2.1-2.23V4.23C22 2.64 21.4 2 19.9 2\",\n },\n ],\n },\n 32: {\n outlined: [\n {\n d: \"M26.533 18H5.467c-2 0-2.8.853-2.8 2.973v5.387c0 2.12.8 2.973 2.8 2.973h21.066c2 0 2.8-.853 2.8-2.973v-5.387c0-2.12-.8-2.973-2.8-2.973m0-15.333H5.467c-2 0-2.8.853-2.8 2.973v5.387c0 2.12.8 2.973 2.8 2.973h21.066c2 0 2.8-.854 2.8-2.973V5.64c0-2.12-.8-2.973-2.8-2.973\",\n },\n ],\n },\n};\n\n/** Props for {@link ListViewIcon}. See {@link BaseIconProps} for the shared shape. */\nexport type ListViewIconProps = BaseIconProps;\n\n/**\n * List view icon. Renders at sizes 16, 24, or 32 px.\n *\n * @example\n * ```tsx\n * <ListViewIcon size={24} />\n * ```\n */\nexport const ListViewIcon = React.forwardRef<SVGSVGElement, ListViewIconProps>((props, ref) => (\n <BaseIcon ref={ref} variants={VARIANTS} {...props} />\n));\n\nListViewIcon.displayName = \"ListViewIcon\";\n"],"names":[],"mappings":";;;;AAIA,MAAM,WAAyB;AAAA,EAC7B,IAAI;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEF,IAAI;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,IACL;AAAA,EACF;AAAA,EAEF,IAAI;AAAA,IACF,UAAU;AAAA,MACR;AAAA,QACE,GAAG;AAAA,MAAA;AAAA,IACL;AAAA,EACF;AAEJ;AAaO,MAAM,eAAe,MAAM,WAA6C,CAAC,OAAO,QACrF,oBAAC,UAAA,EAAS,KAAU,UAAU,UAAW,GAAG,OAAO,CACpD;AAED,aAAa,cAAc;"}
@@ -16,11 +16,47 @@ function warnMissingAccessibleName(ariaLabel, ariaLabelledBy) {
16
16
  }
17
17
  }
18
18
  }
19
+ function warnMissingOptionAccessibleName(options) {
20
+ if (process.env.NODE_ENV !== "production") {
21
+ for (const option of options) {
22
+ if (option.icon && !option.label?.trim()) {
23
+ console.warn(
24
+ `SegmentedControl: icon-only segment "${option.value}" is missing a non-empty \`label\` to use as its accessible name.`
25
+ );
26
+ }
27
+ }
28
+ }
29
+ }
30
+ function getSegmentClassName({
31
+ appearance,
32
+ size,
33
+ variant,
34
+ isSelected,
35
+ disabled
36
+ }) {
37
+ return cn(
38
+ "relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full",
39
+ "motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
40
+ "focus-visible:shadow-focus-ring focus-visible:outline-none",
41
+ variant === "fill" ? "flex-1" : "shrink-0",
42
+ appearance === "plain" ? cn(
43
+ // Padding + negative margin enlarge the hit target without changing the
44
+ // visual footprint, which must stay glyph-only to match the design.
45
+ "-m-1 p-1",
46
+ isSelected ? "text-icons-primary" : "text-icons-tertiary hover:text-icons-primary"
47
+ ) : cn(
48
+ sizeClasses[size],
49
+ isSelected ? "bg-buttons-primary-default text-content-primary-inverted shadow-sm" : "text-content-primary hover:bg-buttons-switch-hover"
50
+ ),
51
+ disabled && "pointer-events-none"
52
+ );
53
+ }
19
54
  const SegmentedControl = React.forwardRef(
20
55
  ({
21
56
  className,
22
57
  size = "32",
23
58
  variant = "hug",
59
+ appearance = "pill",
24
60
  options,
25
61
  value: controlledValue,
26
62
  defaultValue,
@@ -29,6 +65,7 @@ const SegmentedControl = React.forwardRef(
29
65
  ...props
30
66
  }, ref) => {
31
67
  warnMissingAccessibleName(props["aria-label"], props["aria-labelledby"]);
68
+ warnMissingOptionAccessibleName(options);
32
69
  const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0]?.value);
33
70
  const isControlled = controlledValue !== void 0;
34
71
  const currentValue = isControlled ? controlledValue : internalValue;
@@ -55,8 +92,9 @@ const SegmentedControl = React.forwardRef(
55
92
  ref,
56
93
  role: "radiogroup",
57
94
  className: cn(
58
- "relative items-center rounded-full bg-surface-tertiary p-1",
95
+ "relative items-center rounded-full",
59
96
  variant === "fill" ? "flex w-full" : "inline-flex",
97
+ appearance === "plain" ? "gap-2" : "bg-surface-tertiary p-1",
60
98
  disabled && "cursor-not-allowed opacity-50",
61
99
  className
62
100
  ),
@@ -76,18 +114,11 @@ const SegmentedControl = React.forwardRef(
76
114
  "aria-checked": isSelected,
77
115
  tabIndex: isSelected || !anySelected && index === 0 ? 0 : -1,
78
116
  disabled,
117
+ "aria-label": option.icon ? option.label : void 0,
79
118
  onClick: () => handleSelect(option.value),
80
119
  onKeyDown: (e) => handleKeyDown(e, index),
81
- className: cn(
82
- "relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full",
83
- "motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out",
84
- "focus-visible:shadow-focus-ring focus-visible:outline-none",
85
- variant === "fill" ? "flex-1" : "shrink-0",
86
- sizeClasses[size],
87
- isSelected ? "bg-buttons-primary-default text-content-primary-inverted shadow-sm" : "text-content-primary hover:bg-buttons-switch-hover",
88
- disabled && "pointer-events-none"
89
- ),
90
- children: /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: option.label })
120
+ className: getSegmentClassName({ appearance, size, variant, isSelected, disabled }),
121
+ children: option.icon ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center justify-center", "aria-hidden": "true", children: option.icon }) : /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: option.label })
91
122
  },
92
123
  option.value
93
124
  )
@@ -1 +1 @@
1
- {"version":3,"file":"SegmentedControl.mjs","sources":["../../../src/components/SegmentedControl/SegmentedControl.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Height of the segmented control in pixels. */\nexport type SegmentedControlSize = \"32\" | \"40\" | \"48\";\n\n/**\n * Segment layout.\n * - `\"hug\"`: each segment is sized to its content.\n * - `\"fill\"`: the control spans the full width of its container and each segment grows equally.\n */\nexport type SegmentedControlVariant = \"hug\" | \"fill\";\n\n/** Describes one selectable segment. */\nexport interface SegmentedControlOption {\n /** Display label for the segment. */\n label: string;\n /** Value identifier returned via `onChange`. */\n value: string;\n}\n\nexport interface SegmentedControlProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Height of the control in pixels. @default \"32\" */\n size?: SegmentedControlSize;\n /** Segment layout. @default \"hug\" */\n variant?: SegmentedControlVariant;\n /** The selectable segments. Designed for two or three mutually exclusive options. */\n options: SegmentedControlOption[];\n /** Currently selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). Defaults to the first option. */\n defaultValue?: string;\n /** Callback fired when the selected value changes. */\n onChange?: (value: string) => void;\n /** Whether the control is disabled. @default false */\n disabled?: boolean;\n}\n\n/** Padding and typography per size. Combined with the container's `p-1` these hit the 32/40/48px heights. */\nconst sizeClasses: Record<SegmentedControlSize, string> = {\n \"32\": \"px-2 py-1 typography-description-12px-semibold\",\n \"40\": \"px-3 py-1.75 typography-body-small-14px-semibold\",\n \"48\": \"px-4 py-2 typography-body-default-16px-semibold\",\n};\n\nfunction warnMissingAccessibleName(ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"SegmentedControl: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\n/**\n * A compact selector for choosing between two or three mutually exclusive\n * options where the choice affects the content immediately below it. Use\n * instead of tabs when the options are more like settings or filters than\n * navigation, such as toggling a list/grid view or a monthly/annual price.\n *\n * Rendered as a `radiogroup` with roving-tabindex keyboard navigation. Supports\n * both controlled and uncontrolled usage.\n *\n * @example\n * ```tsx\n * <SegmentedControl\n * options={[\n * { label: \"Net\", value: \"net\" },\n * { label: \"Gross\", value: \"gross\" },\n * ]}\n * value={amount}\n * onChange={setAmount}\n * aria-label=\"Amount type\"\n * />\n * ```\n */\nexport const SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n (\n {\n className,\n size = \"32\",\n variant = \"hug\",\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n disabled = false,\n ...props\n },\n ref,\n ) => {\n warnMissingAccessibleName(props[\"aria-label\"], props[\"aria-labelledby\"]);\n\n // Tracks selection for uncontrolled usage; ignored when `value` prop is provided\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0]?.value);\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n const anySelected = options.some((o) => o.value === currentValue);\n const buttonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const handleSelect = (optionValue: string) => {\n if (disabled || optionValue === currentValue) return;\n if (!isControlled) {\n setInternalValue(optionValue);\n }\n onChange?.(optionValue);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const nextIndex =\n e.key === \"ArrowRight\" || e.key === \"ArrowDown\"\n ? (index + 1) % options.length\n : e.key === \"ArrowLeft\" || e.key === \"ArrowUp\"\n ? (index - 1 + options.length) % options.length\n : null;\n if (nextIndex === null) return;\n e.preventDefault();\n const nextOption = options[nextIndex] as SegmentedControlOption;\n handleSelect(nextOption.value);\n buttonRefs.current[nextIndex]?.focus();\n };\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n className={cn(\n \"relative items-center rounded-full bg-surface-tertiary p-1\",\n variant === \"fill\" ? \"flex w-full\" : \"inline-flex\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n {...props}\n >\n {options.map((option, index) => {\n const isSelected = currentValue === option.value;\n return (\n // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation\n <button\n key={option.value}\n ref={(el) => {\n buttonRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n tabIndex={isSelected || (!anySelected && index === 0) ? 0 : -1}\n disabled={disabled}\n onClick={() => handleSelect(option.value)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n className={cn(\n \"relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n variant === \"fill\" ? \"flex-1\" : \"shrink-0\",\n sizeClasses[size],\n isSelected\n ? \"bg-buttons-primary-default text-content-primary-inverted shadow-sm\"\n : \"text-content-primary hover:bg-buttons-switch-hover\",\n disabled && \"pointer-events-none\",\n )}\n >\n <span className=\"min-w-0 truncate\">{option.label}</span>\n </button>\n );\n })}\n </div>\n );\n },\n);\n\nSegmentedControl.displayName = \"SegmentedControl\";\n"],"names":[],"mappings":";;;;AAwCA,MAAM,cAAoD;AAAA,EACxD,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;AAEA,SAAS,0BAA0B,WAAoB,gBAAyB;AAC9E,MAAI,QAAQ,IAAI,aAAa,cAAc;AACzC,QAAI,CAAC,aAAa,CAAC,gBAAgB;AACjC,cAAQ;AAAA,QACN;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AACF;AAwBO,MAAM,mBAAmB,MAAM;AAAA,EACpC,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,UAAU;AAAA,IACV;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,GAAG;AAAA,EAAA,GAEL,QACG;AACH,8BAA0B,MAAM,YAAY,GAAG,MAAM,iBAAiB,CAAC;AAGvE,UAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,gBAAgB,QAAQ,CAAC,GAAG,KAAK;AAC1F,UAAM,eAAe,oBAAoB;AACzC,UAAM,eAAe,eAAe,kBAAkB;AACtD,UAAM,cAAc,QAAQ,KAAK,CAAC,MAAM,EAAE,UAAU,YAAY;AAChE,UAAM,aAAa,MAAM,OAAqC,EAAE;AAEhE,UAAM,eAAe,CAAC,gBAAwB;AAC5C,UAAI,YAAY,gBAAgB,aAAc;AAC9C,UAAI,CAAC,cAAc;AACjB,yBAAiB,WAAW;AAAA,MAC9B;AACA,iBAAW,WAAW;AAAA,IACxB;AAEA,UAAM,gBAAgB,CAAC,GAAwB,UAAkB;AAC/D,YAAM,YACJ,EAAE,QAAQ,gBAAgB,EAAE,QAAQ,eAC/B,QAAQ,KAAK,QAAQ,SACtB,EAAE,QAAQ,eAAe,EAAE,QAAQ,aAChC,QAAQ,IAAI,QAAQ,UAAU,QAAQ,SACvC;AACR,UAAI,cAAc,KAAM;AACxB,QAAE,eAAA;AACF,YAAM,aAAa,QAAQ,SAAS;AACpC,mBAAa,WAAW,KAAK;AAC7B,iBAAW,QAAQ,SAAS,GAAG,MAAA;AAAA,IACjC;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAW;AAAA,UACT;AAAA,UACA,YAAY,SAAS,gBAAgB;AAAA,UACrC,YAAY;AAAA,UACZ;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA,QAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,gBAAM,aAAa,iBAAiB,OAAO;AAC3C;AAAA;AAAA,YAEE;AAAA,cAAC;AAAA,cAAA;AAAA,gBAEC,KAAK,CAAC,OAAO;AACX,6BAAW,QAAQ,KAAK,IAAI;AAAA,gBAC9B;AAAA,gBACA,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,gBAAc;AAAA,gBACd,UAAU,cAAe,CAAC,eAAe,UAAU,IAAK,IAAI;AAAA,gBAC5D;AAAA,gBACA,SAAS,MAAM,aAAa,OAAO,KAAK;AAAA,gBACxC,WAAW,CAAC,MAAM,cAAc,GAAG,KAAK;AAAA,gBACxC,WAAW;AAAA,kBACT;AAAA,kBACA;AAAA,kBACA;AAAA,kBACA,YAAY,SAAS,WAAW;AAAA,kBAChC,YAAY,IAAI;AAAA,kBAChB,aACI,uEACA;AAAA,kBACJ,YAAY;AAAA,gBAAA;AAAA,gBAGd,UAAA,oBAAC,QAAA,EAAK,WAAU,oBAAoB,iBAAO,MAAA,CAAM;AAAA,cAAA;AAAA,cAvB5C,OAAO;AAAA,YAAA;AAAA;AAAA,QA0BlB,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EAGP;AACF;AAEA,iBAAiB,cAAc;"}
1
+ {"version":3,"file":"SegmentedControl.mjs","sources":["../../../src/components/SegmentedControl/SegmentedControl.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Height of the segmented control in pixels. */\nexport type SegmentedControlSize = \"32\" | \"40\" | \"48\";\n\n/**\n * Segment layout.\n * - `\"hug\"`: each segment is sized to its content.\n * - `\"fill\"`: the control spans the full width of its container and each segment grows equally.\n */\nexport type SegmentedControlVariant = \"hug\" | \"fill\";\n\n/**\n * Visual style of the control.\n * - `\"pill\"`: the container has a muted background and the selected segment shows a filled pill (default).\n * - `\"plain\"`: no container or selected-pill background; segments are bare content and selection is\n * communicated by color alone. Designed for icon-only toggles (e.g. a list/grid view switch).\n */\nexport type SegmentedControlAppearance = \"pill\" | \"plain\";\n\n/** Describes one selectable segment. */\nexport interface SegmentedControlOption {\n /**\n * Display label for the segment. When `icon` is provided, the segment renders icon-only and\n * this value is used as its accessible name (applied as `aria-label`) instead of visible text.\n */\n label: string;\n /** Value identifier returned via `onChange`. */\n value: string;\n /**\n * Icon to render instead of the visible label. When set, the segment renders icon-only and\n * `label` becomes required as its accessible name — it is applied as `aria-label` and no\n * visible text is rendered.\n */\n icon?: React.ReactNode;\n}\n\nexport interface SegmentedControlProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Height of the control in pixels. @default \"32\" */\n size?: SegmentedControlSize;\n /** Segment layout. @default \"hug\" */\n variant?: SegmentedControlVariant;\n /** Visual style of the control. @default \"pill\" */\n appearance?: SegmentedControlAppearance;\n /** The selectable segments. Designed for two or three mutually exclusive options. */\n options: SegmentedControlOption[];\n /** Currently selected value (controlled). */\n value?: string;\n /** Initially selected value (uncontrolled). Defaults to the first option. */\n defaultValue?: string;\n /** Callback fired when the selected value changes. */\n onChange?: (value: string) => void;\n /** Whether the control is disabled. @default false */\n disabled?: boolean;\n}\n\n/** Padding and typography per size. Combined with the container's `p-1` these hit the 32/40/48px heights. */\nconst sizeClasses: Record<SegmentedControlSize, string> = {\n \"32\": \"px-2 py-1 typography-description-12px-semibold\",\n \"40\": \"px-3 py-1.75 typography-body-small-14px-semibold\",\n \"48\": \"px-4 py-2 typography-body-default-16px-semibold\",\n};\n\nfunction warnMissingAccessibleName(ariaLabel?: string, ariaLabelledBy?: string) {\n if (process.env.NODE_ENV !== \"production\") {\n if (!ariaLabel && !ariaLabelledBy) {\n console.warn(\n \"SegmentedControl: no accessible name provided. Pass an `aria-label` or `aria-labelledby` prop.\",\n );\n }\n }\n}\n\nfunction warnMissingOptionAccessibleName(options: SegmentedControlOption[]) {\n if (process.env.NODE_ENV !== \"production\") {\n for (const option of options) {\n if (option.icon && !option.label?.trim()) {\n console.warn(\n `SegmentedControl: icon-only segment \"${option.value}\" is missing a non-empty \\`label\\` to use as its accessible name.`,\n );\n }\n }\n }\n}\n\nfunction getSegmentClassName({\n appearance,\n size,\n variant,\n isSelected,\n disabled,\n}: {\n appearance: SegmentedControlAppearance;\n size: SegmentedControlSize;\n variant: SegmentedControlVariant;\n isSelected: boolean;\n disabled: boolean;\n}) {\n return cn(\n \"relative inline-flex min-w-0 cursor-pointer items-center justify-center rounded-full\",\n \"motion-safe:transition-colors motion-safe:duration-150 motion-safe:ease-in-out\",\n \"focus-visible:shadow-focus-ring focus-visible:outline-none\",\n variant === \"fill\" ? \"flex-1\" : \"shrink-0\",\n appearance === \"plain\"\n ? cn(\n // Padding + negative margin enlarge the hit target without changing the\n // visual footprint, which must stay glyph-only to match the design.\n \"-m-1 p-1\",\n isSelected ? \"text-icons-primary\" : \"text-icons-tertiary hover:text-icons-primary\",\n )\n : cn(\n sizeClasses[size],\n isSelected\n ? \"bg-buttons-primary-default text-content-primary-inverted shadow-sm\"\n : \"text-content-primary hover:bg-buttons-switch-hover\",\n ),\n disabled && \"pointer-events-none\",\n );\n}\n\n/**\n * A compact selector for choosing between two or three mutually exclusive\n * options where the choice affects the content immediately below it. Use\n * instead of tabs when the options are more like settings or filters than\n * navigation, such as toggling a list/grid view or a monthly/annual price.\n *\n * Rendered as a `radiogroup` with roving-tabindex keyboard navigation. Supports\n * both controlled and uncontrolled usage.\n *\n * @example\n * ```tsx\n * <SegmentedControl\n * options={[\n * { label: \"Net\", value: \"net\" },\n * { label: \"Gross\", value: \"gross\" },\n * ]}\n * value={amount}\n * onChange={setAmount}\n * aria-label=\"Amount type\"\n * />\n * ```\n *\n * @example Icon-only segments (e.g. a list/grid view toggle)\n * ```tsx\n * <SegmentedControl\n * appearance=\"plain\"\n * options={[\n * { label: \"List view\", value: \"list\", icon: <ListViewIcon size={16} /> },\n * { label: \"Grid view\", value: \"grid\", icon: <GridViewIcon size={16} /> },\n * ]}\n * value={view}\n * onChange={setView}\n * aria-label=\"View\"\n * />\n * ```\n */\nexport const SegmentedControl = React.forwardRef<HTMLDivElement, SegmentedControlProps>(\n (\n {\n className,\n size = \"32\",\n variant = \"hug\",\n appearance = \"pill\",\n options,\n value: controlledValue,\n defaultValue,\n onChange,\n disabled = false,\n ...props\n },\n ref,\n ) => {\n warnMissingAccessibleName(props[\"aria-label\"], props[\"aria-labelledby\"]);\n warnMissingOptionAccessibleName(options);\n\n // Tracks selection for uncontrolled usage; ignored when `value` prop is provided\n const [internalValue, setInternalValue] = React.useState(defaultValue ?? options[0]?.value);\n const isControlled = controlledValue !== undefined;\n const currentValue = isControlled ? controlledValue : internalValue;\n const anySelected = options.some((o) => o.value === currentValue);\n const buttonRefs = React.useRef<(HTMLButtonElement | null)[]>([]);\n\n const handleSelect = (optionValue: string) => {\n if (disabled || optionValue === currentValue) return;\n if (!isControlled) {\n setInternalValue(optionValue);\n }\n onChange?.(optionValue);\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n const nextIndex =\n e.key === \"ArrowRight\" || e.key === \"ArrowDown\"\n ? (index + 1) % options.length\n : e.key === \"ArrowLeft\" || e.key === \"ArrowUp\"\n ? (index - 1 + options.length) % options.length\n : null;\n if (nextIndex === null) return;\n e.preventDefault();\n const nextOption = options[nextIndex] as SegmentedControlOption;\n handleSelect(nextOption.value);\n buttonRefs.current[nextIndex]?.focus();\n };\n\n return (\n <div\n ref={ref}\n role=\"radiogroup\"\n className={cn(\n \"relative items-center rounded-full\",\n variant === \"fill\" ? \"flex w-full\" : \"inline-flex\",\n appearance === \"plain\" ? \"gap-2\" : \"bg-surface-tertiary p-1\",\n disabled && \"cursor-not-allowed opacity-50\",\n className,\n )}\n {...props}\n >\n {options.map((option, index) => {\n const isSelected = currentValue === option.value;\n return (\n // biome-ignore lint/a11y/useSemanticElements: native radio inputs only allow Tab-focus on the checked item; buttons with roving tabindex give full keyboard navigation\n <button\n key={option.value}\n ref={(el) => {\n buttonRefs.current[index] = el;\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={isSelected}\n tabIndex={isSelected || (!anySelected && index === 0) ? 0 : -1}\n disabled={disabled}\n aria-label={option.icon ? option.label : undefined}\n onClick={() => handleSelect(option.value)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n className={getSegmentClassName({ appearance, size, variant, isSelected, disabled })}\n >\n {option.icon ? (\n <span className=\"flex shrink-0 items-center justify-center\" aria-hidden=\"true\">\n {option.icon}\n </span>\n ) : (\n <span className=\"min-w-0 truncate\">{option.label}</span>\n )}\n </button>\n );\n })}\n </div>\n );\n },\n);\n\nSegmentedControl.displayName = \"SegmentedControl\";\n"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -332,6 +332,54 @@ export declare interface AudioFileRejection {
332
332
  errors: AudioValidationError[];
333
333
  }
334
334
 
335
+ /**
336
+ * A compact playback control for a single audio clip: a play/pause toggle, a
337
+ * static amplitude waveform that doubles as a seek scrubber, and elapsed/total
338
+ * timestamps. Designed to sit as an overlay on media thumbnails (Vault cards)
339
+ * as well as inline rows (the AI Voice Message "Generated Audio" row).
340
+ *
341
+ * The waveform is seeded from the decoded audio when possible, falling back
342
+ * to a deterministic (not random) placeholder derived from `src` so the
343
+ * result is stable across renders, SSR, and Chromatic snapshots.
344
+ *
345
+ * @example
346
+ * ```tsx
347
+ * <AudioPlayer src="https://example.com/clip.mp3" duration={5} />
348
+ * ```
349
+ */
350
+ export declare const AudioPlayer: React_2.ForwardRefExoticComponent<AudioPlayerProps & React_2.RefAttributes<HTMLDivElement>>;
351
+
352
+ export declare interface AudioPlayerProps extends Omit<React_2.HTMLAttributes<HTMLDivElement>, "onPlay" | "onPause" | "onEnded"> {
353
+ /** URL of the audio file to play. */
354
+ src: string;
355
+ /**
356
+ * Total duration in seconds, used to render the timestamp before the
357
+ * media's own metadata has loaded (and as a fallback if it never does).
358
+ */
359
+ duration?: number;
360
+ /**
361
+ * Whether playback is active (controlled). Note: once `onEnded` fires, the
362
+ * browser has already stopped native playback. To loop or replay under
363
+ * controlled usage, toggle this prop `false` then `true` (rather than
364
+ * leaving it `true`) — the sync effect only calls `play()` again when this
365
+ * value actually changes.
366
+ */
367
+ playing?: boolean;
368
+ /** Initial playback state (uncontrolled). @default false */
369
+ defaultPlaying?: boolean;
370
+ /** Called when playback starts. */
371
+ onPlay?: () => void;
372
+ /** Called when playback is paused. */
373
+ onPause?: () => void;
374
+ /** Called when playback reaches the end of the media. */
375
+ onEnded?: () => void;
376
+ /** Height of the player row, in pixels. @default "40" */
377
+ size?: AudioPlayerSize;
378
+ }
379
+
380
+ /** Height of the audio player row, in pixels. Matches both the Vault card overlay and the "Generated Audio" modal row in Figma — both use an identical 32px play button and 40px row. */
381
+ export declare type AudioPlayerSize = "40";
382
+
335
383
  /**
336
384
  * Audio file upload with drag-and-drop and optional in-browser recording.
337
385
  * Supports file validation, multiple files, and real-time waveform visualization during recording.
@@ -1812,6 +1860,8 @@ export declare interface DialogContentProps extends React_2.ComponentPropsWithou
1812
1860
  * @default "sheet"
1813
1861
  */
1814
1862
  mobilePresentation?: "sheet" | "card";
1863
+ /** Props forwarded to the default {@link DialogOverlay} when `overlay` is `true`. */
1864
+ overlayProps?: DialogOverlayProps;
1815
1865
  }
1816
1866
 
1817
1867
  /** Accessible description for the dialog. Rendered as secondary text. */
@@ -2603,6 +2653,30 @@ export declare const FlashIcon: React_2.ForwardRefExoticComponent<BaseIconProps
2603
2653
  /** Props for {@link FlashIcon}. See {@link BaseIconProps} for the shared shape. */
2604
2654
  export declare type FlashIconProps = BaseIconProps;
2605
2655
 
2656
+ /**
2657
+ * A circular, elevated button for the single most important action on a
2658
+ * screen (e.g. creating new content). Unlike {@link IconButton}, it always
2659
+ * floats above page content with a drop shadow and is meant to be
2660
+ * positioned by the consumer (e.g. `fixed bottom-6 right-6`) rather than
2661
+ * inline in a toolbar or action row.
2662
+ *
2663
+ * Requires an accessible name — pass `aria-label` or `aria-labelledby`, since
2664
+ * the icon alone doesn't convey the action to screen readers.
2665
+ *
2666
+ * @example
2667
+ * ```tsx
2668
+ * <FloatingActionButton aria-label="Add content" className="fixed right-6 bottom-6">
2669
+ * <AddIcon />
2670
+ * </FloatingActionButton>
2671
+ * ```
2672
+ */
2673
+ export declare const FloatingActionButton: React_2.ForwardRefExoticComponent<FloatingActionButtonProps & React_2.RefAttributes<HTMLButtonElement>>;
2674
+
2675
+ export declare interface FloatingActionButtonProps extends React_2.ButtonHTMLAttributes<HTMLButtonElement> {
2676
+ /** Icon to render inside the button. Pass an icon component (e.g. `<AddIcon />`). */
2677
+ children: React_2.ReactNode;
2678
+ }
2679
+
2606
2680
  /**
2607
2681
  * Folder icon. Renders at sizes 16, 24, or 32 px with outlined and filled variants.
2608
2682
  *
@@ -2705,6 +2779,19 @@ export declare const GoogleIcon: React_2.ForwardRefExoticComponent<React_2.SVGAt
2705
2779
  className?: string;
2706
2780
  } & React_2.RefAttributes<SVGSVGElement>>;
2707
2781
 
2782
+ /**
2783
+ * Grid view icon. Renders at sizes 16, 24, or 32 px.
2784
+ *
2785
+ * @example
2786
+ * ```tsx
2787
+ * <GridViewIcon size={24} />
2788
+ * ```
2789
+ */
2790
+ export declare const GridViewIcon: React_2.ForwardRefExoticComponent<BaseIconProps & React_2.RefAttributes<SVGSVGElement>>;
2791
+
2792
+ /** Props for {@link GridViewIcon}. See {@link BaseIconProps} for the shared shape. */
2793
+ export declare type GridViewIconProps = BaseIconProps;
2794
+
2708
2795
  /**
2709
2796
  * Health icon. Renders at sizes 16, 24, or 32 px with outlined and filled variants.
2710
2797
  *
@@ -3054,6 +3141,19 @@ export declare type LinkSize = "16" | "14";
3054
3141
  /** Visual style variant of the link. */
3055
3142
  export declare type LinkVariant = "primary" | "brand";
3056
3143
 
3144
+ /**
3145
+ * List view icon. Renders at sizes 16, 24, or 32 px.
3146
+ *
3147
+ * @example
3148
+ * ```tsx
3149
+ * <ListViewIcon size={24} />
3150
+ * ```
3151
+ */
3152
+ export declare const ListViewIcon: React_2.ForwardRefExoticComponent<BaseIconProps & React_2.RefAttributes<SVGSVGElement>>;
3153
+
3154
+ /** Props for {@link ListViewIcon}. See {@link BaseIconProps} for the shared shape. */
3155
+ export declare type ListViewIconProps = BaseIconProps;
3156
+
3057
3157
  /**
3058
3158
  * A layout-aware loading indicator that renders a centered `SpinnerIcon` inside
3059
3159
  * a relatively-positioned container. Drop-in replacement for the Olympus `Loader`.
@@ -4071,15 +4171,46 @@ export declare type SearchIconProps = BaseIconProps;
4071
4171
  * aria-label="Amount type"
4072
4172
  * />
4073
4173
  * ```
4174
+ *
4175
+ * @example Icon-only segments (e.g. a list/grid view toggle)
4176
+ * ```tsx
4177
+ * <SegmentedControl
4178
+ * appearance="plain"
4179
+ * options={[
4180
+ * { label: "List view", value: "list", icon: <ListViewIcon size={16} /> },
4181
+ * { label: "Grid view", value: "grid", icon: <GridViewIcon size={16} /> },
4182
+ * ]}
4183
+ * value={view}
4184
+ * onChange={setView}
4185
+ * aria-label="View"
4186
+ * />
4187
+ * ```
4074
4188
  */
4075
4189
  export declare const SegmentedControl: React_2.ForwardRefExoticComponent<SegmentedControlProps & React_2.RefAttributes<HTMLDivElement>>;
4076
4190
 
4191
+ /**
4192
+ * Visual style of the control.
4193
+ * - `"pill"`: the container has a muted background and the selected segment shows a filled pill (default).
4194
+ * - `"plain"`: no container or selected-pill background; segments are bare content and selection is
4195
+ * communicated by color alone. Designed for icon-only toggles (e.g. a list/grid view switch).
4196
+ */
4197
+ export declare type SegmentedControlAppearance = "pill" | "plain";
4198
+
4077
4199
  /** Describes one selectable segment. */
4078
4200
  export declare interface SegmentedControlOption {
4079
- /** Display label for the segment. */
4201
+ /**
4202
+ * Display label for the segment. When `icon` is provided, the segment renders icon-only and
4203
+ * this value is used as its accessible name (applied as `aria-label`) instead of visible text.
4204
+ */
4080
4205
  label: string;
4081
4206
  /** Value identifier returned via `onChange`. */
4082
4207
  value: string;
4208
+ /**
4209
+ * Icon to render instead of the visible label. When set, the segment renders icon-only and
4210
+ * `label` becomes required as its accessible name — it is applied as `aria-label` and no
4211
+ * visible text is rendered.
4212
+ */
4213
+ icon?: React_2.ReactNode;
4083
4214
  }
4084
4215
 
4085
4216
  export declare interface SegmentedControlProps extends Omit<React_2.HTMLAttributes<HTMLDivElement>, "onChange"> {
@@ -4087,6 +4218,8 @@ export declare interface SegmentedControlProps extends Omit<React_2.HTMLAttribut
4087
4218
  size?: SegmentedControlSize;
4088
4219
  /** Segment layout. @default "hug" */
4089
4220
  variant?: SegmentedControlVariant;
4221
+ /** Visual style of the control. @default "pill" */
4222
+ appearance?: SegmentedControlAppearance;
4090
4223
  /** The selectable segments. Designed for two or three mutually exclusive options. */
4091
4224
  options: SegmentedControlOption[];
4092
4225
  /** Currently selected value (controlled). */
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import { AccordionContent } from "./components/Accordion/AccordionContent.mjs";
5
5
  import { AccordionItem } from "./components/Accordion/AccordionItem.mjs";
6
6
  import { AccordionTrigger } from "./components/Accordion/AccordionTrigger.mjs";
7
7
  import { Alert } from "./components/Alert/Alert.mjs";
8
+ import { AudioPlayer } from "./components/AudioPlayer/AudioPlayer.mjs";
8
9
  import { AudioUpload } from "./components/AudioUpload/AudioUpload.mjs";
9
10
  import { useAudioRecorder } from "./components/AudioUpload/useAudioRecorder.mjs";
10
11
  import { Autocomplete } from "./components/Autocomplete/Autocomplete.mjs";
@@ -30,6 +31,7 @@ import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, Dr
30
31
  import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuHeader, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger } from "./components/DropdownMenu/DropdownMenu.mjs";
31
32
  import { EmptyState } from "./components/EmptyState/EmptyState.mjs";
32
33
  import { FanFollowerCount } from "./components/FanFollowerCount/FanFollowerCount.mjs";
34
+ import { FloatingActionButton } from "./components/FloatingActionButton/FloatingActionButton.mjs";
33
35
  import { IconButton } from "./components/IconButton/IconButton.mjs";
34
36
  import { AddIcon } from "./components/Icons/AddIcon.mjs";
35
37
  import { AffiliatesIcon } from "./components/Icons/AffiliatesIcon.mjs";
@@ -103,6 +105,7 @@ import { GenderIcon } from "./components/Icons/GenderIcon.mjs";
103
105
  import { GifIcon } from "./components/Icons/GifIcon.mjs";
104
106
  import { GiftIcon } from "./components/Icons/GiftIcon.mjs";
105
107
  import { GoogleIcon } from "./components/Icons/GoogleIcon.mjs";
108
+ import { GridViewIcon } from "./components/Icons/GridViewIcon.mjs";
106
109
  import { HealthIcon } from "./components/Icons/HealthIcon.mjs";
107
110
  import { HeartIcon } from "./components/Icons/HeartIcon.mjs";
108
111
  import { HelpIcon } from "./components/Icons/HelpIcon.mjs";
@@ -115,6 +118,7 @@ import { InfoCircleIcon } from "./components/Icons/InfoCircleIcon.mjs";
115
118
  import { InfoIcon } from "./components/Icons/InfoIcon.mjs";
116
119
  import { LanguageIcon } from "./components/Icons/LanguageIcon.mjs";
117
120
  import { LinkIcon } from "./components/Icons/LinkIcon.mjs";
121
+ import { ListViewIcon } from "./components/Icons/ListViewIcon.mjs";
118
122
  import { LocationIcon } from "./components/Icons/LocationIcon.mjs";
119
123
  import { LockerIcon } from "./components/Icons/LockerIcon.mjs";
120
124
  import { LockerOffIcon } from "./components/Icons/LockerOffIcon.mjs";
@@ -262,6 +266,7 @@ export {
262
266
  ArrowUpIcon,
263
267
  ArrowUpRightIcon,
264
268
  AtSignIcon,
269
+ AudioPlayer,
265
270
  AudioUpload,
266
271
  AutoMessageIcon,
267
272
  Autocomplete,
@@ -375,6 +380,7 @@ export {
375
380
  FlagIcon,
376
381
  FlameIcon,
377
382
  FlashIcon,
383
+ FloatingActionButton,
378
384
  FolderIcon,
379
385
  ForwardIcon,
380
386
  GalleryIcon,
@@ -383,6 +389,7 @@ export {
383
389
  GifIcon,
384
390
  GiftIcon,
385
391
  GoogleIcon,
392
+ GridViewIcon,
386
393
  HealthIcon,
387
394
  HeartIcon,
388
395
  HelpIcon,
@@ -401,6 +408,7 @@ export {
401
408
  LanguageIcon,
402
409
  Link,
403
410
  LinkIcon,
411
+ ListViewIcon,
404
412
  Loader,
405
413
  LocationIcon,
406
414
  LockerIcon,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -136,6 +136,21 @@
136
136
  animation: accordion-collapse 200ms ease-out;
137
137
  }
138
138
 
139
+ /*
140
+ * `dvh` reflects the browser chrome actually on screen (e.g. iOS in-app browsers
141
+ * like Instagram, which report a `vh` taller than what's visible once chrome +
142
+ * home-indicator inset are subtracted). `dvh` support is what's missing on older
143
+ * engines, not `vh`, so the fallback must be declared second: an unsupported unit
144
+ * makes only that declaration invalid, and the browser keeps the prior valid one.
145
+ * This has to stay a single CSS rule with two declarations — two separate Tailwind
146
+ * utility classes (`max-h-[85vh] max-h-[85dvh]`) share one `tailwind-merge` conflict
147
+ * group, so `cn()`/`twMerge` would drop the `vh` one and destroy the fallback.
148
+ */
149
+ @utility dialog-max-h-dynamic {
150
+ max-height: 85vh;
151
+ max-height: 85dvh;
152
+ }
153
+
139
154
  /* Static tokens not in Figma — manually maintained for now */
140
155
  @utility typography-medium-caption-xs {
141
156
  font-size: 0.625rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fanvue/ui",
3
- "version": "3.13.0",
3
+ "version": "3.14.1",
4
4
  "description": "React component library built with Tailwind CSS for Fanvue ecosystem",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",