@photon-ai/pho-ui 2.3.0 → 2.5.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.
Files changed (51) hide show
  1. package/dist/chunks/basic-page-w6ftlo0h.js +1284 -0
  2. package/dist/chunks/basic-page-w6ftlo0h.js.map +1 -0
  3. package/dist/chunks/{dialog-fWS3A3Z-.js → dialog-8SRNi_rv.js} +2 -2
  4. package/dist/chunks/{dialog-fWS3A3Z-.js.map → dialog-8SRNi_rv.js.map} +1 -1
  5. package/dist/chunks/{input-CCd6XK7Z.js → input-DjvZoF8v.js} +45 -41
  6. package/dist/chunks/input-DjvZoF8v.js.map +1 -0
  7. package/dist/chunks/{input-group-5AtpIhC_.js → input-group-fc9_jD4d.js} +44 -38
  8. package/dist/chunks/input-group-fc9_jD4d.js.map +1 -0
  9. package/dist/chunks/kbd-DNI10uy7.js +18 -0
  10. package/dist/chunks/kbd-DNI10uy7.js.map +1 -0
  11. package/dist/chunks/{scroll-area-DkZHKfqH.js → scroll-area-Bu7egArU.js} +40 -26
  12. package/dist/chunks/scroll-area-Bu7egArU.js.map +1 -0
  13. package/dist/chunks/sidebar-DvB-G0BP.js +231 -0
  14. package/dist/chunks/sidebar-DvB-G0BP.js.map +1 -0
  15. package/dist/chunks/toast-DoXaRPIZ.js +167 -0
  16. package/dist/chunks/toast-DoXaRPIZ.js.map +1 -0
  17. package/dist/components/command.js +125 -172
  18. package/dist/components/command.js.map +1 -1
  19. package/dist/components/dialog.js +1 -1
  20. package/dist/components/input-group.js +1 -1
  21. package/dist/components/input.js +1 -1
  22. package/dist/components/kbd.js +2 -15
  23. package/dist/components/phone-input.js +1 -1
  24. package/dist/components/scroll-area.js +3 -2
  25. package/dist/components/toast.js +3 -57
  26. package/dist/index.js +65 -58
  27. package/dist/primitives.js +1 -1
  28. package/dist/sections/basic-page.js +15 -8
  29. package/dist/sections/sidebar.js +1 -1
  30. package/dist/src/components/command/command.d.ts +5 -4
  31. package/dist/src/components/input/input.d.ts +10 -1
  32. package/dist/src/components/input-group/input-group.d.ts +9 -2
  33. package/dist/src/components/scroll-area/index.d.ts +1 -0
  34. package/dist/src/components/scroll-area/scroll-area-edges.d.ts +6 -0
  35. package/dist/src/components/scroll-area/scroll-area.d.ts +15 -2
  36. package/dist/src/components/toast/index.d.ts +1 -1
  37. package/dist/src/components/toast/toast.d.ts +22 -7
  38. package/dist/src/sections/basic-page/basic-page.d.ts +140 -3
  39. package/dist/src/sections/basic-page/index.d.ts +1 -1
  40. package/dist/src/sections/sidebar/sidebar.d.ts +33 -3
  41. package/package.json +1 -1
  42. package/src/styles/pho.css +19 -0
  43. package/dist/chunks/basic-page-BkLG3Ybg.js +0 -942
  44. package/dist/chunks/basic-page-BkLG3Ybg.js.map +0 -1
  45. package/dist/chunks/input-CCd6XK7Z.js.map +0 -1
  46. package/dist/chunks/input-group-5AtpIhC_.js.map +0 -1
  47. package/dist/chunks/scroll-area-DkZHKfqH.js.map +0 -1
  48. package/dist/chunks/sidebar-Dt1F_CEP.js +0 -186
  49. package/dist/chunks/sidebar-Dt1F_CEP.js.map +0 -1
  50. package/dist/components/kbd.js.map +0 -1
  51. package/dist/components/toast.js.map +0 -1
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { t } from "./cn-ChIgwEl3.js";
3
- import { t as m } from "./createReactComponent-BqCmnqfX.js";
4
- import { t as A } from "./IconAlertCircleFilled-VxKU8TYK.js";
5
- import { useState as S } from "react";
2
+ import { t as s } from "./cn-ChIgwEl3.js";
3
+ import { t as f } from "./createReactComponent-BqCmnqfX.js";
4
+ import { t as D } from "./IconAlertCircleFilled-VxKU8TYK.js";
5
+ import { useState as R } from "react";
6
6
  import { jsx as e, jsxs as i } from "react/jsx-runtime";
