@photon-ai/pho-ui 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/basic-page-c_7Ii5Fr.js +1117 -0
- package/dist/chunks/basic-page-c_7Ii5Fr.js.map +1 -0
- package/dist/chunks/{button-BmhyLgy5.js → button-DNNs5RsT.js} +6 -6
- package/dist/chunks/{button-BmhyLgy5.js.map → button-DNNs5RsT.js.map} +1 -1
- package/dist/chunks/{dialog-DtFO7oy0.js → dialog-8SRNi_rv.js} +3 -3
- package/dist/chunks/{dialog-DtFO7oy0.js.map → dialog-8SRNi_rv.js.map} +1 -1
- package/dist/chunks/{error-Da7P5WZi.js → error-Crs-wqMC.js} +14 -14
- package/dist/chunks/{error-Da7P5WZi.js.map → error-Crs-wqMC.js.map} +1 -1
- package/dist/chunks/form-AiHbbw4m.js +17 -0
- package/dist/chunks/form-AiHbbw4m.js.map +1 -0
- package/dist/chunks/{input-QSAUO30c.js → input-DjvZoF8v.js} +48 -43
- package/dist/chunks/input-DjvZoF8v.js.map +1 -0
- package/dist/chunks/{input-group-5AtpIhC_.js → input-group-fc9_jD4d.js} +44 -38
- package/dist/chunks/input-group-fc9_jD4d.js.map +1 -0
- package/dist/chunks/kbd-DNI10uy7.js +18 -0
- package/dist/chunks/kbd-DNI10uy7.js.map +1 -0
- package/dist/chunks/{menu-Cgj3lq9z.js → menu-BQeprfxB.js} +5 -5
- package/dist/chunks/{menu-Cgj3lq9z.js.map → menu-BQeprfxB.js.map} +1 -1
- package/dist/chunks/scroll-area-Bu7egArU.js +166 -0
- package/dist/chunks/scroll-area-Bu7egArU.js.map +1 -0
- package/dist/chunks/sidebar-DvB-G0BP.js +231 -0
- package/dist/chunks/sidebar-DvB-G0BP.js.map +1 -0
- package/dist/chunks/toast-DoXaRPIZ.js +167 -0
- package/dist/chunks/toast-DoXaRPIZ.js.map +1 -0
- package/dist/components/button.js +1 -1
- package/dist/components/command.js +125 -172
- package/dist/components/command.js.map +1 -1
- package/dist/components/context-menu.js +23 -23
- package/dist/components/dialog.js +1 -1
- package/dist/components/error.js +1 -1
- package/dist/components/field.js +12 -5
- package/dist/components/field.js.map +1 -1
- package/dist/components/form.js +2 -14
- package/dist/components/input-group.js +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/kbd.js +2 -15
- package/dist/components/menu.js +1 -1
- package/dist/components/phone-input.js +1 -1
- package/dist/components/scroll-area.js +3 -2
- package/dist/components/toast.js +3 -57
- package/dist/index.js +65 -58
- package/dist/primitives.js +3 -3
- package/dist/sections/basic-page.js +15 -8
- package/dist/sections/sidebar.js +1 -1
- package/dist/src/components/command/command.d.ts +5 -4
- package/dist/src/components/field/field.d.ts +9 -0
- package/dist/src/components/input/input.d.ts +10 -1
- package/dist/src/components/input-group/input-group.d.ts +9 -2
- package/dist/src/components/scroll-area/index.d.ts +1 -0
- package/dist/src/components/scroll-area/scroll-area-edges.d.ts +11 -1
- package/dist/src/components/scroll-area/scroll-area.d.ts +15 -2
- package/dist/src/components/toast/index.d.ts +1 -1
- package/dist/src/components/toast/toast.d.ts +22 -7
- package/dist/src/sections/basic-page/basic-page.d.ts +210 -30
- package/dist/src/sections/basic-page/index.d.ts +1 -1
- package/dist/src/sections/sidebar/sidebar.d.ts +33 -3
- package/package.json +1 -1
- package/src/styles/pho.css +19 -0
- package/dist/chunks/basic-page-CB36yrpL.js +0 -643
- package/dist/chunks/basic-page-CB36yrpL.js.map +0 -1
- package/dist/chunks/input-QSAUO30c.js.map +0 -1
- package/dist/chunks/input-group-5AtpIhC_.js.map +0 -1
- package/dist/chunks/scroll-area-Cw4JFqNG.js +0 -152
- package/dist/chunks/scroll-area-Cw4JFqNG.js.map +0 -1
- package/dist/chunks/sidebar-BETMObNj.js +0 -186
- package/dist/chunks/sidebar-BETMObNj.js.map +0 -1
- package/dist/components/form.js.map +0 -1
- package/dist/components/kbd.js.map +0 -1
- package/dist/components/toast.js.map +0 -1
|
@@ -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
|
|
4
|
-
import { jsx as
|
|
5
|
-
import { Field as
|
|
6
|
-
import { Separator as
|
|
7
|
-
var
|
|
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
|
-
}),
|
|
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
|
-
},
|
|
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
|
-
},
|
|
22
|
+
}, y = {
|
|
23
23
|
sm: "rounded-base",
|
|
24
24
|
md: "rounded-base",
|
|
25
25
|
lg: "squircle rounded-base"
|
|
26
|
-
},
|
|
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
|
|
32
|
-
const
|
|
33
|
-
if (o?.(
|
|
34
|
-
const
|
|
35
|
-
!
|
|
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__ */
|
|
37
|
+
return /* @__PURE__ */ t(u.Provider, {
|
|
38
38
|
value: {
|
|
39
39
|
size: e,
|
|
40
40
|
invalid: !!r
|
|
41
41
|
},
|
|
42
|
-
children: /* @__PURE__ */
|
|
43
|
-
ref:
|
|
42
|
+
children: /* @__PURE__ */ t("div", {
|
|
43
|
+
ref: l,
|
|
44
44
|
"data-invalid": r || void 0,
|
|
45
|
-
onPointerDown:
|
|
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",
|
|
47
|
-
...
|
|
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,
|
|
52
|
-
const { size:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
61
|
-
const { size: o } =
|
|
62
|
-
return /* @__PURE__ */
|
|
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
|
|
69
|
-
return /* @__PURE__ */
|
|
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
|
|
76
|
-
Root:
|
|
81
|
+
var G = {
|
|
82
|
+
Root: w,
|
|
77
83
|
Input: _,
|
|
78
|
-
Addon:
|
|
79
|
-
Separator:
|
|
84
|
+
Addon: N,
|
|
85
|
+
Separator: S
|
|
80
86
|
};
|
|
81
87
|
export {
|
|
82
|
-
|
|
88
|
+
G as t
|
|
83
89
|
};
|
|
84
90
|
|
|
85
|
-
//# sourceMappingURL=input-group-
|
|
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"}
|
|
@@ -91,8 +91,8 @@ var j = "bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary origin
|
|
|
91
91
|
classes: "text-pho-error data-[highlighted]:bg-pho-error-primary data-[highlighted]:text-pho-error",
|
|
92
92
|
description: "Error ink + error wash on highlight"
|
|
93
93
|
}
|
|
94
|
-
}, A = O(t.Root), B = t.Trigger, H = t.Portal, V = t.Group, $ = t.RadioGroup,
|
|
95
|
-
function
|
|
94
|
+
}, A = O(t.Root), B = t.Trigger, H = t.Portal, V = t.Group, $ = t.RadioGroup, F = t.SubmenuRoot;
|
|
95
|
+
function D({ className: e, sideOffset: n = 6, ...i }) {
|
|
96
96
|
return /* @__PURE__ */ r(t.Positioner, {
|
|
97
97
|
sideOffset: n,
|
|
98
98
|
className: o("z-50", e),
|
|
@@ -179,14 +179,14 @@ var de = {
|
|
|
179
179
|
Root: A,
|
|
180
180
|
Trigger: B,
|
|
181
181
|
Portal: H,
|
|
182
|
-
Positioner:
|
|
182
|
+
Positioner: D,
|
|
183
183
|
Popup: K,
|
|
184
184
|
Item: q,
|
|
185
185
|
LinkItem: J,
|
|
186
186
|
Group: V,
|
|
187
187
|
GroupLabel: Q,
|
|
188
188
|
Separator: W,
|
|
189
|
-
SubmenuRoot:
|
|
189
|
+
SubmenuRoot: F,
|
|
190
190
|
SubmenuTrigger: X,
|
|
191
191
|
CheckboxItem: Y,
|
|
192
192
|
CheckboxItemIndicator: ee,
|
|
@@ -205,4 +205,4 @@ export {
|
|
|
205
205
|
f as t
|
|
206
206
|
};
|
|
207
207
|
|
|
208
|
-
//# sourceMappingURL=menu-
|
|
208
|
+
//# sourceMappingURL=menu-BQeprfxB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-Cgj3lq9z.js","names":[],"sources":["../../src/components/menu/menu-pending.tsx","../../src/components/menu/menu.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { Spinner } from \"../spinner/spinner\";\n\n/** Imperative actions exposed by Base UI menu `actionsRef`. */\ninterface RootActions {\n unmount: () => void;\n close: () => void;\n}\n\ninterface MenuPendingContextValue {\n /** True while a menu item promise is in flight. */\n pending: boolean;\n beginPending: () => void;\n endPending: () => void;\n /** Close the menu imperatively (used by async items on resolve). */\n close: () => void;\n}\n\nconst MenuPendingContext = createContext<MenuPendingContextValue | null>(null);\n\n/**\n * Whether the surrounding menu has an async item promise in flight —\n * use it to disable sibling items while work is pending.\n * Returns `false` outside a `Menu.Root` / `ContextMenu.Root`.\n */\nexport function useMenuPending(): boolean {\n return useContext(MenuPendingContext)?.pending ?? false;\n}\n\nexport function isThenable(value: unknown): value is Promise<unknown> {\n return (\n value != null && typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\ntype PreventableMouseEvent = ReactMouseEvent<HTMLElement> & {\n preventBaseUIHandler?: () => void;\n};\n\ntype RootWithPendingProps = {\n onOpenChange?: (open: boolean, eventDetails: { cancel: () => void }) => void;\n actionsRef?: RefObject<RootActions | null>;\n children?: ReactNode;\n};\n\n/**\n * Wraps a Base UI menu root so async `Menu.Item` clicks can lock dismissal\n * (outside press, Escape) until the promise settles.\n */\nexport function withMenuPendingRoot<P extends RootWithPendingProps>(\n BaseRoot: ComponentType<P>,\n): ComponentType<P> {\n function PendingRoot({ onOpenChange, actionsRef, ...props }: P) {\n const [pending, setPending] = useState(false);\n // Guard reads a ref so a resolve → close() in the same tick isn't blocked\n // by a not-yet-flushed state update.\n const pendingRef = useRef(false);\n const internalActionsRef = useRef<RootActions | null>(null);\n\n useEffect(() => {\n if (!actionsRef) return;\n // Proxy so callers always hit the live Base UI actions, even if the\n // popup remounts after this effect runs.\n actionsRef.current = {\n unmount: () => internalActionsRef.current?.unmount(),\n close: () => internalActionsRef.current?.close(),\n };\n return () => {\n actionsRef.current = null;\n };\n }, [actionsRef]);\n\n const handleOpenChange = useCallback(\n (open: boolean, eventDetails: { cancel: () => void }) => {\n if (!open && pendingRef.current) {\n eventDetails.cancel();\n return;\n }\n onOpenChange?.(open, eventDetails);\n },\n [onOpenChange],\n );\n\n const contextValue = useMemo<MenuPendingContextValue>(\n () => ({\n pending,\n beginPending: () => {\n pendingRef.current = true;\n setPending(true);\n },\n endPending: () => {\n pendingRef.current = false;\n setPending(false);\n },\n close: () => internalActionsRef.current?.close(),\n }),\n [pending],\n );\n\n return (\n <MenuPendingContext.Provider value={contextValue}>\n <BaseRoot\n {...(props as P)}\n actionsRef={internalActionsRef}\n onOpenChange={handleOpenChange}\n />\n </MenuPendingContext.Provider>\n );\n }\n\n return PendingRoot as ComponentType<P>;\n}\n\nexport type AsyncMenuItemClick = (\n event: ReactMouseEvent<HTMLElement>,\n) => void | Promise<unknown>;\n\n/**\n * Shared click handler for `Menu.Item` / `ContextMenu.Item` promise support.\n * Returns whether the item should show its loading state.\n */\nexport function useAsyncMenuItem({\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n}: {\n onClick?: AsyncMenuItemClick;\n closeOnClick?: boolean;\n loading?: boolean;\n disabled?: boolean;\n}) {\n const pendingContext = useContext(MenuPendingContext);\n const menuPending = pendingContext?.pending ?? false;\n const [busy, setBusy] = useState(false);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const inert = busy || loading;\n // Disable siblings while any item promise is in flight — not the busy item itself.\n const blocked = disabled || (menuPending && !busy);\n\n function handleClick(event: ReactMouseEvent<HTMLElement>) {\n if (inert || (menuPending && !busy)) {\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n return;\n }\n const result = onClick?.(event);\n if (!isThenable(result)) {\n return;\n }\n // Stop Base UI's closeOnClick for this press; close on resolve instead.\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n setBusy(true);\n pendingContext?.beginPending();\n result.then(\n () => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (closeOnClick) pendingContext?.close();\n },\n (error: unknown) => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.error(\"Menu.Item: async onClick rejected —\", error);\n }\n },\n );\n }\n\n return { inert, blocked, handleClick };\n}\n\n/** Menu-row spinner — keeps `data-menu-spinner` for the hide-siblings CSS. */\nexport function MenuItemSpinner() {\n return <Spinner data-menu-spinner=\"\" size=\"md\" />;\n}\n","import {\n type ComponentProps,\n type ElementType,\n type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { Separator as BaseSeparator } from \"@base-ui/react/separator\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\nimport {\n MenuItemSpinner,\n useAsyncMenuItem,\n useMenuPending,\n withMenuPendingRoot,\n type AsyncMenuItemClick,\n} from \"./menu-pending\";\n\nexport { useMenuPending };\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\" | \"color\"\n> & {\n className?: string;\n};\n\n/** Shared popup + item styles, reused by `ContextMenu` and `Menubar` menus. */\n// Floating layer: keeps its elevation shadow, sits on the primary surface\n// with the site's hairline (ring-pho-secondary, #eee/#202020) as its edge.\n// `base` radius + p-1.5 inset — the highlight floats 6px clear of the edge.\nexport const MENU_POPUP =\n \"bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary origin-[var(--transform-origin)] min-w-44 rounded-base p-1.5 ring-1 ring-inset outline-none transition-[transform,opacity] duration-[330ms] ease-pho-spring data-[ending-style]:duration-150 data-[ending-style]:ease-out data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:scale-95 data-[ending-style]:opacity-0\";\n// Rest at secondary; highlighted lifts to primary with the quiet ghost wash.\n// Icons use `text-current` so they follow the row ink (including destructive).\n// While loading, the spinner is first; hide the rest of the leading icons.\n// Concentric correction (see /scale): row radius = popup radius − popup\n// padding (base − 6px). py-2 + 20px line-height lands the 36px row height.\nexport const MENU_ITEM =\n \"relative flex cursor-pointer items-center gap-2 rounded-[calc(var(--radius-base)-6px)] py-2 pr-2.5 pl-2.5 text-base text-pho-secondary outline-none select-none data-[highlighted]:bg-pho-ghost-hover data-[highlighted]:text-pho-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-loading:data-[disabled]:opacity-100 data-loading:[&>svg:not([data-menu-spinner])]:hidden [&>svg]:size-4 [&>svg]:text-current\";\n\n/** Semantic ink for `Menu.Item` / `LinkItem` / `ContextMenu.Item`. */\nexport const MENU_ITEM_COLORS = {\n default: { classes: \"\", description: \"Secondary → primary on highlight\" },\n destructive: {\n classes:\n \"text-pho-error data-[highlighted]:bg-pho-error-primary data-[highlighted]:text-pho-error\",\n description: \"Error ink + error wash on highlight\",\n },\n} satisfies VariantMap;\n\nexport type MenuItemColor = keyof typeof MENU_ITEM_COLORS;\n\ntype ItemColorProps = {\n /** Semantic ink. @default \"default\" */\n color?: MenuItemColor;\n};\n\nexport type MenuItemProps = Omit<StyledProps<typeof BaseMenu.Item>, \"onClick\"> &\n ItemColorProps & {\n /**\n * Action handler. Return a promise to keep the menu open while it runs:\n * the item shows a spinner, dismissal is locked, and the menu closes when\n * the promise resolves (if `closeOnClick`). On rejection the menu stays\n * open — surface errors inside the handler.\n */\n onClick?: AsyncMenuItemClick;\n /**\n * Declarative loading state (same visual as an in-flight promise).\n * @default false\n */\n loading?: boolean;\n };\n\nconst Root = withMenuPendingRoot(BaseMenu.Root);\nconst Trigger = BaseMenu.Trigger;\nconst Portal = BaseMenu.Portal;\nconst Group = BaseMenu.Group;\nconst RadioGroup = BaseMenu.RadioGroup;\n// Don't wrap SubmenuRoot — nested pending context would shadow the parent\n// Root and fail to lock dismissal for async items inside a submenu.\nconst SubmenuRoot = BaseMenu.SubmenuRoot;\n\nfunction Positioner({\n className,\n sideOffset = 6,\n ...props\n}: StyledProps<typeof BaseMenu.Positioner>) {\n return (\n <BaseMenu.Positioner\n sideOffset={sideOffset}\n className={cn(\"z-50\", className)}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseMenu.Popup>) {\n return <BaseMenu.Popup className={cn(MENU_POPUP, className)} {...props} />;\n}\n\nfunction Item({\n className,\n color = \"default\",\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n children,\n ...props\n}: MenuItemProps) {\n const { inert, blocked, handleClick } = useAsyncMenuItem({\n onClick,\n closeOnClick,\n loading,\n disabled,\n });\n\n return (\n <BaseMenu.Item\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n closeOnClick={closeOnClick}\n disabled={blocked}\n data-loading={inert || undefined}\n aria-busy={inert || undefined}\n onClick={handleClick as (event: ReactMouseEvent<HTMLElement>) => void}\n {...props}\n >\n {inert ? (\n <>\n <MenuItemSpinner />\n <output className=\"sr-only\" aria-live=\"polite\" />\n </>\n ) : null}\n {children}\n </BaseMenu.Item>\n );\n}\n\nfunction LinkItem({\n className,\n color = \"default\",\n ...props\n}: StyledProps<typeof BaseMenu.LinkItem> & ItemColorProps) {\n return (\n <BaseMenu.LinkItem\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction GroupLabel({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.GroupLabel>) {\n return (\n <BaseMenu.GroupLabel\n className={cn(\n \"text-pho-secondary px-2.5 py-1.5 text-xs font-medium\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Separator({ className, ...props }: StyledProps<typeof BaseSeparator>) {\n return (\n <BaseSeparator\n className={cn(\n \"-mx-1.5 my-1.5 h-px bg-[var(--border-color-pho-secondary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SubmenuTrigger({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.SubmenuTrigger>) {\n return (\n <BaseMenu.SubmenuTrigger\n className={cn(\n MENU_ITEM,\n \"data-[popup-open]:bg-pho-ghost-hover\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CheckboxItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItem>) {\n return (\n <BaseMenu.CheckboxItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nfunction RadioItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItem>) {\n return (\n <BaseMenu.RadioItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nconst CHECK_INDICATOR =\n \"text-pho-brand absolute left-2.5 flex items-center [&>svg]:size-4\";\n\nfunction CheckboxItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItemIndicator>) {\n return (\n <BaseMenu.CheckboxItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\nfunction RadioItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItemIndicator>) {\n return (\n <BaseMenu.RadioItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\n/**\n * Dropdown menu built on Base UI `Menu` — a list of actions opened from a\n * trigger. Supports grouped items, separators, submenus\n * (`SubmenuRoot` + `SubmenuTrigger`), and stateful `CheckboxItem` /\n * `RadioItem`s. `Menu.Item` has native promise support (spinner + locked\n * dismissal while pending). For a right-click menu use `ContextMenu`.\n */\nexport const Menu = {\n Root,\n Trigger,\n Portal,\n Positioner,\n Popup,\n Item,\n LinkItem,\n Group,\n GroupLabel,\n Separator,\n SubmenuRoot,\n SubmenuTrigger,\n CheckboxItem,\n CheckboxItemIndicator,\n RadioGroup,\n RadioItem,\n RadioItemIndicator,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"menu-BQeprfxB.js","names":[],"sources":["../../src/components/menu/menu-pending.tsx","../../src/components/menu/menu.tsx"],"sourcesContent":["import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ComponentType,\n type MouseEvent as ReactMouseEvent,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { Spinner } from \"../spinner/spinner\";\n\n/** Imperative actions exposed by Base UI menu `actionsRef`. */\ninterface RootActions {\n unmount: () => void;\n close: () => void;\n}\n\ninterface MenuPendingContextValue {\n /** True while a menu item promise is in flight. */\n pending: boolean;\n beginPending: () => void;\n endPending: () => void;\n /** Close the menu imperatively (used by async items on resolve). */\n close: () => void;\n}\n\nconst MenuPendingContext = createContext<MenuPendingContextValue | null>(null);\n\n/**\n * Whether the surrounding menu has an async item promise in flight —\n * use it to disable sibling items while work is pending.\n * Returns `false` outside a `Menu.Root` / `ContextMenu.Root`.\n */\nexport function useMenuPending(): boolean {\n return useContext(MenuPendingContext)?.pending ?? false;\n}\n\nexport function isThenable(value: unknown): value is Promise<unknown> {\n return (\n value != null && typeof (value as { then?: unknown }).then === \"function\"\n );\n}\n\ntype PreventableMouseEvent = ReactMouseEvent<HTMLElement> & {\n preventBaseUIHandler?: () => void;\n};\n\ntype RootWithPendingProps = {\n onOpenChange?: (open: boolean, eventDetails: { cancel: () => void }) => void;\n actionsRef?: RefObject<RootActions | null>;\n children?: ReactNode;\n};\n\n/**\n * Wraps a Base UI menu root so async `Menu.Item` clicks can lock dismissal\n * (outside press, Escape) until the promise settles.\n */\nexport function withMenuPendingRoot<P extends RootWithPendingProps>(\n BaseRoot: ComponentType<P>,\n): ComponentType<P> {\n function PendingRoot({ onOpenChange, actionsRef, ...props }: P) {\n const [pending, setPending] = useState(false);\n // Guard reads a ref so a resolve → close() in the same tick isn't blocked\n // by a not-yet-flushed state update.\n const pendingRef = useRef(false);\n const internalActionsRef = useRef<RootActions | null>(null);\n\n useEffect(() => {\n if (!actionsRef) return;\n // Proxy so callers always hit the live Base UI actions, even if the\n // popup remounts after this effect runs.\n actionsRef.current = {\n unmount: () => internalActionsRef.current?.unmount(),\n close: () => internalActionsRef.current?.close(),\n };\n return () => {\n actionsRef.current = null;\n };\n }, [actionsRef]);\n\n const handleOpenChange = useCallback(\n (open: boolean, eventDetails: { cancel: () => void }) => {\n if (!open && pendingRef.current) {\n eventDetails.cancel();\n return;\n }\n onOpenChange?.(open, eventDetails);\n },\n [onOpenChange],\n );\n\n const contextValue = useMemo<MenuPendingContextValue>(\n () => ({\n pending,\n beginPending: () => {\n pendingRef.current = true;\n setPending(true);\n },\n endPending: () => {\n pendingRef.current = false;\n setPending(false);\n },\n close: () => internalActionsRef.current?.close(),\n }),\n [pending],\n );\n\n return (\n <MenuPendingContext.Provider value={contextValue}>\n <BaseRoot\n {...(props as P)}\n actionsRef={internalActionsRef}\n onOpenChange={handleOpenChange}\n />\n </MenuPendingContext.Provider>\n );\n }\n\n return PendingRoot as ComponentType<P>;\n}\n\nexport type AsyncMenuItemClick = (\n event: ReactMouseEvent<HTMLElement>,\n) => void | Promise<unknown>;\n\n/**\n * Shared click handler for `Menu.Item` / `ContextMenu.Item` promise support.\n * Returns whether the item should show its loading state.\n */\nexport function useAsyncMenuItem({\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n}: {\n onClick?: AsyncMenuItemClick;\n closeOnClick?: boolean;\n loading?: boolean;\n disabled?: boolean;\n}) {\n const pendingContext = useContext(MenuPendingContext);\n const menuPending = pendingContext?.pending ?? false;\n const [busy, setBusy] = useState(false);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const inert = busy || loading;\n // Disable siblings while any item promise is in flight — not the busy item itself.\n const blocked = disabled || (menuPending && !busy);\n\n function handleClick(event: ReactMouseEvent<HTMLElement>) {\n if (inert || (menuPending && !busy)) {\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n return;\n }\n const result = onClick?.(event);\n if (!isThenable(result)) {\n return;\n }\n // Stop Base UI's closeOnClick for this press; close on resolve instead.\n (event as PreventableMouseEvent).preventBaseUIHandler?.();\n setBusy(true);\n pendingContext?.beginPending();\n result.then(\n () => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (closeOnClick) pendingContext?.close();\n },\n (error: unknown) => {\n pendingContext?.endPending();\n if (mountedRef.current) setBusy(false);\n if (\n typeof process !== \"undefined\" &&\n process.env?.NODE_ENV !== \"production\"\n ) {\n console.error(\"Menu.Item: async onClick rejected —\", error);\n }\n },\n );\n }\n\n return { inert, blocked, handleClick };\n}\n\n/** Menu-row spinner — keeps `data-menu-spinner` for the hide-siblings CSS. */\nexport function MenuItemSpinner() {\n return <Spinner data-menu-spinner=\"\" size=\"md\" />;\n}\n","import {\n type ComponentProps,\n type ElementType,\n type MouseEvent as ReactMouseEvent,\n} from \"react\";\nimport { Menu as BaseMenu } from \"@base-ui/react/menu\";\nimport { Separator as BaseSeparator } from \"@base-ui/react/separator\";\nimport { cn } from \"../../utils/cn\";\nimport { resolveVariant, type VariantMap } from \"../../utils/resolve-variant\";\nimport {\n MenuItemSpinner,\n useAsyncMenuItem,\n useMenuPending,\n withMenuPendingRoot,\n type AsyncMenuItemClick,\n} from \"./menu-pending\";\n\nexport { useMenuPending };\n\n/** Override Base UI's `string | (state) => string` className with plain string. */\ntype StyledProps<T extends ElementType> = Omit<\n ComponentProps<T>,\n \"className\" | \"color\"\n> & {\n className?: string;\n};\n\n/** Shared popup + item styles, reused by `ContextMenu` and `Menubar` menus. */\n// Floating layer: keeps its elevation shadow, sits on the primary surface\n// with the site's hairline (ring-pho-secondary, #eee/#202020) as its edge.\n// `base` radius + p-1.5 inset — the highlight floats 6px clear of the edge.\nexport const MENU_POPUP =\n \"bg-pho-primary text-pho-primary shadow-pho-lg ring-pho-secondary origin-[var(--transform-origin)] min-w-44 rounded-base p-1.5 ring-1 ring-inset outline-none transition-[transform,opacity] duration-[330ms] ease-pho-spring data-[ending-style]:duration-150 data-[ending-style]:ease-out data-[starting-style]:scale-95 data-[starting-style]:opacity-0 data-[ending-style]:scale-95 data-[ending-style]:opacity-0\";\n// Rest at secondary; highlighted lifts to primary with the quiet ghost wash.\n// Icons use `text-current` so they follow the row ink (including destructive).\n// While loading, the spinner is first; hide the rest of the leading icons.\n// Concentric correction (see /scale): row radius = popup radius − popup\n// padding (base − 6px). py-2 + 20px line-height lands the 36px row height.\nexport const MENU_ITEM =\n \"relative flex cursor-pointer items-center gap-2 rounded-[calc(var(--radius-base)-6px)] py-2 pr-2.5 pl-2.5 text-base text-pho-secondary outline-none select-none data-[highlighted]:bg-pho-ghost-hover data-[highlighted]:text-pho-primary data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-loading:data-[disabled]:opacity-100 data-loading:[&>svg:not([data-menu-spinner])]:hidden [&>svg]:size-4 [&>svg]:text-current\";\n\n/** Semantic ink for `Menu.Item` / `LinkItem` / `ContextMenu.Item`. */\nexport const MENU_ITEM_COLORS = {\n default: { classes: \"\", description: \"Secondary → primary on highlight\" },\n destructive: {\n classes:\n \"text-pho-error data-[highlighted]:bg-pho-error-primary data-[highlighted]:text-pho-error\",\n description: \"Error ink + error wash on highlight\",\n },\n} satisfies VariantMap;\n\nexport type MenuItemColor = keyof typeof MENU_ITEM_COLORS;\n\ntype ItemColorProps = {\n /** Semantic ink. @default \"default\" */\n color?: MenuItemColor;\n};\n\nexport type MenuItemProps = Omit<StyledProps<typeof BaseMenu.Item>, \"onClick\"> &\n ItemColorProps & {\n /**\n * Action handler. Return a promise to keep the menu open while it runs:\n * the item shows a spinner, dismissal is locked, and the menu closes when\n * the promise resolves (if `closeOnClick`). On rejection the menu stays\n * open — surface errors inside the handler.\n */\n onClick?: AsyncMenuItemClick;\n /**\n * Declarative loading state (same visual as an in-flight promise).\n * @default false\n */\n loading?: boolean;\n };\n\nconst Root = withMenuPendingRoot(BaseMenu.Root);\nconst Trigger = BaseMenu.Trigger;\nconst Portal = BaseMenu.Portal;\nconst Group = BaseMenu.Group;\nconst RadioGroup = BaseMenu.RadioGroup;\n// Don't wrap SubmenuRoot — nested pending context would shadow the parent\n// Root and fail to lock dismissal for async items inside a submenu.\nconst SubmenuRoot = BaseMenu.SubmenuRoot;\n\nfunction Positioner({\n className,\n sideOffset = 6,\n ...props\n}: StyledProps<typeof BaseMenu.Positioner>) {\n return (\n <BaseMenu.Positioner\n sideOffset={sideOffset}\n className={cn(\"z-50\", className)}\n {...props}\n />\n );\n}\n\nfunction Popup({ className, ...props }: StyledProps<typeof BaseMenu.Popup>) {\n return <BaseMenu.Popup className={cn(MENU_POPUP, className)} {...props} />;\n}\n\nfunction Item({\n className,\n color = \"default\",\n onClick,\n closeOnClick = true,\n loading = false,\n disabled = false,\n children,\n ...props\n}: MenuItemProps) {\n const { inert, blocked, handleClick } = useAsyncMenuItem({\n onClick,\n closeOnClick,\n loading,\n disabled,\n });\n\n return (\n <BaseMenu.Item\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n closeOnClick={closeOnClick}\n disabled={blocked}\n data-loading={inert || undefined}\n aria-busy={inert || undefined}\n onClick={handleClick as (event: ReactMouseEvent<HTMLElement>) => void}\n {...props}\n >\n {inert ? (\n <>\n <MenuItemSpinner />\n <output className=\"sr-only\" aria-live=\"polite\" />\n </>\n ) : null}\n {children}\n </BaseMenu.Item>\n );\n}\n\nfunction LinkItem({\n className,\n color = \"default\",\n ...props\n}: StyledProps<typeof BaseMenu.LinkItem> & ItemColorProps) {\n return (\n <BaseMenu.LinkItem\n className={cn(\n MENU_ITEM,\n resolveVariant(MENU_ITEM_COLORS, color),\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction GroupLabel({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.GroupLabel>) {\n return (\n <BaseMenu.GroupLabel\n className={cn(\n \"text-pho-secondary px-2.5 py-1.5 text-xs font-medium\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction Separator({ className, ...props }: StyledProps<typeof BaseSeparator>) {\n return (\n <BaseSeparator\n className={cn(\n \"-mx-1.5 my-1.5 h-px bg-[var(--border-color-pho-secondary)]\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction SubmenuTrigger({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.SubmenuTrigger>) {\n return (\n <BaseMenu.SubmenuTrigger\n className={cn(\n MENU_ITEM,\n \"data-[popup-open]:bg-pho-ghost-hover\",\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction CheckboxItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItem>) {\n return (\n <BaseMenu.CheckboxItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nfunction RadioItem({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItem>) {\n return (\n <BaseMenu.RadioItem\n className={cn(MENU_ITEM, \"pl-8\", className)}\n {...props}\n />\n );\n}\n\nconst CHECK_INDICATOR =\n \"text-pho-brand absolute left-2.5 flex items-center [&>svg]:size-4\";\n\nfunction CheckboxItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.CheckboxItemIndicator>) {\n return (\n <BaseMenu.CheckboxItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\nfunction RadioItemIndicator({\n className,\n ...props\n}: StyledProps<typeof BaseMenu.RadioItemIndicator>) {\n return (\n <BaseMenu.RadioItemIndicator\n className={cn(CHECK_INDICATOR, className)}\n {...props}\n />\n );\n}\n\n/**\n * Dropdown menu built on Base UI `Menu` — a list of actions opened from a\n * trigger. Supports grouped items, separators, submenus\n * (`SubmenuRoot` + `SubmenuTrigger`), and stateful `CheckboxItem` /\n * `RadioItem`s. `Menu.Item` has native promise support (spinner + locked\n * dismissal while pending). For a right-click menu use `ContextMenu`.\n */\nexport const Menu = {\n Root,\n Trigger,\n Portal,\n Positioner,\n Popup,\n Item,\n LinkItem,\n Group,\n GroupLabel,\n Separator,\n SubmenuRoot,\n SubmenuTrigger,\n CheckboxItem,\n CheckboxItemIndicator,\n RadioGroup,\n RadioItem,\n RadioItemIndicator,\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use client";
|
|
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
|
+
import { jsx as l, jsxs as x } from "react/jsx-runtime";
|
|
5
|
+
import { motion as C } from "motion/react";
|
|
6
|
+
import { ScrollArea as c } from "@base-ui/react/scroll-area";
|
|
7
|
+
var L = "pointer-events-none absolute inset-x-0 z-10 h-8", $ = 8, q = 1;
|
|
8
|
+
function N(t, e) {
|
|
9
|
+
const a = 100 / (t + 1);
|
|
10
|
+
return Array.from({ length: t }, (r, o) => ({
|
|
11
|
+
blurPx: o / (t - 1) * e,
|
|
12
|
+
window: [
|
|
13
|
+
o * a,
|
|
14
|
+
(o + 1) * a,
|
|
15
|
+
(o + 2) * a,
|
|
16
|
+
(o + 3) * a
|
|
17
|
+
]
|
|
18
|
+
})).filter((r) => r.blurPx > 0);
|
|
19
|
+
}
|
|
20
|
+
var _ = N($, 1);
|
|
21
|
+
function T(t, e) {
|
|
22
|
+
const a = t === "top" ? "to top" : "to bottom", [r, o, i, n] = e;
|
|
23
|
+
return `linear-gradient(${a}, transparent ${r}%, black ${o}%, black ${i}%, transparent ${n}%)`;
|
|
24
|
+
}
|
|
25
|
+
var z = "var(--pho-surface, var(--background-color-pho-primary))";
|
|
26
|
+
function F(t, e) {
|
|
27
|
+
return `linear-gradient(${t === "top" ? "to top" : "to bottom"}, color-mix(in oklab, ${e} 20%, transparent) 0%, ${e} 100%)`;
|
|
28
|
+
}
|
|
29
|
+
function h(t, e) {
|
|
30
|
+
if (t)
|
|
31
|
+
for (const a of t.children) {
|
|
32
|
+
const r = a;
|
|
33
|
+
if (r.dataset.scrollFade === "wash") {
|
|
34
|
+
r.style.opacity = String(e);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const o = `blur(${Number(r.dataset.blurPx) * e}px)`;
|
|
38
|
+
r.style.backdropFilter = o, r.style.setProperty("-webkit-backdrop-filter", o);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function g({ edge: t, edgeRef: e, surface: a = z, className: r }) {
|
|
42
|
+
return /* @__PURE__ */ x("div", {
|
|
43
|
+
ref: e,
|
|
44
|
+
"aria-hidden": !0,
|
|
45
|
+
"data-edge": t,
|
|
46
|
+
className: d(L, r ?? (t === "top" ? "top-0" : "bottom-0")),
|
|
47
|
+
children: [_.map(({ blurPx: o, window: i }, n) => {
|
|
48
|
+
const s = T(t, i);
|
|
49
|
+
return /* @__PURE__ */ l("div", {
|
|
50
|
+
"data-blur-px": o,
|
|
51
|
+
className: "absolute inset-0",
|
|
52
|
+
style: {
|
|
53
|
+
maskImage: s,
|
|
54
|
+
WebkitMaskImage: s
|
|
55
|
+
}
|
|
56
|
+
}, n);
|
|
57
|
+
}), /* @__PURE__ */ l("div", {
|
|
58
|
+
"data-scroll-fade": "wash",
|
|
59
|
+
className: "absolute inset-0 opacity-0",
|
|
60
|
+
style: { background: F(t, a) }
|
|
61
|
+
})]
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
var E = S(null);
|
|
65
|
+
function P() {
|
|
66
|
+
const t = A(E);
|
|
67
|
+
if (t == null) throw new Error("ScrollArea parts must be used inside ScrollArea.Root");
|
|
68
|
+
return t;
|
|
69
|
+
}
|
|
70
|
+
function y(t) {
|
|
71
|
+
return t <= 0 ? 0 : t >= 1 ? 1 : t;
|
|
72
|
+
}
|
|
73
|
+
function U(t, e) {
|
|
74
|
+
typeof t == "function" ? t(e) : t && (t.current = e);
|
|
75
|
+
}
|
|
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;
|
|
88
|
+
function M({ className: t, children: e, ...a }) {
|
|
89
|
+
const r = m(null), o = m(null);
|
|
90
|
+
return /* @__PURE__ */ l(c.Root, {
|
|
91
|
+
className: d("relative overflow-hidden", t),
|
|
92
|
+
...a,
|
|
93
|
+
children: /* @__PURE__ */ x(E.Provider, {
|
|
94
|
+
value: {
|
|
95
|
+
topEl: r,
|
|
96
|
+
bottomEl: o
|
|
97
|
+
},
|
|
98
|
+
children: [
|
|
99
|
+
e,
|
|
100
|
+
/* @__PURE__ */ l(g, {
|
|
101
|
+
edge: "top",
|
|
102
|
+
edgeRef: r
|
|
103
|
+
}),
|
|
104
|
+
/* @__PURE__ */ l(g, {
|
|
105
|
+
edge: "bottom",
|
|
106
|
+
edgeRef: o
|
|
107
|
+
})
|
|
108
|
+
]
|
|
109
|
+
})
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function O({ className: t, ref: e, ...a }) {
|
|
113
|
+
const { topEl: r, bottomEl: o } = P(), i = m(null);
|
|
114
|
+
return k(() => {
|
|
115
|
+
const n = i.current;
|
|
116
|
+
if (!n) return;
|
|
117
|
+
const s = () => {
|
|
118
|
+
const v = r.current?.offsetHeight || o.current?.offsetHeight || 32, p = n.scrollHeight - n.clientHeight, w = p <= 0 ? 0 : y(n.scrollTop / v), R = p <= 0 ? 0 : y((p - n.scrollTop) / v);
|
|
119
|
+
h(r.current, w), h(o.current, R);
|
|
120
|
+
};
|
|
121
|
+
let u = 0;
|
|
122
|
+
const b = () => {
|
|
123
|
+
u || (u = requestAnimationFrame(() => {
|
|
124
|
+
u = 0, s();
|
|
125
|
+
}));
|
|
126
|
+
};
|
|
127
|
+
s(), n.addEventListener("scroll", b, { passive: !0 });
|
|
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();
|
|
131
|
+
};
|
|
132
|
+
}, [r, o]), /* @__PURE__ */ l(c.Viewport, {
|
|
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),
|
|
135
|
+
...a,
|
|
136
|
+
ref: (n) => {
|
|
137
|
+
i.current = n, U(e, n);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function j({ className: t, ...e }) {
|
|
142
|
+
return /* @__PURE__ */ l(c.Scrollbar, {
|
|
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),
|
|
144
|
+
...e
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function D({ className: t, ...e }) {
|
|
148
|
+
return /* @__PURE__ */ l(c.Thumb, {
|
|
149
|
+
className: d("flex-1 rounded-full bg-[var(--border-color-pho-primary)]", t),
|
|
150
|
+
...e
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
var W = {
|
|
154
|
+
Root: M,
|
|
155
|
+
Viewport: O,
|
|
156
|
+
Content: B,
|
|
157
|
+
Scrollbar: j,
|
|
158
|
+
Thumb: D,
|
|
159
|
+
Corner: H
|
|
160
|
+
};
|
|
161
|
+
export {
|
|
162
|
+
q as n,
|
|
163
|
+
W as t
|
|
164
|
+
};
|
|
165
|
+
|
|
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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"}
|