@photon-ai/pho-ui 1.0.0 → 1.1.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/{close-button-8f-oRJXt.js → close-button-Bq9f-H15.js} +256 -181
- package/dist/chunks/close-button-Bq9f-H15.js.map +1 -0
- package/dist/chunks/{dialog-Bgb2Z1_v.js → dialog-DolPKFGS.js} +2 -2
- package/dist/chunks/{dialog-Bgb2Z1_v.js.map → dialog-DolPKFGS.js.map} +1 -1
- package/dist/chunks/{menu-COLB_liS.js → menu-Dan0oRvL.js} +79 -76
- package/dist/chunks/menu-Dan0oRvL.js.map +1 -0
- package/dist/chunks/settings-B3ilbRcF.js +104 -0
- package/dist/chunks/settings-B3ilbRcF.js.map +1 -0
- package/dist/chunks/{sidebar-16Pfn7IC.js → sidebar-C5cIhWs1.js} +4 -4
- package/dist/chunks/sidebar-C5cIhWs1.js.map +1 -0
- package/dist/components/autocomplete.js +1 -1
- package/dist/components/autocomplete.js.map +1 -1
- package/dist/components/button.js +1 -1
- package/dist/components/combobox.js +1 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/context-menu.js +52 -49
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/dialog.js +1 -1
- package/dist/components/menu.js +1 -1
- package/dist/components/select.js +16 -16
- package/dist/components/select.js.map +1 -1
- package/dist/components/skeleton.js +18 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/theme-toggle.js +22 -18
- package/dist/components/theme-toggle.js.map +1 -1
- package/dist/index.js +21 -17
- package/dist/sections/settings.js +7 -0
- package/dist/sections/sidebar.js +1 -1
- package/dist/src/components/skeleton/index.d.ts +1 -0
- package/dist/src/components/skeleton/skeleton.d.ts +11 -0
- package/dist/src/components/theme-toggle/theme-toggle.d.ts +4 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/sections/settings/index.d.ts +1 -0
- package/dist/src/sections/settings/settings.d.ts +128 -0
- package/dist/src/sections/sidebar/sidebar.d.ts +5 -4
- package/package.json +9 -1
- package/src/styles/theme.css +13 -2
- package/dist/chunks/close-button-8f-oRJXt.js.map +0 -1
- package/dist/chunks/menu-COLB_liS.js.map +0 -1
- package/dist/chunks/sidebar-16Pfn7IC.js.map +0 -1
|
@@ -29,7 +29,7 @@ var j = [
|
|
|
29
29
|
}], ["path", {
|
|
30
30
|
d: "M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7",
|
|
31
31
|
key: "svg-1"
|
|
32
|
-
}]], Z = y("outline", "sun", "Sun", Y), b = "pho-mode",
|
|
32
|
+
}]], Z = y("outline", "sun", "Sun", Y), b = "pho-mode", s = "pho-themechange", p = [
|
|
33
33
|
{
|
|
34
34
|
value: "device",
|
|
35
35
|
label: "Device",
|
|
@@ -46,6 +46,10 @@ var j = [
|
|
|
46
46
|
Icon: V
|
|
47
47
|
}
|
|
48
48
|
], B = {
|
|
49
|
+
xs: {
|
|
50
|
+
track: "gap-px rounded-base p-0.5",
|
|
51
|
+
item: "size-5.5 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3"
|
|
52
|
+
},
|
|
49
53
|
sm: {
|
|
50
54
|
track: "gap-0.5 rounded-base p-0.5",
|
|
51
55
|
item: "size-7 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3.5"
|
|
@@ -68,14 +72,14 @@ function M(e, n = typeof window < "u" ? window.matchMedia("(prefers-color-scheme
|
|
|
68
72
|
function l(e) {
|
|
69
73
|
return typeof e == "function" ? e() : e !== void 0 ? e : typeof document > "u" ? null : document.documentElement;
|
|
70
74
|
}
|
|
71
|
-
var
|
|
75
|
+
var u = null, g = null, k = /* @__PURE__ */ new Map();
|
|
72
76
|
function w(e) {
|
|
73
77
|
if (e == null || typeof localStorage > "u") return null;
|
|
74
78
|
const n = localStorage.getItem(e);
|
|
75
79
|
return n == null ? null : F(n);
|
|
76
80
|
}
|
|
77
81
|
function J(e = b) {
|
|
78
|
-
return
|
|
82
|
+
return u ?? w(e) ?? "device";
|
|
79
83
|
}
|
|
80
84
|
function re(e = typeof document > "u" ? null : document.documentElement) {
|
|
81
85
|
return e?.dataset.mode === "dark" ? "dark" : "light";
|
|
@@ -86,7 +90,7 @@ function T(e, n) {
|
|
|
86
90
|
function Q(e) {
|
|
87
91
|
if (g?.(), g = null, typeof window > "u" || J(e) !== "device") return;
|
|
88
92
|
const n = window.matchMedia("(prefers-color-scheme: dark)"), r = () => {
|
|
89
|
-
T(M("device", n), document.documentElement), window.dispatchEvent(new Event(
|
|
93
|
+
T(M("device", n), document.documentElement), window.dispatchEvent(new Event(s));
|
|
90
94
|
};
|
|
91
95
|
n.addEventListener("change", r), g = () => n.removeEventListener("change", r);
|
|
92
96
|
}
|
|
@@ -106,11 +110,11 @@ function E(e, n) {
|
|
|
106
110
|
t && W(t, e);
|
|
107
111
|
return;
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
u = e, r != null && typeof localStorage < "u" && localStorage.setItem(r, e), Q(r ?? null), typeof window < "u" && window.dispatchEvent(new Event(s));
|
|
110
114
|
}
|
|
111
115
|
function X(e) {
|
|
112
116
|
return typeof window > "u" ? () => {
|
|
113
|
-
} : (window.addEventListener(
|
|
117
|
+
} : (window.addEventListener(s, e), () => window.removeEventListener(s, e));
|
|
114
118
|
}
|
|
115
119
|
function D(e, n) {
|
|
116
120
|
n.modeProp === void 0 && E(e, {
|
|
@@ -119,9 +123,9 @@ function D(e, n) {
|
|
|
119
123
|
}), n.onModeChange?.(e);
|
|
120
124
|
}
|
|
121
125
|
function K({ mode: e, defaultMode: n = "device", onModeChange: r, storageKey: t = b, target: i, size: z = "md", className: A, "aria-label": C = "Theme", ...H }) {
|
|
122
|
-
const O = $(X, () =>
|
|
126
|
+
const O = $(X, () => u ?? w(t) ?? n, () => n);
|
|
123
127
|
v(() => {
|
|
124
|
-
e === void 0 && E(
|
|
128
|
+
e === void 0 && E(u ?? w(t) ?? n, {
|
|
125
129
|
target: l(i),
|
|
126
130
|
storageKey: t
|
|
127
131
|
});
|
|
@@ -140,8 +144,8 @@ function K({ mode: e, defaultMode: n = "device", onModeChange: r, storageKey: t
|
|
|
140
144
|
t,
|
|
141
145
|
i
|
|
142
146
|
]), v(() => () => {
|
|
143
|
-
const
|
|
144
|
-
|
|
147
|
+
const d = l(i);
|
|
148
|
+
d && d !== document.documentElement && N(d);
|
|
145
149
|
}, [i]);
|
|
146
150
|
const x = e ?? O, S = B[z];
|
|
147
151
|
return /* @__PURE__ */ h("div", {
|
|
@@ -149,31 +153,31 @@ function K({ mode: e, defaultMode: n = "device", onModeChange: r, storageKey: t
|
|
|
149
153
|
"aria-label": C,
|
|
150
154
|
...H,
|
|
151
155
|
className: _("ring-pho-interactive inline-flex items-center ring-1 ring-inset", S.track, A),
|
|
152
|
-
children: p.map(({ value:
|
|
153
|
-
const f = x ===
|
|
156
|
+
children: p.map(({ value: d, label: R, Icon: q }) => {
|
|
157
|
+
const f = x === d;
|
|
154
158
|
return /* @__PURE__ */ h("button", {
|
|
155
159
|
type: "button",
|
|
156
160
|
role: "radio",
|
|
157
161
|
"aria-checked": f,
|
|
158
162
|
"aria-label": R,
|
|
159
163
|
tabIndex: f ? 0 : -1,
|
|
160
|
-
onClick: () => D(
|
|
164
|
+
onClick: () => D(d, {
|
|
161
165
|
modeProp: e,
|
|
162
166
|
storageKey: t,
|
|
163
167
|
target: i,
|
|
164
168
|
onModeChange: r
|
|
165
169
|
}),
|
|
166
|
-
onKeyDown: (
|
|
170
|
+
onKeyDown: (a) => {
|
|
167
171
|
const o = p.map((m) => m.value), L = o.indexOf(x);
|
|
168
172
|
let c;
|
|
169
|
-
if (
|
|
170
|
-
|
|
173
|
+
if (a.key === "ArrowRight" || a.key === "ArrowDown" ? c = o[(L + 1) % o.length] : a.key === "ArrowLeft" || a.key === "ArrowUp" ? c = o[(L - 1 + o.length) % o.length] : a.key === "Home" ? c = o[0] : a.key === "End" && (c = o[o.length - 1]), !c) return;
|
|
174
|
+
a.preventDefault(), D(c, {
|
|
171
175
|
modeProp: e,
|
|
172
176
|
storageKey: t,
|
|
173
177
|
target: i,
|
|
174
178
|
onModeChange: r
|
|
175
179
|
});
|
|
176
|
-
const I =
|
|
180
|
+
const I = a.currentTarget.parentElement?.querySelector(`[aria-label="${p.find((m) => m.value === c)?.label}"]`);
|
|
177
181
|
I instanceof HTMLElement && I.focus();
|
|
178
182
|
},
|
|
179
183
|
className: _("outline-pho-brand inline-flex flex-1 cursor-pointer items-center justify-center transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2", f ? "bg-pho-inverse text-pho-inverse" : "text-pho-description hover:text-pho-primary", S.item),
|
|
@@ -181,7 +185,7 @@ function K({ mode: e, defaultMode: n = "device", onModeChange: r, storageKey: t
|
|
|
181
185
|
"aria-hidden": "true",
|
|
182
186
|
className: "shrink-0"
|
|
183
187
|
})
|
|
184
|
-
},
|
|
188
|
+
}, d);
|
|
185
189
|
})
|
|
186
190
|
});
|
|
187
191
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-toggle.js","names":["__iconNode","__iconNode"],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconDeviceDesktop.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconMoon.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconSun.mjs","../../src/components/theme-toggle/theme-toggle.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.44.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\": \"M3 5a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M7 20h10\", \"key\": \"svg-1\" }], [\"path\", { \"d\": \"M9 16v4\", \"key\": \"svg-2\" }], [\"path\", { \"d\": \"M15 16v4\", \"key\": \"svg-3\" }]];\nconst IconDeviceDesktop = createReactComponent(\"outline\", \"device-desktop\", \"DeviceDesktop\", __iconNode);\n\nexport { __iconNode, IconDeviceDesktop as default };\n//# sourceMappingURL=IconDeviceDesktop.mjs.map\n","/**\n * @license @tabler/icons-react v3.44.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\": \"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008\", \"key\": \"svg-0\" }]];\nconst IconMoon = createReactComponent(\"outline\", \"moon\", \"Moon\", __iconNode);\n\nexport { __iconNode, IconMoon as default };\n//# sourceMappingURL=IconMoon.mjs.map\n","/**\n * @license @tabler/icons-react v3.44.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\": \"M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7\", \"key\": \"svg-1\" }]];\nconst IconSun = createReactComponent(\"outline\", \"sun\", \"Sun\", __iconNode);\n\nexport { __iconNode, IconSun as default };\n//# sourceMappingURL=IconSun.mjs.map\n","import { useEffect, useSyncExternalStore, type HTMLAttributes } from \"react\";\nimport { IconDeviceDesktop, IconMoon, IconSun } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Appearance preference — `device` follows `prefers-color-scheme`; `light` /\n * `dark` force the scheme via `data-mode`.\n */\nexport type ThemeMode = \"device\" | \"light\" | \"dark\";\n\n/** Resolved color-scheme after applying a preference. */\nexport type ResolvedTheme = \"light\" | \"dark\";\n\n/** Default `localStorage` key used by the docs site and this component. */\nexport const THEME_STORAGE_KEY = \"pho-mode\";\n\nconst THEME_CHANGE_EVENT = \"pho-themechange\";\n\nconst OPTIONS = [\n { value: \"device\" as const, label: \"Device\", Icon: IconDeviceDesktop },\n { value: \"light\" as const, label: \"Light\", Icon: IconSun },\n { value: \"dark\" as const, label: \"Dark\", Icon: IconMoon },\n];\n\n// Concentric correction (see /scale): inner = outer − gap, derived from the\n// radius token with calc so it tracks theme changes. Track is `base`; the gap\n// is the track padding (sm/md p-0.5 = 2px, lg p-1 = 4px). Plain round, no\n// squircle (the track carries a ring-inset).\nconst THEME_SIZES = {\n sm: {\n track: \"gap-0.5 rounded-base p-0.5\",\n item: \"size-7 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3.5\",\n },\n md: {\n track: \"gap-0.5 rounded-base p-0.5\",\n item: \"size-8 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-4\",\n },\n lg: {\n track: \"gap-1 rounded-base p-1\",\n item: \"size-9 rounded-[calc(var(--radius-base)-4px)] [&>svg]:size-5\",\n },\n} as const;\n\nexport type ThemeToggleSize = keyof typeof THEME_SIZES;\n\nexport interface ThemeToggleProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\" | \"color\" | \"onChange\" | \"defaultValue\"\n> {\n /** Controlled preference. */\n mode?: ThemeMode;\n /** Uncontrolled initial preference. @default \"device\" */\n defaultMode?: ThemeMode;\n /** Fired when the user picks a segment. */\n onModeChange?: (mode: ThemeMode) => void;\n /**\n * Persist the preference under this `localStorage` key. Pass `null` to skip\n * persistence. @default \"pho-mode\"\n */\n storageKey?: string | null;\n /**\n * Element that receives `data-mode=\"dark\"`. Defaults to\n * `document.documentElement`.\n */\n target?: HTMLElement | null | (() => HTMLElement | null);\n /** Size variant. @default \"md\" */\n size?: ThemeToggleSize;\n}\n\n/** Parse a stored preference; unknown / missing values fall back to `device`. */\nexport function parseThemeMode(value: string | null | undefined): ThemeMode {\n if (value === \"light\" || value === \"dark\" || value === \"device\") return value;\n return \"device\";\n}\n\n/** Resolve a preference against the OS color-scheme. */\nexport function resolveThemeMode(\n mode: ThemeMode,\n query: { matches: boolean } = typeof window !== \"undefined\"\n ? window.matchMedia(\"(prefers-color-scheme: dark)\")\n : { matches: false },\n): ResolvedTheme {\n if (mode === \"light\" || mode === \"dark\") return mode;\n return query.matches ? \"dark\" : \"light\";\n}\n\nfunction resolveTarget(\n target?: ThemeToggleProps[\"target\"],\n): HTMLElement | null {\n if (typeof target === \"function\") return target();\n if (target !== undefined) return target;\n return typeof document === \"undefined\" ? null : document.documentElement;\n}\n\n/** Preference mirrored across document-level ThemeToggle instances. */\nlet storedPreference: ThemeMode | null = null;\nlet mediaCleanup: (() => void) | null = null;\n/** Per-target `prefers-color-scheme` listeners (scoped `target` instances). */\nconst targetMediaCleanups = new Map<HTMLElement, () => void>();\n\n/** Drop in-memory preference + media listener (tests / full remounts). */\nexport function clearThemePreference() {\n storedPreference = null;\n mediaCleanup?.();\n mediaCleanup = null;\n for (const cleanup of targetMediaCleanups.values()) cleanup();\n targetMediaCleanups.clear();\n}\n\n/** Read a persisted preference, or `null` when nothing is stored. */\nfunction readStoredPreference(storageKey: string | null): ThemeMode | null {\n if (storageKey == null || typeof localStorage === \"undefined\") return null;\n const raw = localStorage.getItem(storageKey);\n if (raw == null) return null;\n return parseThemeMode(raw);\n}\n\n/** Current document preference (memory, then storage, else `device`). */\nexport function getThemePreference(\n storageKey: string | null = THEME_STORAGE_KEY,\n): ThemeMode {\n return storedPreference ?? readStoredPreference(storageKey) ?? \"device\";\n}\n\n/** Read the resolved light/dark mode from an element (or `<html>`). */\nexport function getThemeMode(\n target: HTMLElement | null = typeof document === \"undefined\"\n ? null\n : document.documentElement,\n): ResolvedTheme {\n return target?.dataset.mode === \"dark\" ? \"dark\" : \"light\";\n}\n\nfunction applyResolved(resolved: ResolvedTheme, target: HTMLElement | null) {\n if (!target) return;\n if (resolved === \"dark\") target.dataset.mode = \"dark\";\n else delete target.dataset.mode;\n}\n\nfunction bindMediaListener(storageKey: string | null) {\n mediaCleanup?.();\n mediaCleanup = null;\n if (typeof window === \"undefined\") return;\n if (getThemePreference(storageKey) !== \"device\") return;\n\n const mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const onChange = () => {\n applyResolved(resolveThemeMode(\"device\", mql), document.documentElement);\n window.dispatchEvent(new Event(THEME_CHANGE_EVENT));\n };\n mql.addEventListener(\"change\", onChange);\n mediaCleanup = () => mql.removeEventListener(\"change\", onChange);\n}\n\n/** Track OS scheme changes for a scoped target (docs previews, etc.). */\nfunction bindTargetMediaListener(target: HTMLElement, mode: ThemeMode) {\n unbindTargetMediaListener(target);\n if (typeof window === \"undefined\" || mode !== \"device\") return;\n\n const mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const onChange = () => {\n applyResolved(resolveThemeMode(\"device\", mql), target);\n };\n mql.addEventListener(\"change\", onChange);\n targetMediaCleanups.set(target, () =>\n mql.removeEventListener(\"change\", onChange),\n );\n}\n\nfunction unbindTargetMediaListener(target: HTMLElement | null) {\n if (!target) return;\n targetMediaCleanups.get(target)?.();\n targetMediaCleanups.delete(target);\n}\n\n/**\n * Apply an appearance preference: persist it, resolve against the OS when\n * `device`, set `data-mode` on the target, and notify document-level toggles.\n */\nexport function setThemeMode(\n mode: ThemeMode,\n options?: {\n target?: HTMLElement | null;\n storageKey?: string | null;\n },\n): void {\n const storageKey =\n options && \"storageKey\" in options ? options.storageKey : THEME_STORAGE_KEY;\n const target =\n options?.target ??\n (typeof document === \"undefined\" ? null : document.documentElement);\n const syncDocument =\n typeof document === \"undefined\" ||\n target == null ||\n target === document.documentElement;\n\n applyResolved(resolveThemeMode(mode), target);\n\n if (!syncDocument) {\n if (target) bindTargetMediaListener(target, mode);\n return;\n }\n\n storedPreference = mode;\n if (storageKey != null && typeof localStorage !== \"undefined\") {\n localStorage.setItem(storageKey, mode);\n }\n bindMediaListener(storageKey ?? null);\n\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(new Event(THEME_CHANGE_EVENT));\n }\n}\n\nfunction subscribeTheme(onChange: () => void) {\n if (typeof window === \"undefined\") return () => {};\n window.addEventListener(THEME_CHANGE_EVENT, onChange);\n return () => window.removeEventListener(THEME_CHANGE_EVENT, onChange);\n}\n\nfunction selectMode(\n value: ThemeMode,\n options: {\n modeProp?: ThemeMode;\n storageKey: string | null;\n target: ThemeToggleProps[\"target\"];\n onModeChange?: (mode: ThemeMode) => void;\n },\n) {\n if (options.modeProp === undefined) {\n setThemeMode(value, {\n target: resolveTarget(options.target),\n storageKey: options.storageKey,\n });\n }\n options.onModeChange?.(value);\n}\n\n/**\n * Segmented appearance control — Device / Light / Dark. Forces\n * `data-mode=\"dark\"` (or clears it) so Pho's `light-dark()` tokens follow\n * `color-scheme`. `device` tracks `prefers-color-scheme`. Persists under\n * `pho-mode` by default; pair with a blocking `<head>` script to avoid a\n * flash (see docs).\n */\nexport function ThemeToggle({\n mode: modeProp,\n defaultMode = \"device\",\n onModeChange,\n storageKey = THEME_STORAGE_KEY,\n target,\n size = \"md\",\n className,\n \"aria-label\": ariaLabel = \"Theme\",\n ...props\n}: ThemeToggleProps) {\n const storeMode = useSyncExternalStore(\n subscribeTheme,\n () => storedPreference ?? readStoredPreference(storageKey) ?? defaultMode,\n () => defaultMode,\n );\n\n // Uncontrolled — hydrate from storage (or `defaultMode`) on mount.\n useEffect(() => {\n if (modeProp !== undefined) return;\n const initial =\n storedPreference ?? readStoredPreference(storageKey) ?? defaultMode;\n setThemeMode(initial, {\n target: resolveTarget(target),\n storageKey,\n });\n }, [modeProp, storageKey, target, defaultMode]);\n\n // Controlled — keep the target in sync with the prop.\n useEffect(() => {\n if (modeProp === undefined) return;\n setThemeMode(modeProp, {\n target: resolveTarget(target),\n storageKey,\n });\n }, [modeProp, storageKey, target]);\n\n // Drop scoped-target media listeners on unmount (document listener is shared).\n useEffect(() => {\n return () => {\n const el = resolveTarget(target);\n if (el && el !== document.documentElement) {\n unbindTargetMediaListener(el);\n }\n };\n }, [target]);\n\n const mode = modeProp ?? storeMode;\n const sizes = THEME_SIZES[size];\n\n return (\n // Segmented control: styled buttons with radiogroup semantics. Native\n // `<input type=\"radio\">` cannot express the inverse-chip track layout.\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n {...props}\n className={cn(\n \"ring-pho-interactive inline-flex items-center ring-1 ring-inset\",\n sizes.track,\n className,\n )}\n >\n {OPTIONS.map(({ value, label, Icon }) => {\n const selected = mode === value;\n return (\n <button\n key={value}\n type=\"button\"\n role=\"radio\" // oxlint-disable-line jsx-a11y/prefer-tag-over-role -- segment needs button chrome\n aria-checked={selected}\n aria-label={label}\n tabIndex={selected ? 0 : -1}\n onClick={() =>\n selectMode(value, { modeProp, storageKey, target, onModeChange })\n }\n onKeyDown={(event) => {\n const order = OPTIONS.map((o) => o.value);\n const index = order.indexOf(mode);\n let next: ThemeMode | undefined;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = order[(index + 1) % order.length];\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = order[(index - 1 + order.length) % order.length];\n } else if (event.key === \"Home\") {\n next = order[0];\n } else if (event.key === \"End\") {\n next = order[order.length - 1];\n }\n if (!next) return;\n event.preventDefault();\n selectMode(next, { modeProp, storageKey, target, onModeChange });\n const el = event.currentTarget.parentElement?.querySelector(\n `[aria-label=\"${OPTIONS.find((o) => o.value === next)?.label}\"]`,\n );\n if (el instanceof HTMLElement) el.focus();\n }}\n className={cn(\n \"outline-pho-brand inline-flex flex-1 cursor-pointer items-center justify-center transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2\",\n // No hover wash — in a segmented control the only filled element\n // is the selected chip. Unselected segments rest at the dim\n // description ink and jump to primary on hover for a clear cue.\n selected\n ? \"bg-pho-inverse text-pho-inverse\"\n : \"text-pho-description hover:text-pho-primary\",\n sizes.item,\n )}\n >\n <Icon aria-hidden=\"true\" className=\"shrink-0\" />\n </button>\n );\n })}\n </div>\n );\n}\n\nThemeToggle.displayName = \"ThemeToggle\";\n"],"x_google_ignoreList":[0,1,2],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme-toggle.js","names":["__iconNode","__iconNode"],"sources":["../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconDeviceDesktop.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconMoon.mjs","../../../../node_modules/.pnpm/@tabler+icons-react@3.44.0_react@19.2.7/node_modules/@tabler/icons-react/dist/esm/icons/IconSun.mjs","../../src/components/theme-toggle/theme-toggle.tsx"],"sourcesContent":["/**\n * @license @tabler/icons-react v3.44.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\": \"M3 5a1 1 0 0 1 1 -1h16a1 1 0 0 1 1 1v10a1 1 0 0 1 -1 1h-16a1 1 0 0 1 -1 -1v-10\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M7 20h10\", \"key\": \"svg-1\" }], [\"path\", { \"d\": \"M9 16v4\", \"key\": \"svg-2\" }], [\"path\", { \"d\": \"M15 16v4\", \"key\": \"svg-3\" }]];\nconst IconDeviceDesktop = createReactComponent(\"outline\", \"device-desktop\", \"DeviceDesktop\", __iconNode);\n\nexport { __iconNode, IconDeviceDesktop as default };\n//# sourceMappingURL=IconDeviceDesktop.mjs.map\n","/**\n * @license @tabler/icons-react v3.44.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\": \"M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454l0 .008\", \"key\": \"svg-0\" }]];\nconst IconMoon = createReactComponent(\"outline\", \"moon\", \"Moon\", __iconNode);\n\nexport { __iconNode, IconMoon as default };\n//# sourceMappingURL=IconMoon.mjs.map\n","/**\n * @license @tabler/icons-react v3.44.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\": \"M8 12a4 4 0 1 0 8 0a4 4 0 1 0 -8 0\", \"key\": \"svg-0\" }], [\"path\", { \"d\": \"M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7\", \"key\": \"svg-1\" }]];\nconst IconSun = createReactComponent(\"outline\", \"sun\", \"Sun\", __iconNode);\n\nexport { __iconNode, IconSun as default };\n//# sourceMappingURL=IconSun.mjs.map\n","import { useEffect, useSyncExternalStore, type HTMLAttributes } from \"react\";\nimport { IconDeviceDesktop, IconMoon, IconSun } from \"@tabler/icons-react\";\nimport { cn } from \"../../utils/cn\";\n\n/**\n * Appearance preference — `device` follows `prefers-color-scheme`; `light` /\n * `dark` force the scheme via `data-mode`.\n */\nexport type ThemeMode = \"device\" | \"light\" | \"dark\";\n\n/** Resolved color-scheme after applying a preference. */\nexport type ResolvedTheme = \"light\" | \"dark\";\n\n/** Default `localStorage` key used by the docs site and this component. */\nexport const THEME_STORAGE_KEY = \"pho-mode\";\n\nconst THEME_CHANGE_EVENT = \"pho-themechange\";\n\nconst OPTIONS = [\n { value: \"device\" as const, label: \"Device\", Icon: IconDeviceDesktop },\n { value: \"light\" as const, label: \"Light\", Icon: IconSun },\n { value: \"dark\" as const, label: \"Dark\", Icon: IconMoon },\n];\n\n// Concentric correction (see /scale): inner = outer − gap, derived from the\n// radius token with calc so it tracks theme changes. Track is `base`; the gap\n// is the track padding (xs–md p-0.5 = 2px, lg p-1 = 4px). Plain round, no\n// squircle (the track carries a ring-inset).\nconst THEME_SIZES = {\n xs: {\n track: \"gap-px rounded-base p-0.5\",\n item: \"size-5.5 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3\",\n },\n sm: {\n track: \"gap-0.5 rounded-base p-0.5\",\n item: \"size-7 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3.5\",\n },\n md: {\n track: \"gap-0.5 rounded-base p-0.5\",\n item: \"size-8 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-4\",\n },\n lg: {\n track: \"gap-1 rounded-base p-1\",\n item: \"size-9 rounded-[calc(var(--radius-base)-4px)] [&>svg]:size-5\",\n },\n} as const;\n\nexport type ThemeToggleSize = keyof typeof THEME_SIZES;\n\nexport interface ThemeToggleProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\" | \"color\" | \"onChange\" | \"defaultValue\"\n> {\n /** Controlled preference. */\n mode?: ThemeMode;\n /** Uncontrolled initial preference. @default \"device\" */\n defaultMode?: ThemeMode;\n /** Fired when the user picks a segment. */\n onModeChange?: (mode: ThemeMode) => void;\n /**\n * Persist the preference under this `localStorage` key. Pass `null` to skip\n * persistence. @default \"pho-mode\"\n */\n storageKey?: string | null;\n /**\n * Element that receives `data-mode=\"dark\"`. Defaults to\n * `document.documentElement`.\n */\n target?: HTMLElement | null | (() => HTMLElement | null);\n /** Size variant. @default \"md\" */\n size?: ThemeToggleSize;\n}\n\n/** Parse a stored preference; unknown / missing values fall back to `device`. */\nexport function parseThemeMode(value: string | null | undefined): ThemeMode {\n if (value === \"light\" || value === \"dark\" || value === \"device\") return value;\n return \"device\";\n}\n\n/** Resolve a preference against the OS color-scheme. */\nexport function resolveThemeMode(\n mode: ThemeMode,\n query: { matches: boolean } = typeof window !== \"undefined\"\n ? window.matchMedia(\"(prefers-color-scheme: dark)\")\n : { matches: false },\n): ResolvedTheme {\n if (mode === \"light\" || mode === \"dark\") return mode;\n return query.matches ? \"dark\" : \"light\";\n}\n\nfunction resolveTarget(\n target?: ThemeToggleProps[\"target\"],\n): HTMLElement | null {\n if (typeof target === \"function\") return target();\n if (target !== undefined) return target;\n return typeof document === \"undefined\" ? null : document.documentElement;\n}\n\n/** Preference mirrored across document-level ThemeToggle instances. */\nlet storedPreference: ThemeMode | null = null;\nlet mediaCleanup: (() => void) | null = null;\n/** Per-target `prefers-color-scheme` listeners (scoped `target` instances). */\nconst targetMediaCleanups = new Map<HTMLElement, () => void>();\n\n/** Drop in-memory preference + media listener (tests / full remounts). */\nexport function clearThemePreference() {\n storedPreference = null;\n mediaCleanup?.();\n mediaCleanup = null;\n for (const cleanup of targetMediaCleanups.values()) cleanup();\n targetMediaCleanups.clear();\n}\n\n/** Read a persisted preference, or `null` when nothing is stored. */\nfunction readStoredPreference(storageKey: string | null): ThemeMode | null {\n if (storageKey == null || typeof localStorage === \"undefined\") return null;\n const raw = localStorage.getItem(storageKey);\n if (raw == null) return null;\n return parseThemeMode(raw);\n}\n\n/** Current document preference (memory, then storage, else `device`). */\nexport function getThemePreference(\n storageKey: string | null = THEME_STORAGE_KEY,\n): ThemeMode {\n return storedPreference ?? readStoredPreference(storageKey) ?? \"device\";\n}\n\n/** Read the resolved light/dark mode from an element (or `<html>`). */\nexport function getThemeMode(\n target: HTMLElement | null = typeof document === \"undefined\"\n ? null\n : document.documentElement,\n): ResolvedTheme {\n return target?.dataset.mode === \"dark\" ? \"dark\" : \"light\";\n}\n\nfunction applyResolved(resolved: ResolvedTheme, target: HTMLElement | null) {\n if (!target) return;\n if (resolved === \"dark\") target.dataset.mode = \"dark\";\n else delete target.dataset.mode;\n}\n\nfunction bindMediaListener(storageKey: string | null) {\n mediaCleanup?.();\n mediaCleanup = null;\n if (typeof window === \"undefined\") return;\n if (getThemePreference(storageKey) !== \"device\") return;\n\n const mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const onChange = () => {\n applyResolved(resolveThemeMode(\"device\", mql), document.documentElement);\n window.dispatchEvent(new Event(THEME_CHANGE_EVENT));\n };\n mql.addEventListener(\"change\", onChange);\n mediaCleanup = () => mql.removeEventListener(\"change\", onChange);\n}\n\n/** Track OS scheme changes for a scoped target (docs previews, etc.). */\nfunction bindTargetMediaListener(target: HTMLElement, mode: ThemeMode) {\n unbindTargetMediaListener(target);\n if (typeof window === \"undefined\" || mode !== \"device\") return;\n\n const mql = window.matchMedia(\"(prefers-color-scheme: dark)\");\n const onChange = () => {\n applyResolved(resolveThemeMode(\"device\", mql), target);\n };\n mql.addEventListener(\"change\", onChange);\n targetMediaCleanups.set(target, () =>\n mql.removeEventListener(\"change\", onChange),\n );\n}\n\nfunction unbindTargetMediaListener(target: HTMLElement | null) {\n if (!target) return;\n targetMediaCleanups.get(target)?.();\n targetMediaCleanups.delete(target);\n}\n\n/**\n * Apply an appearance preference: persist it, resolve against the OS when\n * `device`, set `data-mode` on the target, and notify document-level toggles.\n */\nexport function setThemeMode(\n mode: ThemeMode,\n options?: {\n target?: HTMLElement | null;\n storageKey?: string | null;\n },\n): void {\n const storageKey =\n options && \"storageKey\" in options ? options.storageKey : THEME_STORAGE_KEY;\n const target =\n options?.target ??\n (typeof document === \"undefined\" ? null : document.documentElement);\n const syncDocument =\n typeof document === \"undefined\" ||\n target == null ||\n target === document.documentElement;\n\n applyResolved(resolveThemeMode(mode), target);\n\n if (!syncDocument) {\n if (target) bindTargetMediaListener(target, mode);\n return;\n }\n\n storedPreference = mode;\n if (storageKey != null && typeof localStorage !== \"undefined\") {\n localStorage.setItem(storageKey, mode);\n }\n bindMediaListener(storageKey ?? null);\n\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(new Event(THEME_CHANGE_EVENT));\n }\n}\n\nfunction subscribeTheme(onChange: () => void) {\n if (typeof window === \"undefined\") return () => {};\n window.addEventListener(THEME_CHANGE_EVENT, onChange);\n return () => window.removeEventListener(THEME_CHANGE_EVENT, onChange);\n}\n\nfunction selectMode(\n value: ThemeMode,\n options: {\n modeProp?: ThemeMode;\n storageKey: string | null;\n target: ThemeToggleProps[\"target\"];\n onModeChange?: (mode: ThemeMode) => void;\n },\n) {\n if (options.modeProp === undefined) {\n setThemeMode(value, {\n target: resolveTarget(options.target),\n storageKey: options.storageKey,\n });\n }\n options.onModeChange?.(value);\n}\n\n/**\n * Segmented appearance control — Device / Light / Dark. Forces\n * `data-mode=\"dark\"` (or clears it) so Pho's `light-dark()` tokens follow\n * `color-scheme`. `device` tracks `prefers-color-scheme`. Persists under\n * `pho-mode` by default; pair with a blocking `<head>` script to avoid a\n * flash (see docs).\n */\nexport function ThemeToggle({\n mode: modeProp,\n defaultMode = \"device\",\n onModeChange,\n storageKey = THEME_STORAGE_KEY,\n target,\n size = \"md\",\n className,\n \"aria-label\": ariaLabel = \"Theme\",\n ...props\n}: ThemeToggleProps) {\n const storeMode = useSyncExternalStore(\n subscribeTheme,\n () => storedPreference ?? readStoredPreference(storageKey) ?? defaultMode,\n () => defaultMode,\n );\n\n // Uncontrolled — hydrate from storage (or `defaultMode`) on mount.\n useEffect(() => {\n if (modeProp !== undefined) return;\n const initial =\n storedPreference ?? readStoredPreference(storageKey) ?? defaultMode;\n setThemeMode(initial, {\n target: resolveTarget(target),\n storageKey,\n });\n }, [modeProp, storageKey, target, defaultMode]);\n\n // Controlled — keep the target in sync with the prop.\n useEffect(() => {\n if (modeProp === undefined) return;\n setThemeMode(modeProp, {\n target: resolveTarget(target),\n storageKey,\n });\n }, [modeProp, storageKey, target]);\n\n // Drop scoped-target media listeners on unmount (document listener is shared).\n useEffect(() => {\n return () => {\n const el = resolveTarget(target);\n if (el && el !== document.documentElement) {\n unbindTargetMediaListener(el);\n }\n };\n }, [target]);\n\n const mode = modeProp ?? storeMode;\n const sizes = THEME_SIZES[size];\n\n return (\n // Segmented control: styled buttons with radiogroup semantics. Native\n // `<input type=\"radio\">` cannot express the inverse-chip track layout.\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n {...props}\n className={cn(\n \"ring-pho-interactive inline-flex items-center ring-1 ring-inset\",\n sizes.track,\n className,\n )}\n >\n {OPTIONS.map(({ value, label, Icon }) => {\n const selected = mode === value;\n return (\n <button\n key={value}\n type=\"button\"\n role=\"radio\" // oxlint-disable-line jsx-a11y/prefer-tag-over-role -- segment needs button chrome\n aria-checked={selected}\n aria-label={label}\n tabIndex={selected ? 0 : -1}\n onClick={() =>\n selectMode(value, { modeProp, storageKey, target, onModeChange })\n }\n onKeyDown={(event) => {\n const order = OPTIONS.map((o) => o.value);\n const index = order.indexOf(mode);\n let next: ThemeMode | undefined;\n if (event.key === \"ArrowRight\" || event.key === \"ArrowDown\") {\n next = order[(index + 1) % order.length];\n } else if (event.key === \"ArrowLeft\" || event.key === \"ArrowUp\") {\n next = order[(index - 1 + order.length) % order.length];\n } else if (event.key === \"Home\") {\n next = order[0];\n } else if (event.key === \"End\") {\n next = order[order.length - 1];\n }\n if (!next) return;\n event.preventDefault();\n selectMode(next, { modeProp, storageKey, target, onModeChange });\n const el = event.currentTarget.parentElement?.querySelector(\n `[aria-label=\"${OPTIONS.find((o) => o.value === next)?.label}\"]`,\n );\n if (el instanceof HTMLElement) el.focus();\n }}\n className={cn(\n \"outline-pho-brand inline-flex flex-1 cursor-pointer items-center justify-center transition duration-100 ease-linear focus-visible:outline-2 focus-visible:outline-offset-2\",\n // No hover wash — in a segmented control the only filled element\n // is the selected chip. Unselected segments rest at the dim\n // description ink and jump to primary on hover for a clear cue.\n selected\n ? \"bg-pho-inverse text-pho-inverse\"\n : \"text-pho-description hover:text-pho-primary\",\n sizes.item,\n )}\n >\n <Icon aria-hidden=\"true\" className=\"shrink-0\" />\n </button>\n );\n })}\n </div>\n );\n}\n\nThemeToggle.displayName = \"ThemeToggle\";\n"],"x_google_ignoreList":[0,1,2],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { t as a } from "./chunks/cn-DTiB6ek_.js";
|
|
3
3
|
import { n as o, r as n, t as r } from "./chunks/link-provider-9TFNyDqO.js";
|
|
4
|
-
import { t as
|
|
4
|
+
import { t as T } from "./chunks/resolve-variant-CcD8iG5l.js";
|
|
5
5
|
import "./utils.js";
|
|
6
|
-
import { a as E, c as
|
|
7
|
-
import { a as
|
|
6
|
+
import { a as E, c as S, d as I, f, i as B, l as _, n as p, o as u, r as A, s as O, t as R, u as N } from "./chunks/close-button-Bq9f-H15.js";
|
|
7
|
+
import { a as c, i as D, n as l, r as V, t as L } from "./chunks/sidebar-C5cIhWs1.js";
|
|
8
8
|
import { t as d } from "./chunks/input-BSJGBUbf.js";
|
|
9
|
-
import { n as
|
|
9
|
+
import { n as k, t as M } from "./chunks/dialog-DolPKFGS.js";
|
|
10
|
+
import { n as b, r as v, t as F } from "./chunks/settings-B3ilbRcF.js";
|
|
10
11
|
export {
|
|
11
|
-
|
|
12
|
+
p as BUTTON_COLORS,
|
|
12
13
|
A as BUTTON_SHAPES,
|
|
13
|
-
|
|
14
|
+
B as BUTTON_SIZES,
|
|
14
15
|
E as BUTTON_VARIANTS,
|
|
15
|
-
|
|
16
|
+
u as Button,
|
|
16
17
|
O as ButtonLink,
|
|
17
18
|
R as CloseButton,
|
|
18
19
|
M as Dialog,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
S as EFFECT_BUTTON_SIZES,
|
|
21
|
+
_ as EffectButton,
|
|
22
|
+
N as EffectButtonLink,
|
|
22
23
|
d as Input,
|
|
23
24
|
r as LinkProvider,
|
|
25
|
+
F as SETTINGS_CARD,
|
|
26
|
+
b as SETTINGS_ROW,
|
|
24
27
|
L as SIDEBAR_ITEM,
|
|
25
|
-
|
|
28
|
+
l as SIDEBAR_ITEM_ACTIVE,
|
|
26
29
|
V as SIDEBAR_ITEM_INACTIVE,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
D as SIDEBAR_ROOT,
|
|
31
|
+
v as Settings,
|
|
32
|
+
c as Sidebar,
|
|
33
|
+
I as buttonVariants,
|
|
30
34
|
a as cn,
|
|
31
|
-
|
|
35
|
+
f as effectButtonVariants,
|
|
32
36
|
n as isReactComponent,
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
T as resolveVariant,
|
|
38
|
+
k as useDialogPending,
|
|
35
39
|
o as useLinkComponent
|
|
36
40
|
};
|
package/dist/sections/sidebar.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Skeleton, type SkeletonProps } from './skeleton';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ComponentProps } from 'react';
|
|
2
|
+
export interface SkeletonProps extends ComponentProps<"div"> {
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* A pulsing placeholder bone for loading surfaces. Size and shape come from
|
|
6
|
+
* `className` (`h-*`, `w-*`, `rounded-full`, …) — the component only supplies
|
|
7
|
+
* the wash and motion. Decorative by default (`aria-hidden`); mark the
|
|
8
|
+
* surrounding region with `aria-busy` / `aria-label` instead of labeling each
|
|
9
|
+
* bone. Prefer `Spinner` when a single control is pending.
|
|
10
|
+
*/
|
|
11
|
+
export declare const Skeleton: import('react').ForwardRefExoticComponent<Omit<SkeletonProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
@@ -9,6 +9,10 @@ export type ResolvedTheme = "light" | "dark";
|
|
|
9
9
|
/** Default `localStorage` key used by the docs site and this component. */
|
|
10
10
|
export declare const THEME_STORAGE_KEY = "pho-mode";
|
|
11
11
|
declare const THEME_SIZES: {
|
|
12
|
+
readonly xs: {
|
|
13
|
+
readonly track: "gap-px rounded-base p-0.5";
|
|
14
|
+
readonly item: "size-5.5 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3";
|
|
15
|
+
};
|
|
12
16
|
readonly sm: {
|
|
13
17
|
readonly track: "gap-0.5 rounded-base p-0.5";
|
|
14
18
|
readonly item: "size-7 rounded-[calc(var(--radius-base)-2px)] [&>svg]:size-3.5";
|
package/dist/src/index.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Settings, SETTINGS_CARD, SETTINGS_ROW, type SettingsRowProps, } from './settings';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ComponentProps, ReactNode, Ref } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Settings — Pho's settings-page section: a page title, section titles, and
|
|
4
|
+
* bordered cards of label/control rows.
|
|
5
|
+
*
|
|
6
|
+
* Spacing: `Stack` separates blocks (`gap-10`). Inside a block, children share
|
|
7
|
+
* `gap-3`. Wrap a section title + description in `Header` (`gap-0.5`) so that
|
|
8
|
+
* pair stays tight — don't put them as loose siblings. Host the page at
|
|
9
|
+
* `max-w-xl` (parts already carry `px-5`).
|
|
10
|
+
*/
|
|
11
|
+
/** Bordered card that stacks rows with hairline dividers. */
|
|
12
|
+
export declare const SETTINGS_CARD = "divide-y divide-pho-secondary rounded-base border border-pho-secondary bg-pho-primary";
|
|
13
|
+
/**
|
|
14
|
+
* One settings row — label (+ optional description) left, control right.
|
|
15
|
+
* `min-h-[4.25rem]` (68px) keeps avatar/control rows from collapsing shorter
|
|
16
|
+
* than a comfortable touch target.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SETTINGS_ROW = "flex min-h-[4.25rem] flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6";
|
|
19
|
+
/** Page title — largest heading on the surface. */
|
|
20
|
+
declare function PageTitle({ className, children, ...props }: ComponentProps<"h1"> & {
|
|
21
|
+
ref?: Ref<HTMLHeadingElement>;
|
|
22
|
+
}): import("react").JSX.Element;
|
|
23
|
+
/**
|
|
24
|
+
* Vertical stack under the page title. Blocks (forms / sections) use `gap-10`;
|
|
25
|
+
* each block lays out its children with `gap-3`.
|
|
26
|
+
*/
|
|
27
|
+
declare function Stack({ className, ...props }: ComponentProps<"div"> & {
|
|
28
|
+
ref?: Ref<HTMLDivElement>;
|
|
29
|
+
}): import("react").JSX.Element;
|
|
30
|
+
/**
|
|
31
|
+
* Title + optional description pair. Keeps that couple tight (`gap-0.5`) so
|
|
32
|
+
* the block's `gap-3` only applies between this header and the card.
|
|
33
|
+
*/
|
|
34
|
+
declare function Header({ className, ...props }: ComponentProps<"div"> & {
|
|
35
|
+
ref?: Ref<HTMLDivElement>;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
/** Section heading — same padding as the page title, smaller type. */
|
|
38
|
+
declare function SectionTitle({ className, children, ...props }: ComponentProps<"h2"> & {
|
|
39
|
+
ref?: Ref<HTMLHeadingElement>;
|
|
40
|
+
}): import("react").JSX.Element;
|
|
41
|
+
/**
|
|
42
|
+
* Supporting copy under a section title. Same display step as the title;
|
|
43
|
+
* color + normal weight keep it secondary. Always wrap with `Header`.
|
|
44
|
+
*/
|
|
45
|
+
declare function Description({ className, ...props }: ComponentProps<"p"> & {
|
|
46
|
+
ref?: Ref<HTMLParagraphElement>;
|
|
47
|
+
}): import("react").JSX.Element;
|
|
48
|
+
/** Bordered card that stacks settings rows with hairline dividers. */
|
|
49
|
+
declare function Card({ className, ...props }: ComponentProps<"div"> & {
|
|
50
|
+
ref?: Ref<HTMLDivElement>;
|
|
51
|
+
}): import("react").JSX.Element;
|
|
52
|
+
export interface SettingsRowProps extends Omit<ComponentProps<"div">, "title"> {
|
|
53
|
+
/** Left-side field name. */
|
|
54
|
+
label: string;
|
|
55
|
+
/** Optional helper under the label. */
|
|
56
|
+
description?: ReactNode;
|
|
57
|
+
/** Associates the label with a control id. When omitted, the label is not a `<label>`. */
|
|
58
|
+
htmlFor?: string;
|
|
59
|
+
ref?: Ref<HTMLDivElement>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One settings row: label (+ optional description) left, control right.
|
|
63
|
+
* Pass `htmlFor` when the row wraps a labelled input; omit it for display/
|
|
64
|
+
* action rows (avatar, danger-zone delete).
|
|
65
|
+
*/
|
|
66
|
+
declare function Row({ label, description, htmlFor, className, children, ...props }: SettingsRowProps): import("react").JSX.Element;
|
|
67
|
+
/** Read-only value on the right side of a row (email, etc.). */
|
|
68
|
+
declare function Value({ className, ...props }: ComponentProps<"p"> & {
|
|
69
|
+
ref?: Ref<HTMLParagraphElement>;
|
|
70
|
+
}): import("react").JSX.Element;
|
|
71
|
+
/**
|
|
72
|
+
* Inline error under a row (still inside the card). Matches card horizontal
|
|
73
|
+
* padding so hosts don't re-apply `px-5`.
|
|
74
|
+
*/
|
|
75
|
+
declare function Alert({ className, ...props }: ComponentProps<"p"> & {
|
|
76
|
+
ref?: Ref<HTMLParagraphElement>;
|
|
77
|
+
}): import("react").JSX.Element;
|
|
78
|
+
/**
|
|
79
|
+
* Trailing bar inside a card — typically a save status + submit button that
|
|
80
|
+
* appears after the form is dirty. Same horizontal/vertical padding as a row.
|
|
81
|
+
*/
|
|
82
|
+
declare function Footer({ className, ...props }: ComponentProps<"div"> & {
|
|
83
|
+
ref?: Ref<HTMLDivElement>;
|
|
84
|
+
}): import("react").JSX.Element;
|
|
85
|
+
/**
|
|
86
|
+
* Settings page layout. One stack, one gap rhythm — only title scale differs:
|
|
87
|
+
*
|
|
88
|
+
* ```tsx
|
|
89
|
+
* <Settings.PageTitle>Profile</Settings.PageTitle>
|
|
90
|
+
* <Settings.Stack>
|
|
91
|
+
* <form>
|
|
92
|
+
* <Settings.Card>
|
|
93
|
+
* <Settings.Row label="Name" htmlFor="first-name">
|
|
94
|
+
* <Input id="first-name" size="sm" />
|
|
95
|
+
* </Settings.Row>
|
|
96
|
+
* </Settings.Card>
|
|
97
|
+
* </form>
|
|
98
|
+
* <form>
|
|
99
|
+
* <Settings.Header>
|
|
100
|
+
* <Settings.SectionTitle>Agent profile</Settings.SectionTitle>
|
|
101
|
+
* <Settings.Description>…</Settings.Description>
|
|
102
|
+
* </Settings.Header>
|
|
103
|
+
* <Settings.Card>…</Settings.Card>
|
|
104
|
+
* </form>
|
|
105
|
+
* <div>
|
|
106
|
+
* <Settings.SectionTitle>Danger zone</Settings.SectionTitle>
|
|
107
|
+
* <Settings.Card>
|
|
108
|
+
* <Settings.Row label="Delete account" description="…">
|
|
109
|
+
* <Button color="destructive" size="sm">Delete</Button>
|
|
110
|
+
* </Settings.Row>
|
|
111
|
+
* </Settings.Card>
|
|
112
|
+
* </div>
|
|
113
|
+
* </Settings.Stack>
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare const Settings: {
|
|
117
|
+
PageTitle: typeof PageTitle;
|
|
118
|
+
Stack: typeof Stack;
|
|
119
|
+
Header: typeof Header;
|
|
120
|
+
SectionTitle: typeof SectionTitle;
|
|
121
|
+
Description: typeof Description;
|
|
122
|
+
Card: typeof Card;
|
|
123
|
+
Row: typeof Row;
|
|
124
|
+
Value: typeof Value;
|
|
125
|
+
Alert: typeof Alert;
|
|
126
|
+
Footer: typeof Footer;
|
|
127
|
+
};
|
|
128
|
+
export {};
|
|
@@ -13,17 +13,18 @@ import { AnchorHTMLAttributes, ButtonHTMLAttributes, ComponentProps, FC, ReactNo
|
|
|
13
13
|
* `Menu.Trigger render={...}` to open an account menu).
|
|
14
14
|
*/
|
|
15
15
|
/** The rail itself — sticky, full-height, hairline right edge. */
|
|
16
|
-
export declare const SIDEBAR_ROOT = "border-pho-secondary bg-pho-page sticky top-0 flex h-dvh w-64 shrink-0 flex-col border-r px-4
|
|
16
|
+
export declare const SIDEBAR_ROOT = "border-pho-secondary bg-pho-page sticky top-0 flex h-dvh w-64 shrink-0 flex-col border-r px-4 pt-5 pb-4";
|
|
17
17
|
/** A nav row at rest and on hover. Active/inactive inks split below. */
|
|
18
|
-
export declare const SIDEBAR_ITEM = "outline-pho-brand flex w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-base transition-colors focus-visible:outline-2 focus-visible:-outline-offset-1";
|
|
18
|
+
export declare const SIDEBAR_ITEM = "outline-pho-brand flex min-h-8 w-full cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-left text-base transition-colors focus-visible:outline-2 focus-visible:-outline-offset-1";
|
|
19
19
|
/**
|
|
20
20
|
* Selected row — lifted onto the primary surface with the site hairline.
|
|
21
21
|
* The icon carries the same ink as the label so the pair reads as one unit.
|
|
22
22
|
*/
|
|
23
23
|
export declare const SIDEBAR_ITEM_ACTIVE = "bg-pho-primary text-pho-primary ring-pho-secondary font-medium ring-1 ring-inset *:data-icon:text-pho-primary";
|
|
24
24
|
/**
|
|
25
|
-
* Resting row — secondary ink (75%)
|
|
26
|
-
*
|
|
25
|
+
* Resting row — secondary ink (75%). Hover lifts onto the primary surface
|
|
26
|
+
* (same plane as the active chip, without the ring) so the wash stays quiet
|
|
27
|
+
* on `pho-page`. Icon tracks the label's ink at every state.
|
|
27
28
|
*/
|
|
28
29
|
export declare const SIDEBAR_ITEM_INACTIVE: string;
|
|
29
30
|
interface RootProps extends ComponentProps<"aside"> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@photon-ai/pho-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Pho Design System — Photon's React component library, built on Base UI",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -185,6 +185,10 @@
|
|
|
185
185
|
"types": "./dist/src/components/kbd/index.d.ts",
|
|
186
186
|
"import": "./dist/components/kbd.js"
|
|
187
187
|
},
|
|
188
|
+
"./components/skeleton": {
|
|
189
|
+
"types": "./dist/src/components/skeleton/index.d.ts",
|
|
190
|
+
"import": "./dist/components/skeleton.js"
|
|
191
|
+
},
|
|
188
192
|
"./components/spinner": {
|
|
189
193
|
"types": "./dist/src/components/spinner/index.d.ts",
|
|
190
194
|
"import": "./dist/components/spinner.js"
|
|
@@ -197,6 +201,10 @@
|
|
|
197
201
|
"types": "./dist/src/sections/sidebar/index.d.ts",
|
|
198
202
|
"import": "./dist/sections/sidebar.js"
|
|
199
203
|
},
|
|
204
|
+
"./sections/settings": {
|
|
205
|
+
"types": "./dist/src/sections/settings/index.d.ts",
|
|
206
|
+
"import": "./dist/sections/settings.js"
|
|
207
|
+
},
|
|
200
208
|
"./icons": {
|
|
201
209
|
"types": "./dist/src/icons/index.d.ts",
|
|
202
210
|
"import": "./dist/icons.js"
|
package/src/styles/theme.css
CHANGED
|
@@ -58,12 +58,12 @@
|
|
|
58
58
|
/* =========================== Type scale =========================== */
|
|
59
59
|
/* CLOSED scale — the reset removes Tailwind's default sizes (text-2xl, …)
|
|
60
60
|
so only these steps generate utilities. Body: xs/base/md/lg/xl =
|
|
61
|
-
|
|
61
|
+
13/14/16/18/20 (Pho `text-base` is 14px, not Tailwind's 16px default).
|
|
62
62
|
Display: xs/sm/base/md/lg/xl/2xl = 18/20/24/32/40/48/60.
|
|
63
63
|
NOTE: the reset wipes the whole --text-* prefix; every --text-color-pho-*
|
|
64
64
|
token is declared AFTER this point in the file, so it survives. */
|
|
65
65
|
--text-*: initial;
|
|
66
|
-
--text-xs: 0.
|
|
66
|
+
--text-xs: 0.8125rem;
|
|
67
67
|
--text-xs--line-height: 1.125rem;
|
|
68
68
|
--text-base: 0.875rem;
|
|
69
69
|
--text-base--line-height: 1.25rem;
|
|
@@ -321,4 +321,15 @@
|
|
|
321
321
|
transform: rotate(360deg);
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
/* ==================== Animation: Skeleton ==================== */
|
|
326
|
+
/* Soft opacity pulse for placeholder bones. Global reduced-motion guard
|
|
327
|
+
in pho.css collapses this to a static wash. */
|
|
328
|
+
--animate-pho-skeleton: pho-skeleton-pulse 1.6s ease-in-out infinite;
|
|
329
|
+
|
|
330
|
+
@keyframes pho-skeleton-pulse {
|
|
331
|
+
50% {
|
|
332
|
+
opacity: 0.45;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
324
335
|
}
|