7
- import { Field as r } from "@base-ui/react/field";
8
- var D = [
7
+ import { Field as o } from "@base-ui/react/field";
8
+ var k = [
9
9
  ["path", {
10
10
  d: "M10.585 10.587a2 2 0 0 0 2.829 2.828",
11
11
  key: "svg-0"
@@ -18,88 +18,92 @@ var D = [
18
18
  d: "M3 3l18 18",
19
19
  key: "svg-2"
20
20
  }]
21
- ], R = m("outline", "eye-off", "EyeOff", D), k = [["path", {
21
+ ], C = f("outline", "eye-off", "EyeOff", k), L = [["path", {
22
22
  d: "M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0",
23
23
  key: "svg-0"
24
24
  }], ["path", {
25
25
  d: "M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6",
26
26
  key: "svg-1"
27
- }]], C = m("outline", "eye", "Eye", k), L = {
27
+ }]], O = f("outline", "eye", "Eye", L), M = {
28
28
  sm: "px-2.5 py-1.5 text-md md:text-base",
29
29
  md: "px-2.5 py-1.5 text-md",
30
30
  lg: "px-3 py-2 text-md"
31
- }, O = {
31
+ }, T = {
32
32
  sm: "pl-8.5",
33
33
  md: "pl-9.5",
34
34
  lg: "pl-10"
35
- }, M = {
35
+ }, P = {
36
36
  sm: "left-3 size-4",
37
37
  md: "left-3 size-5",
38
38
  lg: "left-3.5 size-5"
39
- }, P = {
39
+ }, Z = {
40
40
  sm: "rounded-base",
41
41
  md: "rounded-base",
42
42
  lg: "squircle rounded-base"
43
- }, T = {
43
+ }, j = {
44
44
  sm: "",
45
45
  md: "",
46
- lg: t("isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']", "before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]", "data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]", "has-[input:focus-visible]:before:inset-0.5 has-[input:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]", "before:transition-[inset,border-radius] before:duration-100 before:ease-linear")
47
- }, Z = {
46
+ lg: s("isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']", "before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]", "data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]", "has-[input:focus-visible]:before:inset-0.5 has-[input:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]", "before:transition-[inset,border-radius] before:duration-100 before:ease-linear")
47
+ }, F = {
48
48
  sm: "autofill:rounded-[calc(var(--radius-base)-2px)]",
49
49
  md: "autofill:rounded-[calc(var(--radius-base)-2px)]",
50
50
  lg: "autofill:rounded-[calc(var(--radius-base)-2px)]"
51
51
  };
52
- function j({ label: l, hint: d, size: a = "md", invalid: o, icon: n, labelTrailing: f, disabled: b, required: c, type: p = "text", className: h, wrapperClassName: v, id: x, ref: g, ...y }) {
53
- const [u, N] = S(!1), s = p === "password", I = s && u ? "text" : p, _ = !!n, E = s || o;
54
- return /* @__PURE__ */ i(r.Root, {
55
- disabled: b,
52
+ function G({ label: l, hint: d, size: a = "md", invalid: r, icon: n, labelTrailing: b, trailing: c, disabled: h, required: p, type: u = "text", className: x, wrapperClassName: v, id: g, ref: y, ...N }) {
53
+ const [m, I] = R(!1), t = u === "password", _ = t && m ? "text" : u, E = !!n, w = t || r, A = c != null && !r && !t;
54
+ return /* @__PURE__ */ i(o.Root, {
55
+ disabled: h,
56
56
  className: "group/field flex w-full flex-col gap-1.5",
57
57
  children: [
58
58
  l && /* @__PURE__ */ i("div", {
59
59
  className: "flex items-center justify-between gap-2",
60
- children: [/* @__PURE__ */ i(r.Label, {
60
+ children: [/* @__PURE__ */ i(o.Label, {
61
61
  className: "text-pho-secondary flex cursor-default items-center gap-0.5 text-base font-medium",
62
- children: [l, c && /* @__PURE__ */ e("span", {
62
+ children: [l, p && /* @__PURE__ */ e("span", {
63
63
  className: "text-pho-brand",
64
64
  children: "*"
65
65
  })]
66
- }), f]
66
+ }), b]
67
67
  }),
68
68
  /* @__PURE__ */ i("div", {
69
- "data-invalid": o || void 0,
70
- className: t("bg-pho-primary ring-pho-primary relative flex w-full items-center p-0.5 ring-1 transition-shadow duration-100 ease-linear ring-inset", P[a], T[a], "has-[input:focus-visible]:ring-pho-brand has-[input:focus-visible]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[input:focus-visible]:ring-pho-error data-[invalid]:ring-[1.5px]", "group-data-[disabled]/field:cursor-not-allowed group-data-[disabled]/field:opacity-50", v),
69
+ "data-invalid": r || void 0,
70
+ className: s("bg-pho-primary ring-pho-primary relative flex w-full items-center p-0.5 ring-1 transition-shadow duration-100 ease-linear ring-inset", Z[a], j[a], "has-[input:focus-visible]:ring-pho-brand has-[input:focus-visible]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[input:focus-visible]:ring-pho-error data-[invalid]:ring-[1.5px]", "group-data-[disabled]/field:cursor-not-allowed group-data-[disabled]/field:opacity-50", v),
71
71
  children: [
72
- n && /* @__PURE__ */ e(n, { className: t("text-pho-secondary pointer-events-none absolute", M[a]) }),
73
- /* @__PURE__ */ e(r.Control, {
74
- ref: g,
75
- id: x,
76
- type: I,
77
- required: c,
78
- ...y,
79
- className: t("text-pho-primary placeholder:text-pho-placeholder m-0 w-full bg-transparent outline-none disabled:cursor-not-allowed", L[a], Z[a], _ && O[a], E && "pr-8.5", h)
72
+ n && /* @__PURE__ */ e(n, { className: s("text-pho-secondary pointer-events-none absolute", P[a]) }),
73
+ /* @__PURE__ */ e(o.Control, {
74
+ ref: y,
75
+ id: g,
76
+ type: _,
77
+ required: p,
78
+ ...N,
79
+ className: s("text-pho-primary placeholder:text-pho-placeholder m-0 w-full bg-transparent outline-none disabled:cursor-not-allowed", M[a], F[a], E && T[a], w && "pr-8.5", x)
80
+ }),
81
+ r && !t && /* @__PURE__ */ e(D, { className: "text-pho-error pointer-events-none absolute right-3 size-4" }),
82
+ A && /* @__PURE__ */ e("span", {
83
+ className: "text-pho-description pointer-events-none absolute right-3 flex items-center",
84
+ children: c
80
85
  }),
81
- o && !s && /* @__PURE__ */ e(A, { className: "text-pho-error pointer-events-none absolute right-3 size-4" }),
82
- s && /* @__PURE__ */ e("button", {
86
+ t && /* @__PURE__ */ e("button", {
83
87
  type: "button",
84
88
  tabIndex: -1,
85
89
  "aria-label": "Toggle password visibility",
86
- onClick: () => N((w) => !w),
90
+ onClick: () => I((S) => !S),
87
91
  className: "text-pho-secondary hover:text-pho-secondary-hover absolute right-3 flex cursor-pointer items-center transition duration-100 ease-linear focus:outline-none",
88
- children: u ? /* @__PURE__ */ e(R, { className: "size-4" }) : /* @__PURE__ */ e(C, { className: "size-4" })
92
+ children: m ? /* @__PURE__ */ e(C, { className: "size-4" }) : /* @__PURE__ */ e(O, { className: "size-4" })
89
93
  })
90
94
  ]
91
95
  }),
92
- d && /* @__PURE__ */ e(r.Description, {
93
- className: t("text-pho-description text-base", o && "text-pho-error"),
96
+ d && /* @__PURE__ */ e(o.Description, {
97
+ className: s("text-pho-description text-base", r && "text-pho-error"),
94
98
  children: d
95
99
  }),
96
- /* @__PURE__ */ e(r.Error, { className: "text-pho-error text-base" })
100
+ /* @__PURE__ */ e(o.Error, { className: "text-pho-error text-base" })
97
101
  ]
98
102
  });
99
103
  }
100
- j.displayName = "Input";
104
+ G.displayName = "Input";
101
105
  export {
102
- j as t
106
+ G as t
103
107
  };
104
108
 
105
- //# sourceMappingURL=input-CCd6XK7Z.js.map
109
+ //# sourceMappingURL=input-DjvZoF8v.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-DjvZoF8v.js","names":["__iconNode"],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconEyeOff.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.46.0_react@19.2.8/node_modules/@tabler/icons-react/dist/esm/icons/IconEye.mjs","../../src/components/input/input.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M10.585 10.587a2 2 0 0 0 2.829 2.828\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M16.681 16.673a8.717 8.717 0 0 1 -4.681 1.327c-3.6 0 -6.6 -2 -9 -6c1.272 -2.12 2.712 -3.678 4.32 -4.674m2.86 -1.146a9.055 9.055 0 0 1 1.82 -.18c3.6 0 6.6 2 9 6c-.666 1.11 -1.379 2.067 -2.138 2.87\", \"key\": \"svg-1\" }], [\"path\", { \"d\": \"M3 3l18 18\", \"key\": \"svg-2\" }]];\nconst IconEyeOff = createReactComponent(\"outline\", \"eye-off\", \"EyeOff\", __iconNode);\n\nexport { __iconNode, IconEyeOff as default };\n//# sourceMappingURL=IconEyeOff.mjs.map\n","/**\n * @license @tabler/icons-react v3.46.0 - MIT\n *\n * This source code is licensed under the MIT license.\n * See the LICENSE file in the root directory of this source tree.\n */\n\nimport createReactComponent from '../createReactComponent.mjs';\n\nconst __iconNode = [[\"path\", { \"d\": \"M10 12a2 2 0 1 0 4 0a2 2 0 0 0 -4 0\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M21 12c-2.4 4 -5.4 6 -9 6c-3.6 0 -6.6 -2 -9 -6c2.4 -4 5.4 -6 9 -6c3.6 0 6.6 2 9 6\", \"key\": \"svg-1\" }]];\nconst IconEye = createReactComponent(\"outline\", \"eye\", \"Eye\", __iconNode);\n\nexport { __iconNode, IconEye as default };\n//# sourceMappingURL=IconEye.mjs.map\n","import {\n useState,\n type ComponentType,\n type InputHTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport {\n IconAlertCircleFilled,\n IconEye,\n IconEyeOff,\n} from \"@tabler/icons-react\";\nimport { Field } from \"@base-ui/react/field\";\nimport { cn } from \"../../utils/cn\";\n\nexport type InputSize = \"sm\" | \"md\" | \"lg\";\n\n// Control padding per size. The wrapper carries a 2px inset (`p-0.5`) so the\n// control's border box — and the browser's autofill fill it paints — sits\n// inside the ring instead of over it; these paddings are the documented\n// effective padding (sm/md 12/8px, lg 14/10px) minus that 2px inset, so the\n// text position and total field height are unchanged.\nconst SIZE_PAD: Record<InputSize, string> = {\n // 16px on small viewports — iOS zooms into any focused field under 16px.\n sm: \"px-2.5 py-1.5 text-md md:text-base\",\n md: \"px-2.5 py-1.5 text-md\",\n lg: \"px-3 py-2 text-md\",\n};\n// Effective icon clearance minus the wrapper's 2px inset.\nconst LEADING_PAD: Record<InputSize, string> = {\n sm: \"pl-8.5\",\n md: \"pl-9.5\",\n lg: \"pl-10\",\n};\n// Icons are absolutely positioned, so they're placed from the wrapper's\n// padding edge — which, with no border, is the visible field edge; the\n// wrapper's `p-0.5` does not offset them.\nconst LEADING_ICON_POS: Record<InputSize, string> = {\n sm: \"left-3 size-4\",\n md: \"left-3 size-5\",\n lg: \"left-3.5 size-5\",\n};\n// Wrapper corner radius — `rounded-base` (12px) at every size; `lg` also gets\n// `squircle` superellipse smoothing (the ring is a box-shadow on this wrapper,\n// so it follows the border-radius automatically).\nconst SIZE_RADIUS: Record<InputSize, string> = {\n sm: \"rounded-base\",\n md: \"rounded-base\",\n lg: \"squircle rounded-base\",\n};\n// Concentric inner-curve cover, `lg` only. Nested corner radii must follow\n// innerRadius = outerRadius − inset, so the hairline's inner edge at a 12px\n// outer radius is 11px at rest (ring-1) and 10px when focused (ring-2).\n// Browsers derive that automatically for plain round corners, but with\n// `corner-shape: superellipse(...)` shipping engines still scale (rather than\n// offset) the inner shadow curve, which renders the ring visibly thicker at\n// the four corners (see w3c/csswg-drafts#12961). This ::before repaints the\n// field surface over everything inside the correct concentric curve — a round\n// radius is the right parallel-offset approximation of the mild superellipse —\n// so the ring reads as a uniform hairline in every engine. `isolate` +\n// `-z-10` slot the cover above the wrapper's background/ring but below the\n// control's text, icons, and autofill fill. `sm`/`md` have no corner-shape,\n// so the native (already concentric) rendering is kept there.\n// The ::before is absolutely positioned, so its inset is measured from the\n// wrapper's border box (border 0): 1px in at rest, 2px in when focused.\nconst SIZE_RING_COVER: Record<InputSize, string> = {\n sm: \"\",\n md: \"\",\n lg: cn(\n \"isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']\",\n \"before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]\",\n // Invalid sits between rest (1px) and focus (2px) — keep the cover concentric.\n \"data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]\",\n \"has-[input:focus-visible]:before:inset-0.5 has-[input:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]\",\n \"before:transition-[inset,border-radius] before:duration-100 before:ease-linear\",\n ),\n};\n// The control's border box sits 2px inside the wrapper (its `p-0.5` inset),\n// clear of both ring widths, so the browser's autofill fill paints inside the\n// hairline instead of over it. Rounding follows inner = outer − gap, derived\n// from the token with calc. Round (not squircle) — the parallel offset of\n// the wrapper's mild superellipse is effectively circular.\nconst SIZE_AUTOFILL_RADIUS: Record<InputSize, string> = {\n sm: \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n md: \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n lg: \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n};\n\nexport interface InputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"size\"\n> {\n /** Field label (associated for accessibility). */\n label?: ReactNode;\n /** Helper text below the field; rendered in the error color when `invalid`. */\n hint?: ReactNode;\n /** Size variant. @default \"md\" */\n size?: InputSize;\n /** Render the invalid state (ring + icon + error-colored hint). */\n invalid?: boolean;\n /** Leading icon component (e.g. a Tabler icon). */\n icon?: ComponentType<{ className?: string }>;\n /** Node rendered at the right edge of the label row (e.g. \"Forgot?\"). */\n labelTrailing?: ReactNode;\n /**\n * Quiet word or glyph inside the field at its trailing edge (an\n * auto-save's `Saved`, a live character count). Non-interactive overlay:\n * it never takes the pointer, the invalid icon takes the corner over\n * it, and the input reserves nothing — content that shows only\n * sometimes brings its own surface wash (as `Saved` does) so long text\n * fades beneath it instead of colliding.\n */\n trailing?: ReactNode;\n /** Class applied to the input wrapper. */\n wrapperClassName?: string;\n ref?: Ref<HTMLInputElement>;\n}\n\n/**\n * Text input built on Base UI `Field` (label/description a11y wiring) with\n * Pho's bordered surface, leading icon, invalid state, and a built-in password\n * reveal toggle.\n */\nexport function Input({\n label,\n hint,\n size = \"md\",\n invalid,\n icon: Icon,\n labelTrailing,\n trailing,\n disabled,\n required,\n type = \"text\",\n className,\n wrapperClassName,\n id,\n ref,\n ...props\n}: InputProps) {\n const [reveal, setReveal] = useState(false);\n const isPassword = type === \"password\";\n const resolvedType = isPassword && reveal ? \"text\" : type;\n const hasLeadingIcon = Boolean(Icon);\n const hasTrailingIcon = isPassword || invalid;\n const hasTrailing = trailing != null && !invalid && !isPassword;\n\n return (\n <Field.Root\n disabled={disabled}\n className=\"group/field flex w-full flex-col gap-1.5\"\n >\n {label && (\n <div className=\"flex items-center justify-between gap-2\">\n <Field.Label className=\"text-pho-secondary flex cursor-default items-center gap-0.5 text-base font-medium\">\n {label}\n {required && <span className=\"text-pho-brand\">*</span>}\n </Field.Label>\n {labelTrailing}\n </div>\n )}\n\n <div\n data-invalid={invalid || undefined}\n className={cn(\n // Flat field surface — hairline ring only; elevation shadows are\n // reserved for floating layers (menus/popovers/dialogs). `p-0.5`\n // insets the control 2px (the focused ring width) so its background\n // and the browser's autofill fill nest inside the ring; the size\n // maps compensate so effective padding/height are unchanged.\n \"bg-pho-primary ring-pho-primary relative flex w-full items-center p-0.5 ring-1 transition-shadow duration-100 ease-linear ring-inset\",\n SIZE_RADIUS[size],\n SIZE_RING_COVER[size],\n \"has-[input:focus-visible]:ring-pho-brand has-[input:focus-visible]:ring-2\",\n // Invalid is mid-weight: thicker than the 1px rest hairline, thinner\n // than the 2px focus ring. Rest uses the subtle error ink; focus\n // steps up to the solid error (same hue, full opacity).\n \"data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[input:focus-visible]:ring-pho-error data-[invalid]:ring-[1.5px]\",\n \"group-data-[disabled]/field:cursor-not-allowed group-data-[disabled]/field:opacity-50\",\n wrapperClassName,\n )}\n >\n {Icon && (\n <Icon\n className={cn(\n \"text-pho-secondary pointer-events-none absolute\",\n LEADING_ICON_POS[size],\n )}\n />\n )}\n\n <Field.Control\n ref={ref}\n id={id}\n type={resolvedType}\n required={required}\n {...props}\n className={cn(\n \"text-pho-primary placeholder:text-pho-placeholder m-0 w-full bg-transparent outline-none disabled:cursor-not-allowed\",\n SIZE_PAD[size],\n SIZE_AUTOFILL_RADIUS[size],\n hasLeadingIcon && LEADING_PAD[size],\n // 34px + the wrapper's 2px inset = the previous 36px clearance.\n hasTrailingIcon && \"pr-8.5\",\n className,\n )}\n />\n\n {invalid && !isPassword && (\n <IconAlertCircleFilled className=\"text-pho-error pointer-events-none absolute right-3 size-4\" />\n )}\n {hasTrailing && (\n <span className=\"text-pho-description pointer-events-none absolute right-3 flex items-center\">\n {trailing}\n </span>\n )}\n\n {isPassword && (\n <button\n type=\"button\"\n // Reachable by click but skipped in tab order so Tab moves field→field.\n tabIndex={-1}\n aria-label=\"Toggle password visibility\"\n onClick={() => setReveal((v) => !v)}\n className=\"text-pho-secondary hover:text-pho-secondary-hover absolute right-3 flex cursor-pointer items-center transition duration-100 ease-linear focus:outline-none\"\n >\n {reveal ? (\n <IconEyeOff className=\"size-4\" />\n ) : (\n <IconEye className=\"size-4\" />\n )}\n </button>\n )}\n </div>\n\n {hint && (\n <Field.Description\n className={cn(\n \"text-pho-description text-base\",\n invalid && \"text-pho-error\",\n )}\n >\n {hint}\n </Field.Description>\n )}\n {/* Validation and server errors routed here by Field / Form. */}\n <Field.Error className=\"text-pho-error text-base\" />\n </Field.Root>\n );\n}\n\nInput.displayName = \"Input\";\n"],"x_google_ignoreList":[0,1],"mappings":";;;;;;;AASA,IAAMA,IAAa;AAAA,EAAC,CAAC,QAAQ;AAAA,IAAE,GAAK;AAAA,IAAwC,KAAO;AAAA,EAAQ,CAAC;AAAA,EAAG,CAAC,QAAQ;AAAA,IAAE,GAAK;AAAA,IAAuM,KAAO;AAAA,EAAQ,CAAC;AAAA,EAAG,CAAC,QAAQ;AAAA,IAAE,GAAK;AAAA,IAAc,KAAO;AAAA,EAAQ,CAAC;AAAC,GAClX,IAAa,EAAqB,WAAW,WAAW,UAAUA,CAAU,GCD5E,IAAa,CAAC,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAuC,KAAO;AAAQ,CAAC,GAAG,CAAC,QAAQ;AAAA,EAAE,GAAK;AAAA,EAAqF,KAAO;AAAQ,CAAC,CAAC,GAC9M,IAAU,EAAqB,WAAW,OAAO,OAAO,CAAU,GCYlE,IAAsC;AAAA,EAE1C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAEM,IAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAIM,IAA8C;AAAA,EAClD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAIM,IAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAgBM,IAA6C;AAAA,EACjD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,EACF,uFACA,uFAEA,sGACA,sHACA,gFACF;AACF,GAMM,IAAkD;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAqCA,SAAgB,EAAM,EACpB,OAAA,GACA,MAAA,GACA,MAAA,IAAO,MACP,SAAA,GACA,MAAM,GACN,eAAA,GACA,UAAA,GACA,UAAA,GACA,UAAA,GACA,MAAA,IAAO,QACP,WAAA,GACA,kBAAA,GACA,IAAA,GACA,KAAA,GACA,GAAG,EAAA,GACU;AACb,QAAM,CAAC,GAAQ,CAAA,IAAa,EAAS,EAAK,GACpC,IAAa,MAAS,YACtB,IAAe,KAAc,IAAS,SAAS,GAC/C,IAAiB,EAAQ,GACzB,IAAkB,KAAc,GAChC,IAAc,KAAY,QAAQ,CAAC,KAAW,CAAC;AAErD,SACE,gBAAA,EAAC,EAAM,MAAP;AAAA,IACY,UAAA;AAAA,IACV,WAAU;AAAA,IAFZ,UAAA;AAAA,MAIG,KACC,gBAAA,EAAC,OAAD;AAAA,QAAK,WAAU;AAAA,QAAf,UAAA,CACE,gBAAA,EAAC,EAAM,OAAP;AAAA,UAAa,WAAU;AAAA,UAAvB,UAAA,CACG,GACA,KAAY,gBAAA,EAAC,QAAD;AAAA,YAAM,WAAU;AAAA,YAAiB,UAAA;AAAA,UAAO,CAAA,CAC1C;AAAA,QACZ,CAAA,GAAA,CACE;AAAA;MAGP,gBAAA,EAAC,OAAD;AAAA,QACE,gBAAc,KAAW;AAAA,QACzB,WAAW,EAMT,wIACA,EAAY,CAAA,GACZ,EAAgB,CAAA,GAChB,6EAIA,4HACA,yFACA,CACF;AAAA,QAlBF,UAAA;AAAA,UAoBG,KACC,gBAAA,EAAC,GAAD,EACE,WAAW,EACT,mDACA,EAAiB,CAAA,CACnB,EACD,CAAA;AAAA,UAGH,gBAAA,EAAC,EAAM,SAAP;AAAA,YACO,KAAA;AAAA,YACD,IAAA;AAAA,YACJ,MAAM;AAAA,YACI,UAAA;AAAA,YACV,GAAI;AAAA,YACJ,WAAW,EACT,wHACA,EAAS,CAAA,GACT,EAAqB,CAAA,GACrB,KAAkB,EAAY,CAAA,GAE9B,KAAmB,UACnB,CACF;AAAA,UACD,CAAA;AAAA,UAEA,KAAW,CAAC,KACX,gBAAA,EAAC,GAAD,EAAuB,WAAU,6DAA8D,CAAA;AAAA,UAEhG,KACC,gBAAA,EAAC,QAAD;AAAA,YAAM,WAAU;AAAA,YACb,UAAA;AAAA,UACG,CAAA;AAAA,UAGP,KACC,gBAAA,EAAC,UAAD;AAAA,YACE,MAAK;AAAA,YAEL,UAAU;AAAA,YACV,cAAW;AAAA,YACX,SAAA,MAAe,EAAA,CAAW,MAAM,CAAC,CAAC;AAAA,YAClC,WAAU;AAAA,YAET,UAAA,IACC,gBAAA,EAAC,GAAD,EAAY,WAAU,SAAU,CAAA,IAEhC,gBAAA,EAAC,GAAD,EAAS,WAAU,SAAU,CAAA;AAAA,UAEzB,CAAA;AAAA,QAEP;AAAA;MAEJ,KACC,gBAAA,EAAC,EAAM,aAAP;AAAA,QACE,WAAW,EACT,kCACA,KAAW,gBACb;AAAA,QAEC,UAAA;AAAA,MACgB,CAAA;AAAA,MAGrB,gBAAA,EAAC,EAAM,OAAP,EAAa,WAAU,2BAA4B,CAAA;AAAA,IACzC;AAAA;AAEhB;AAEA,EAAM,cAAc"}
@@ -1,17 +1,17 @@
1
1
  "use client";
2
2
  import { t as i } from "./cn-ChIgwEl3.js";
3
- import { createContext as b, useContext as p } from "react";
4
- import { jsx as n } from "react/jsx-runtime";
5
- import { Field as f } from "@base-ui/react/field";
6
- import { Separator as m } from "@base-ui/react/separator";
7
- var l = b({
3
+ import { createContext as b, useContext as c } from "react";
4
+ import { jsx as t, jsxs as f } from "react/jsx-runtime";
5
+ import { Field as m } from "@base-ui/react/field";
6
+ import { Separator as v } from "@base-ui/react/separator";
7
+ var u = b({
8
8
  size: "md",
9
9
  invalid: !1
10
- }), v = {
10
+ }), h = {
11
11
  sm: "gap-2 px-3 text-md md:text-base",
12
12
  md: "gap-2 px-3 text-md",
13
13
  lg: "gap-2 px-3.5 text-md"
14
- }, h = {
14
+ }, x = {
15
15
  sm: "my-0.5 py-1.5",
16
16
  md: "my-0.5 py-1.5",
17
17
  lg: "my-0.5 py-2"
@@ -19,67 +19,73 @@ var l = b({
19
19
  sm: "[&_svg]:size-4",
20
20
  md: "[&_svg]:size-5",
21
21
  lg: "[&_svg]:size-5"
22
- }, x = {
22
+ }, y = {
23
23
  sm: "rounded-base",
24
24
  md: "rounded-base",
25
25
  lg: "squircle rounded-base"
26
- }, y = {
26
+ }, I = {
27
27
  sm: "",
28
28
  md: "",
29
29
  lg: i("isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']", "before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]", "data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]", "has-[:focus-visible]:before:inset-0.5 has-[:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]", "before:transition-[inset,border-radius] before:duration-100 before:ease-linear")
30
30
  };
31
- function I({ size: e = "md", invalid: r, className: a, onPointerDown: o, ref: s, ...u }) {
32
- const c = (t) => {
33
- if (o?.(t), t.defaultPrevented || t.target.closest("input, button, a, select, textarea, label")) return;
34
- const d = t.currentTarget.querySelector("input");
35
- !d || d.disabled || (t.preventDefault(), d.focus());
31
+ function w({ size: e = "md", invalid: r, className: a, onPointerDown: o, ref: l, ...d }) {
32
+ const s = (n) => {
33
+ if (o?.(n), n.defaultPrevented || n.target.closest("input, button, a, select, textarea, label")) return;
34
+ const p = n.currentTarget.querySelector("input");
35
+ !p || p.disabled || (n.preventDefault(), p.focus());
36
36
  };
37
- return /* @__PURE__ */ n(l.Provider, {
37
+ return /* @__PURE__ */ t(u.Provider, {
38
38
  value: {
39
39
  size: e,
40
40
  invalid: !!r
41
41
  },
42
- children: /* @__PURE__ */ n("div", {
43
- ref: s,
42
+ children: /* @__PURE__ */ t("div", {
43
+ ref: l,
44
44
  "data-invalid": r || void 0,
45
- onPointerDown: c,
46
- className: i("bg-pho-primary ring-pho-primary relative flex w-full cursor-text items-center ring-1 transition-shadow duration-100 ease-linear ring-inset", v[e], x[e], y[e], "has-[:focus-visible]:ring-pho-brand has-[:focus-visible]:ring-2", "has-[button[data-popup-open]]:ring-pho-brand has-[button[data-popup-open]]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[:focus-visible]:ring-pho-error data-[invalid]:has-[button[data-popup-open]]:ring-pho-error data-[invalid]:ring-[1.5px]", "has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50", a),
47
- ...u
45
+ onPointerDown: s,
46
+ className: i("bg-pho-primary ring-pho-primary relative flex w-full cursor-text items-center ring-1 transition-shadow duration-100 ease-linear ring-inset", h[e], y[e], I[e], "has-[:focus-visible]:ring-pho-brand has-[:focus-visible]:ring-2", "has-[button[data-popup-open]]:ring-pho-brand has-[button[data-popup-open]]:ring-2", "data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[:focus-visible]:ring-pho-error data-[invalid]:has-[button[data-popup-open]]:ring-pho-error data-[invalid]:ring-[1.5px]", "has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50", a),
47
+ ...d
48
48
  })
49
49
  });
50
50
  }
51
- function _({ className: e, ref: r, ...a }) {
52
- const { size: o, invalid: s } = p(l);
53
- return /* @__PURE__ */ n(f.Control, {
54
- ref: r,
55
- "aria-invalid": s || void 0,
56
- ...a,
57
- className: i("text-pho-primary placeholder:text-pho-placeholder m-0 w-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed", "autofill:rounded-[calc(var(--radius-base)-2px)]", h[o], e)
51
+ function _({ className: e, trailing: r, ref: a, ...o }) {
52
+ const { size: l, invalid: d } = c(u), s = /* @__PURE__ */ t(m.Control, {
53
+ ref: a,
54
+ "aria-invalid": d || void 0,
55
+ ...o,
56
+ className: i("text-pho-primary placeholder:text-pho-placeholder m-0 w-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed", "autofill:rounded-[calc(var(--radius-base)-2px)]", x[l], e)
57
+ });
58
+ return r == null ? s : /* @__PURE__ */ f("span", {
59
+ className: "relative flex w-full min-w-0 flex-1 items-center",
60
+ children: [s, /* @__PURE__ */ t("span", {
61
+ className: "text-pho-description pointer-events-none absolute right-2 flex items-center",
62
+ children: r
63
+ })]
58
64
  });
59
65
  }
60
- function S({ className: e, ref: r, ...a }) {
61
- const { size: o } = p(l);
62
- return /* @__PURE__ */ n("div", {
66
+ function N({ className: e, ref: r, ...a }) {
67
+ const { size: o } = c(u);
68
+ return /* @__PURE__ */ t("div", {
63
69
  ref: r,
64
70
  className: i("text-pho-secondary flex shrink-0 items-center gap-1 select-none", "[&_svg]:shrink-0", g[o], e),
65
71
  ...a
66
72
  });
67
73
  }
68
- function w({ className: e, orientation: r = "vertical", ...a }) {
69
- return /* @__PURE__ */ n(m, {
74
+ function S({ className: e, orientation: r = "vertical", ...a }) {
75
+ return /* @__PURE__ */ t(v, {
70
76
  orientation: r,
71
77
  className: i("my-0.5 w-px shrink-0 self-stretch bg-[var(--border-color-pho-secondary)]", e),
72
78
  ...a
73
79
  });
74
80
  }
75
- var R = {
76
- Root: I,
81
+ var G = {
82
+ Root: w,
77
83
  Input: _,
78
- Addon: S,
79
- Separator: w
84
+ Addon: N,
85
+ Separator: S
80
86
  };
81
87
  export {
82
- R as t
88
+ G as t
83
89
  };
84
90
 
85
- //# sourceMappingURL=input-group-5AtpIhC_.js.map
91
+ //# sourceMappingURL=input-group-fc9_jD4d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-group-fc9_jD4d.js","names":[],"sources":["../../src/components/input-group/input-group.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n type ComponentProps,\n type ElementType,\n type HTMLAttributes,\n type InputHTMLAttributes,\n type PointerEvent,\n type Ref,\n type ReactNode,\n} from \"react\";\nimport { Field } from \"@base-ui/react/field\";\nimport { Separator as BaseSeparator } from \"@base-ui/react/separator\";\nimport { cn } from \"../../utils/cn\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\nexport type InputGroupSize = \"sm\" | \"md\" | \"lg\";\n\n/** Lets `Addon` and the control read the Root's size/invalid state. */\nconst InputGroupContext = createContext<{\n size: InputGroupSize;\n invalid: boolean;\n}>({ size: \"md\", invalid: false });\n\n// Mirrors Input's size math (see input.tsx): the group surface carries the\n// horizontal padding and text scale, the control carries the vertical\n// padding. Effective metrics — sm/md 12/8px, lg 14/10px — land the same\n// 36/40/44px field heights, so grouped and plain inputs align in a form.\nconst SIZE_ROOT: Record<InputGroupSize, string> = {\n // 16px on small viewports — iOS zooms into any focused field under 16px.\n sm: \"gap-2 px-3 text-md md:text-base\",\n md: \"gap-2 px-3 text-md\",\n lg: \"gap-2 px-3.5 text-md\",\n};\n// `my-0.5` insets the control 2px (the focused ring width) so the browser's\n// autofill fill nests inside the ring; `py` makes up the rest of the height.\nconst SIZE_CONTROL: Record<InputGroupSize, string> = {\n sm: \"my-0.5 py-1.5\",\n md: \"my-0.5 py-1.5\",\n lg: \"my-0.5 py-2\",\n};\n// In-field icon size per group size — same scale as Input's leading icon.\nconst SIZE_ADDON_ICON: Record<InputGroupSize, string> = {\n sm: \"[&_svg]:size-4\",\n md: \"[&_svg]:size-5\",\n lg: \"[&_svg]:size-5\",\n};\n// `lg` gets the squircle treatment like Input; sm/md stay plain round.\nconst SIZE_RADIUS: Record<InputGroupSize, string> = {\n sm: \"rounded-base\",\n md: \"rounded-base\",\n lg: \"squircle rounded-base\",\n};\n// Concentric inner-curve cover for the `lg` squircle — same engine workaround\n// as Input (see input.tsx `SIZE_RING_COVER` for the full story).\nconst SIZE_RING_COVER: Record<InputGroupSize, string> = {\n sm: \"\",\n md: \"\",\n lg: cn(\n \"isolate before:pointer-events-none before:absolute before:-z-10 before:content-['']\",\n \"before:bg-pho-primary before:inset-px before:rounded-[calc(var(--radius-base)-1px)]\",\n // Invalid sits between rest (1px) and focus (2px) — keep the cover concentric.\n \"data-[invalid]:before:inset-[1.5px] data-[invalid]:before:rounded-[calc(var(--radius-base)-1.5px)]\",\n \"has-[:focus-visible]:before:inset-0.5 has-[:focus-visible]:before:rounded-[calc(var(--radius-base)-2px)]\",\n \"before:transition-[inset,border-radius] before:duration-100 before:ease-linear\",\n ),\n};\n\nexport interface InputGroupRootProps extends HTMLAttributes<HTMLDivElement> {\n /** Size variant, matching Input's scale. @default \"md\" */\n size?: InputGroupSize;\n /** Render the invalid state (error ring; `aria-invalid` on the control). */\n invalid?: boolean;\n ref?: Ref<HTMLDivElement>;\n}\n\n/**\n * The shared field surface: one hairline ring around the control and its\n * addons, with the same focus / invalid / disabled treatment as `Input`.\n * Clicking anywhere on the surface (addon text, icons, blank space) focuses\n * the control, so the whole group reads as a single field.\n */\nfunction Root({\n size = \"md\",\n invalid,\n className,\n onPointerDown,\n ref,\n ...props\n}: InputGroupRootProps) {\n const handlePointerDown = (event: PointerEvent<HTMLDivElement>) => {\n onPointerDown?.(event);\n if (event.defaultPrevented) return;\n // Interactive children keep their own pointer behavior (and the control\n // itself keeps native caret placement).\n const target = event.target as HTMLElement;\n if (target.closest(\"input, button, a, select, textarea, label\")) return;\n const input = event.currentTarget.querySelector(\"input\");\n if (!input || input.disabled) return;\n // Focus the control instead of the wrapper — and don't blur it again.\n event.preventDefault();\n input.focus();\n };\n\n return (\n <InputGroupContext.Provider value={{ size, invalid: Boolean(invalid) }}>\n <div\n ref={ref}\n data-invalid={invalid || undefined}\n onPointerDown={handlePointerDown}\n className={cn(\n // Flat field surface — hairline ring only, same as Input; elevation\n // shadows are reserved for floating layers.\n \"bg-pho-primary ring-pho-primary relative flex w-full cursor-text items-center ring-1 transition-shadow duration-100 ease-linear ring-inset\",\n SIZE_ROOT[size],\n SIZE_RADIUS[size],\n SIZE_RING_COVER[size],\n // Any focusable child (input, a Select trigger, an addon button)\n // lights the shared ring — including an open Select popup.\n \"has-[:focus-visible]:ring-pho-brand has-[:focus-visible]:ring-2\",\n \"has-[button[data-popup-open]]:ring-pho-brand has-[button[data-popup-open]]:ring-2\",\n // Invalid is mid-weight: thicker than the 1px rest hairline, thinner\n // than the 2px focus / open ring. Rest uses the subtle error ink;\n // focus / open steps up to the solid error.\n \"data-[invalid]:ring-pho-error-subtle data-[invalid]:has-[:focus-visible]:ring-pho-error data-[invalid]:has-[button[data-popup-open]]:ring-pho-error data-[invalid]:ring-[1.5px]\",\n \"has-[input:disabled]:cursor-not-allowed has-[input:disabled]:opacity-50\",\n className,\n )}\n {...props}\n />\n </InputGroupContext.Provider>\n );\n}\n\nexport interface InputGroupInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"size\"\n> {\n /**\n * Quiet word or glyph over the control's trailing edge, before the\n * addons (an auto-save's `Saved`). Non-interactive overlay; content\n * that shows only sometimes brings its own surface wash so long text\n * fades beneath it. Reserves nothing.\n */\n trailing?: ReactNode;\n ref?: Ref<HTMLInputElement>;\n}\n\n/**\n * The text control. Rendered as Base UI `Field.Control`, so inside a\n * `Field.Root` it picks up the label and description wiring automatically;\n * standalone it behaves as a plain `<input>` (pass `aria-label` then).\n */\nfunction GroupInput({\n className,\n trailing,\n ref,\n ...props\n}: InputGroupInputProps) {\n const { size, invalid } = useContext(InputGroupContext);\n const control = (\n <Field.Control\n ref={ref}\n aria-invalid={invalid || undefined}\n {...props}\n className={cn(\n \"text-pho-primary placeholder:text-pho-placeholder m-0 w-full min-w-0 flex-1 bg-transparent outline-none disabled:cursor-not-allowed\",\n // Rounding for the autofill fill nested 2px inside the ring.\n \"autofill:rounded-[calc(var(--radius-base)-2px)]\",\n SIZE_CONTROL[size],\n className,\n )}\n />\n );\n if (trailing == null) return control;\n return (\n <span className=\"relative flex w-full min-w-0 flex-1 items-center\">\n {control}\n <span className=\"text-pho-description pointer-events-none absolute right-2 flex items-center\">\n {trailing}\n </span>\n </span>\n );\n}\n\n/**\n * Inline content beside the control — prefix/suffix text, an icon, a `Kbd`\n * hint, or a small button. Text and icons render in the secondary tone so the\n * value stays the loudest thing on the surface.\n */\nfunction Addon({\n className,\n ref,\n ...props\n}: HTMLAttributes<HTMLDivElement> & { ref?: Ref<HTMLDivElement> }) {\n const { size } = useContext(InputGroupContext);\n return (\n <div\n ref={ref}\n className={cn(\n \"text-pho-secondary flex shrink-0 items-center gap-1 select-none\",\n \"[&_svg]:shrink-0\",\n SIZE_ADDON_ICON[size],\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * A hairline between segments — turns an addon into a boxed section\n * (`https:// │ workspace │ .photon.dev`).\n */\nfunction GroupSeparator({\n className,\n orientation = \"vertical\",\n ...props\n}: StyledProps<typeof BaseSeparator>) {\n return (\n <BaseSeparator\n orientation={orientation}\n className={cn(\n // `my-0.5` keeps the line 2px clear of the ring (children paint over\n // the inset box-shadow, so a full-bleed line would notch it) — the\n // same inset the control itself uses, covering the focused 2px ring.\n \"my-0.5 w-px shrink-0 self-stretch bg-[var(--border-color-pho-secondary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Input Group — one field surface shared by a text control and its addons:\n * prefix/suffix text, icons, keyboard hints, or a trailing button. Same\n * ring, sizes, and focus / invalid / disabled behavior as `Input`, so grouped\n * and plain fields align in the same form. For a leading icon alone, plain\n * `Input` with `icon` is enough.\n */\nexport const InputGroup = {\n Root,\n Input: GroupInput,\n Addon,\n Separator: GroupSeparator,\n};\n"],"mappings":";;;;;;AA0BA,IAAM,IAAoB,EAGvB;AAAA,EAAE,MAAM;AAAA,EAAM,SAAS;AAAM,CAAC,GAM3B,IAA4C;AAAA,EAEhD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAGM,IAA+C;AAAA,EACnD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAEM,IAAkD;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAEM,IAA8C;AAAA,EAClD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAGM,IAAkD;AAAA,EACtD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,EACF,uFACA,uFAEA,sGACA,4GACA,gFACF;AACF;AAgBA,SAAS,EAAK,EACZ,MAAA,IAAO,MACP,SAAA,GACA,WAAA,GACA,eAAA,GACA,KAAA,GACA,GAAG,EAAA,GACmB;AACtB,QAAM,IAAA,CAAqB,MAAwC;AAMjE,QALA,IAAgB,CAAK,GACjB,EAAM,oBAGK,EAAM,OACV,QAAQ,2CAA2C,EAAG;AACjE,UAAM,IAAQ,EAAM,cAAc,cAAc,OAAO;AACvD,IAAI,CAAC,KAAS,EAAM,aAEpB,EAAM,eAAe,GACrB,EAAM,MAAM;AAAA,EACd;AAEA,SACE,gBAAA,EAAC,EAAkB,UAAnB;AAAA,IAA4B,OAAO;AAAA,MAAE,MAAA;AAAA,MAAM,SAAS,EAAQ;AAAA,IAAS;AAAA,IACnE,UAAA,gBAAA,EAAC,OAAD;AAAA,MACO,KAAA;AAAA,MACL,gBAAc,KAAW;AAAA,MACzB,eAAe;AAAA,MACf,WAAW,EAGT,8IACA,EAAU,CAAA,GACV,EAAY,CAAA,GACZ,EAAgB,CAAA,GAGhB,mEACA,qFAIA,mLACA,2EACA,CACF;AAAA,MACA,GAAI;AAAA,IACL,CAAA;AAAA,EACyB,CAAA;AAEhC;AAqBA,SAAS,EAAW,EAClB,WAAA,GACA,UAAA,GACA,KAAA,GACA,GAAG,EAAA,GACoB;AACvB,QAAM,EAAE,MAAA,GAAM,SAAA,EAAA,IAAY,EAAW,CAAiB,GAChD,IACJ,gBAAA,EAAC,EAAM,SAAP;AAAA,IACO,KAAA;AAAA,IACL,gBAAc,KAAW;AAAA,IACzB,GAAI;AAAA,IACJ,WAAW,EACT,uIAEA,mDACA,EAAa,CAAA,GACb,CACF;AAAA,EACD,CAAA;AAEH,SAAI,KAAY,OAAa,IAE3B,gBAAA,EAAC,QAAD;AAAA,IAAM,WAAU;AAAA,IAAhB,UAAA,CACG,GACD,gBAAA,EAAC,QAAD;AAAA,MAAM,WAAU;AAAA,MACb,UAAA;AAAA,IACG,CAAA,CACF;AAAA;AAEV;AAOA,SAAS,EAAM,EACb,WAAA,GACA,KAAA,GACA,GAAG,EAAA,GAC8D;AACjE,QAAM,EAAE,MAAA,EAAA,IAAS,EAAW,CAAiB;AAC7C,SACE,gBAAA,EAAC,OAAD;AAAA,IACO,KAAA;AAAA,IACL,WAAW,EACT,mEACA,oBACA,EAAgB,CAAA,GAChB,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AAMA,SAAS,EAAe,EACtB,WAAA,GACA,aAAA,IAAc,YACd,GAAG,EAAA,GACiC;AACpC,SACE,gBAAA,EAAC,GAAD;AAAA,IACe,aAAA;AAAA,IACb,WAAW,EAIT,4EACA,CACF;AAAA,IACA,GAAI;AAAA,EACL,CAAA;AAEL;AASA,IAAa,IAAa;AAAA,EACxB,MAAA;AAAA,EACA,OAAO;AAAA,EACP,OAAA;AAAA,EACA,WAAW;AACb"}
@@ -0,0 +1,18 @@
1
+ "use client";
2
+ import { t as i } from "./cn-ChIgwEl3.js";
3
+ import "react";
4
+ import { jsx as o } from "react/jsx-runtime";
5
+ var s = "inline-flex h-4 min-w-4 items-center justify-center rounded-[3px] px-1 font-mono text-xs font-medium text-pho-secondary ring-1 ring-pho-secondary ring-inset select-none";
6
+ function m({ keys: e, className: n, children: t, ...r }) {
7
+ return /* @__PURE__ */ o("kbd", {
8
+ className: i(s, n),
9
+ ...r,
10
+ children: t ?? e
11
+ });
12
+ }
13
+ m.displayName = "Kbd";
14
+ export {
15
+ m as t
16
+ };
17
+
18
+ //# sourceMappingURL=kbd-DNI10uy7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kbd-DNI10uy7.js","names":[],"sources":["../../src/components/kbd/kbd.tsx"],"sourcesContent":["import { type ComponentProps } from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** A single keycap surface — a mono-label pill with the site hairline (ring-pho-secondary) in secondary ink. */\nconst KBD_CLASS =\n \"inline-flex h-4 min-w-4 items-center justify-center rounded-[3px] px-1 font-mono text-xs font-medium text-pho-secondary ring-1 ring-pho-secondary ring-inset select-none\";\n\ninterface KbdBaseProps {\n /** Convenience string for the key(s) to show, e.g. \"⌘K\". Ignored when `children` are provided. */\n keys?: string;\n}\n\nexport interface KbdProps\n extends KbdBaseProps, Omit<ComponentProps<\"kbd\">, keyof KbdBaseProps> {}\n\n/**\n * Kbd — a keyboard keycap for documenting a shortcut (`⌘K`, `Esc`, `↵`). Supply\n * the key via `children` or the `keys` string; for a chord, place several in a\n * row (`Press ⌘ K`). It's purely presentational and carries no interaction — to\n * actually trigger a shortcut, wire a key handler on the target control.\n */\nexport function Kbd({ keys, className, children, ...props }: KbdProps) {\n return (\n <kbd className={cn(KBD_CLASS, className)} {...props}>\n {children ?? keys}\n </kbd>\n );\n}\n\nKbd.displayName = \"Kbd\";\n"],"mappings":";;;;AAIA,IAAM,IACJ;AAgBF,SAAgB,EAAI,EAAE,MAAA,GAAM,WAAA,GAAW,UAAA,GAAU,GAAG,EAAA,GAAmB;AACrE,SACE,gBAAA,EAAC,OAAD;AAAA,IAAK,WAAW,EAAG,GAAW,CAAS;AAAA,IAAG,GAAI;AAAA,IAC3C,UAAA,KAAY;AAAA,EACV,CAAA;AAET;AAEA,EAAI,cAAc"}
@@ -1,9 +1,10 @@
1
1
  "use client";
2
- import { t as f } from "./cn-ChIgwEl3.js";
3
- import { createContext as A, useContext as S, useLayoutEffect as k, useRef as m } from "react";
2
+ import { t as d } from "./cn-ChIgwEl3.js";
3
+ import { createContext as S, useContext as A, useLayoutEffect as k, useRef as m } from "react";
4
4
  import { jsx as l, jsxs as x } from "react/jsx-runtime";
5
+ import { motion as C } from "motion/react";
5
6
  import { ScrollArea as c } from "@base-ui/react/scroll-area";
6
- var C = "pointer-events-none absolute inset-x-0 z-10 h-8", L = 8, $ = 1;
7
+ var L = "pointer-events-none absolute inset-x-0 z-10 h-8", $ = 8, q = 1;
7
8
  function N(t, e) {
8
9
  const a = 100 / (t + 1);
9
10
  return Array.from({ length: t }, (r, o) => ({
@@ -16,13 +17,13 @@ function N(t, e) {
16
17
  ]
17
18
  })).filter((r) => r.blurPx > 0);
18
19
  }
19
- var _ = N(L, $);
20
- function z(t, e) {
20
+ var _ = N($, 1);
21
+ function T(t, e) {
21
22
  const a = t === "top" ? "to top" : "to bottom", [r, o, i, n] = e;
22
23
  return `linear-gradient(${a}, transparent ${r}%, black ${o}%, black ${i}%, transparent ${n}%)`;
23
24
  }
24
- var F = "var(--background-color-pho-primary)";
25
- function P(t, e) {
25
+ var z = "var(--pho-surface, var(--background-color-pho-primary))";
26
+ function F(t, e) {
26
27
  return `linear-gradient(${t === "top" ? "to top" : "to bottom"}, color-mix(in oklab, ${e} 20%, transparent) 0%, ${e} 100%)`;
27
28
  }
28
29
  function h(t, e) {
@@ -37,14 +38,14 @@ function h(t, e) {
37
38
  r.style.backdropFilter = o, r.style.setProperty("-webkit-backdrop-filter", o);
38
39
  }
39
40
  }
40
- function g({ edge: t, edgeRef: e, surface: a = F, className: r }) {
41
+ function g({ edge: t, edgeRef: e, surface: a = z, className: r }) {
41
42
  return /* @__PURE__ */ x("div", {
42
43
  ref: e,
43
44
  "aria-hidden": !0,
44
45
  "data-edge": t,
45
- className: f(C, r ?? (t === "top" ? "top-0" : "bottom-0")),
46
+ className: d(L, r ?? (t === "top" ? "top-0" : "bottom-0")),
46
47
  children: [_.map(({ blurPx: o, window: i }, n) => {
47
- const s = z(t, i);
48
+ const s = T(t, i);
48
49
  return /* @__PURE__ */ l("div", {
49
50
  "data-blur-px": o,
50
51
  className: "absolute inset-0",
@@ -56,13 +57,13 @@ function g({ edge: t, edgeRef: e, surface: a = F, className: r }) {
56
57
  }), /* @__PURE__ */ l("div", {
57
58
  "data-scroll-fade": "wash",
58
59
  className: "absolute inset-0 opacity-0",
59
- style: { background: P(t, a) }
60
+ style: { background: F(t, a) }
60
61
  })]
61
62
  });
62
63
  }
63
- var E = A(null);
64
- function T() {
65
- const t = S(E);
64
+ var E = S(null);
65
+ function P() {
66
+ const t = A(E);
66
67
  if (t == null) throw new Error("ScrollArea parts must be used inside ScrollArea.Root");
67
68
  return t;
68
69
  }
@@ -72,11 +73,22 @@ function y(t) {
72
73
  function U(t, e) {
73
74
  typeof t == "function" ? t(e) : t && (t.current = e);
74
75
  }
75
- var B = c.Content, H = c.Corner;
76
+ function B({ inset: t, insetTop: e, insetBottom: a, style: r, ref: o, ...i }) {
77
+ return /* @__PURE__ */ l(c.Content, {
78
+ ref: o,
79
+ style: {
80
+ paddingTop: e ?? t,
81
+ paddingBottom: a ?? t,
82
+ ...r
83
+ },
84
+ ...i
85
+ });
86
+ }
87
+ var H = c.Corner;
76
88
  function M({ className: t, children: e, ...a }) {
77
89
  const r = m(null), o = m(null);
78
90
  return /* @__PURE__ */ l(c.Root, {
79
- className: f("relative overflow-hidden", t),
91
+ className: d("relative overflow-hidden", t),
80
92
  ...a,
81
93
  children: /* @__PURE__ */ x(E.Provider, {
82
94
  value: {
@@ -98,7 +110,7 @@ function M({ className: t, children: e, ...a }) {
98
110
  });
99
111
  }
100
112
  function O({ className: t, ref: e, ...a }) {
101
- const { topEl: r, bottomEl: o } = T(), i = m(null);
113
+ const { topEl: r, bottomEl: o } = P(), i = m(null);
102
114
  return k(() => {
103
115
  const n = i.current;
104
116
  if (!n) return;
@@ -113,12 +125,13 @@ function O({ className: t, ref: e, ...a }) {
113
125
  }));
114
126
  };
115
127
  s(), n.addEventListener("scroll", b, { passive: !0 });
116
- let d = null;
117
- return typeof ResizeObserver < "u" && (d = new ResizeObserver(s), d.observe(n), n.firstElementChild && d.observe(n.firstElementChild)), () => {
118
- n.removeEventListener("scroll", b), u && cancelAnimationFrame(u), d?.disconnect();
128
+ let f = null;
129
+ return typeof ResizeObserver < "u" && (f = new ResizeObserver(s), f.observe(n), n.firstElementChild && f.observe(n.firstElementChild)), () => {
130
+ n.removeEventListener("scroll", b), u && cancelAnimationFrame(u), f?.disconnect();
119
131
  };
120
132
  }, [r, o]), /* @__PURE__ */ l(c.Viewport, {
121
- className: f("outline-pho-brand h-full min-h-0 w-full overscroll-contain rounded-[inherit] focus-visible:outline-2 focus-visible:-outline-offset-1", t),
133
+ render: /* @__PURE__ */ l(C.div, { layoutScroll: !0 }),
134
+ className: d("outline-pho-brand h-full min-h-0 w-full overscroll-contain rounded-[inherit] focus-visible:outline-2 focus-visible:-outline-offset-1", t),
122
135
  ...a,
123
136
  ref: (n) => {
124
137
  i.current = n, U(e, n);
@@ -127,17 +140,17 @@ function O({ className: t, ref: e, ...a }) {
127
140
  }
128
141
  function j({ className: t, ...e }) {
129
142
  return /* @__PURE__ */ l(c.Scrollbar, {
130
- className: f("relative z-20 flex touch-none p-0.5 opacity-0 transition-opacity delay-300 select-none", "data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:opacity-100 data-[scrolling]:delay-0 data-[scrolling]:duration-0", "data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:w-2.5", t),
143
+ className: d("relative z-20 flex touch-none p-0.5 opacity-0 transition-opacity delay-300 select-none", "data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:opacity-100 data-[scrolling]:delay-0 data-[scrolling]:duration-0", "data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:w-2.5", t),
131
144
  ...e
132
145
  });
133
146
  }
134
147
  function D({ className: t, ...e }) {
135
148
  return /* @__PURE__ */ l(c.Thumb, {
136
- className: f("flex-1 rounded-full bg-[var(--border-color-pho-primary)]", t),
149
+ className: d("flex-1 rounded-full bg-[var(--border-color-pho-primary)]", t),
137
150
  ...e
138
151
  });
139
152
  }
140
- var Y = {
153
+ var W = {
141
154
  Root: M,
142
155
  Viewport: O,
143
156
  Content: B,
@@ -146,7 +159,8 @@ var Y = {
146
159
  Corner: H
147
160
  };
148
161
  export {
149
- Y as t
162
+ q as n,
163
+ W as t
150
164
  };
151
165
 
152
- //# sourceMappingURL=scroll-area-DkZHKfqH.js.map
166
+ //# sourceMappingURL=scroll-area-Bu7egArU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scroll-area-Bu7egArU.js","names":[],"sources":["../../src/components/scroll-area/scroll-area-edges.tsx","../../src/components/scroll-area/scroll-area.tsx"],"sourcesContent":["import { type Ref } from \"react\";\nimport { cn } from \"../../utils/cn\";\n\n/** Overlay strip height — a short ramp, not a banner. `h-8` = 32px. */\nexport const EDGE_CLASS = \"pointer-events-none absolute inset-x-0 z-10 h-8\";\nexport const EDGE_REVEAL_PX = 32;\n\nconst BLUR_LAYER_COUNT = 8;\n\n/**\n * The system's one blur strength. The edge fade peaks here, and a motion\n * that softens (a scope swap, a bar title) borrows the same value, so\n * every soft focus in the product is the same soft focus.\n */\nexport const BLUR_MAX_PX = 1;\n\nfunction buildBlurLayers(count: number, maxPx: number) {\n const segment = 100 / (count + 1);\n return Array.from({ length: count }, (_, i) => ({\n blurPx: (i / (count - 1)) * maxPx,\n window: [\n i * segment,\n (i + 1) * segment,\n (i + 2) * segment,\n (i + 3) * segment,\n ] as const,\n })).filter((layer) => layer.blurPx > 0);\n}\n\nconst BLUR_LAYERS = buildBlurLayers(BLUR_LAYER_COUNT, BLUR_MAX_PX);\n\nfunction edgeMask(edge: \"top\" | \"bottom\", window: readonly number[]) {\n const toEdge = edge === \"top\" ? \"to top\" : \"to bottom\";\n const [a, b, c, d] = window;\n return `linear-gradient(${toEdge}, transparent ${a}%, black ${b}%, black ${c}%, transparent ${d}%)`;\n}\n\n// The wash fades into the block it sits on: every `bg-pho-*` surface\n// registers itself as `--pho-surface` (see the stylesheet), so the nearest\n// surface ancestor's background carries down here — live across themes.\n// The primary surface is the fallback for hosts outside any pho surface.\nconst DEFAULT_SURFACE =\n \"var(--pho-surface, var(--background-color-pho-primary))\";\n\nfunction washGradient(edge: \"top\" | \"bottom\", surface: string) {\n const to = edge === \"top\" ? \"to top\" : \"to bottom\";\n return `linear-gradient(${to}, color-mix(in oklab, ${surface} 20%, transparent) 0%, ${surface} 100%)`;\n}\n\nexport function paintEdge(node: HTMLDivElement | null, progress: number) {\n if (!node) return;\n for (const child of node.children) {\n const el = child as HTMLElement;\n if (el.dataset.scrollFade === \"wash\") {\n el.style.opacity = String(progress);\n continue;\n }\n const blur = `blur(${Number(el.dataset.blurPx) * progress}px)`;\n el.style.backdropFilter = blur;\n el.style.setProperty(\"-webkit-backdrop-filter\", blur);\n }\n}\n\nexport function Edge({\n edge,\n edgeRef,\n surface = DEFAULT_SURFACE,\n className,\n}: {\n edge: \"top\" | \"bottom\";\n edgeRef: Ref<HTMLDivElement>;\n /** The surface the wash fades into — the card by default, or a page. */\n surface?: string;\n /** Position override — the strip sits at the named edge unless placed. */\n className?: string;\n}) {\n return (\n <div\n ref={edgeRef}\n aria-hidden\n data-edge={edge}\n className={cn(\n EDGE_CLASS,\n className ?? (edge === \"top\" ? \"top-0\" : \"bottom-0\"),\n )}\n >\n {BLUR_LAYERS.map(({ blurPx, window }, index) => {\n const mask = edgeMask(edge, window);\n return (\n <div\n key={index}\n data-blur-px={blurPx}\n className=\"absolute inset-0\"\n style={{\n maskImage: mask,\n WebkitMaskImage: mask,\n }}\n />\n );\n })}\n <div\n data-scroll-fade=\"wash\"\n className=\"absolute inset-0 opacity-0\"\n style={{ background: washGradient(edge, surface) }}\n />\n </div>\n );\n}\n","import {\n createContext,\n useContext,\n useLayoutEffect,\n useRef,\n type ComponentProps,\n type ElementType,\n type Ref,\n type RefObject,\n} from \"react\";\nimport { ScrollArea as BaseScrollArea } from \"@base-ui/react/scroll-area\";\nimport { motion } from \"motion/react\";\nimport { cn } from \"../../utils/cn\";\nimport { Edge, EDGE_REVEAL_PX, paintEdge } from \"./scroll-area-edges\";\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\"\n> & {\n className?: string;\n};\n\nconst ScrollAreaContext = createContext<{\n topEl: RefObject<HTMLDivElement | null>;\n bottomEl: RefObject<HTMLDivElement | null>;\n} | null>(null);\n\nfunction useScrollAreaContext() {\n const ctx = useContext(ScrollAreaContext);\n if (ctx == null) {\n throw new Error(\"ScrollArea parts must be used inside ScrollArea.Root\");\n }\n return ctx;\n}\n\nfunction clamp01(n: number) {\n if (n <= 0) return 0;\n if (n >= 1) return 1;\n return n;\n}\n\nfunction assignRef<T>(ref: Ref<T> | undefined, node: T | null) {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref) {\n // React 18 types `RefObject.current` as readonly; the write is legal.\n (ref as { current: T | null }).current = node;\n }\n}\n\n/**\n * The scrolled content. `inset` is a resting inset in the scroll axis —\n * like a scroll view's safe-area padding: the content stands clear of the\n * edges at rest but scrolls past them to the true edge, and the scrollbar\n * and edge fade stay on `Root`'s edges. `insetTop` / `insetBottom` set the\n * ends separately (they win over `inset`).\n */\nfunction Content({\n inset,\n insetTop,\n insetBottom,\n style,\n ref,\n ...props\n}: ComponentProps<typeof BaseScrollArea.Content> & {\n inset?: number | string;\n insetTop?: number | string;\n insetBottom?: number | string;\n ref?: Ref<HTMLDivElement>;\n}) {\n return (\n <BaseScrollArea.Content\n ref={ref}\n style={{\n paddingTop: insetTop ?? inset,\n paddingBottom: insetBottom ?? inset,\n ...style,\n }}\n {...props}\n />\n );\n}\n\nconst Corner = BaseScrollArea.Corner;\n\nfunction Root({\n className,\n children,\n ...props\n}: StyledProps<typeof BaseScrollArea.Root>) {\n const topEl = useRef<HTMLDivElement | null>(null);\n const bottomEl = useRef<HTMLDivElement | null>(null);\n\n return (\n <BaseScrollArea.Root\n className={cn(\"relative overflow-hidden\", className)}\n {...props}\n >\n <ScrollAreaContext.Provider value={{ topEl, bottomEl }}>\n {children}\n <Edge edge=\"top\" edgeRef={topEl} />\n <Edge edge=\"bottom\" edgeRef={bottomEl} />\n </ScrollAreaContext.Provider>\n </BaseScrollArea.Root>\n );\n}\n\nfunction Viewport({\n className,\n ref,\n ...props\n}: StyledProps<typeof BaseScrollArea.Viewport>) {\n const { topEl, bottomEl } = useScrollAreaContext();\n const viewportRef = useRef<HTMLElement | null>(null);\n\n useLayoutEffect(() => {\n const viewport = viewportRef.current;\n if (!viewport) return;\n\n const paint = () => {\n const reveal =\n topEl.current?.offsetHeight ||\n bottomEl.current?.offsetHeight ||\n EDGE_REVEAL_PX;\n const maxScroll = viewport.scrollHeight - viewport.clientHeight;\n const top = maxScroll <= 0 ? 0 : clamp01(viewport.scrollTop / reveal);\n const bottom =\n maxScroll <= 0 ? 0 : clamp01((maxScroll - viewport.scrollTop) / reveal);\n paintEdge(topEl.current, top);\n paintEdge(bottomEl.current, bottom);\n };\n\n let frame = 0;\n const onScroll = () => {\n if (frame) return;\n frame = requestAnimationFrame(() => {\n frame = 0;\n paint();\n });\n };\n\n paint();\n viewport.addEventListener(\"scroll\", onScroll, { passive: true });\n let observer: ResizeObserver | null = null;\n if (typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(paint);\n observer.observe(viewport);\n if (viewport.firstElementChild) {\n observer.observe(viewport.firstElementChild);\n }\n }\n return () => {\n viewport.removeEventListener(\"scroll\", onScroll);\n if (frame) cancelAnimationFrame(frame);\n observer?.disconnect();\n };\n }, [topEl, bottomEl]);\n\n return (\n <BaseScrollArea.Viewport\n // A motion element with layoutScroll: any layout-animated content\n // inside (a shared-element morph, a FLIP row) projects against the\n // viewport's scroll offset instead of reading a scroll as a layout\n // change.\n render={<motion.div layoutScroll />}\n className={cn(\n \"outline-pho-brand h-full min-h-0 w-full overscroll-contain rounded-[inherit] focus-visible:outline-2 focus-visible:-outline-offset-1\",\n className,\n )}\n {...props}\n ref={(node) => {\n viewportRef.current = node;\n assignRef(ref, node);\n }}\n />\n );\n}\n\nfunction Scrollbar({\n className,\n ...props\n}: StyledProps<typeof BaseScrollArea.Scrollbar>) {\n return (\n <BaseScrollArea.Scrollbar\n className={cn(\n \"relative z-20 flex touch-none p-0.5 opacity-0 transition-opacity delay-300 select-none\",\n \"data-[hovering]:opacity-100 data-[hovering]:delay-0 data-[scrolling]:opacity-100 data-[scrolling]:delay-0 data-[scrolling]:duration-0\",\n \"data-[orientation=horizontal]:h-2.5 data-[orientation=horizontal]:flex-col data-[orientation=vertical]:w-2.5\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Thumb({\n className,\n ...props\n}: StyledProps<typeof BaseScrollArea.Thumb>) {\n return (\n <BaseScrollArea.Thumb\n className={cn(\n \"flex-1 rounded-full bg-[var(--border-color-pho-primary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Scroll area built on Base UI `ScrollArea` — a capped region with overlay\n * scrollbars and progressive edge fade. Approaching rows blur (0 → 1px) and\n * wash into the surface; strength tracks scroll distance over one strip\n * height. Give `Root` a fixed size; put content in `Viewport` → `Content`\n * and add a `Scrollbar` → `Thumb` per axis.\n */\nexport const ScrollArea = {\n Root,\n Viewport,\n Content,\n Scrollbar,\n Thumb,\n Corner,\n};\n"],"mappings":"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"}