@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.
Files changed (40) hide show
  1. package/dist/chunks/{close-button-8f-oRJXt.js → close-button-Bq9f-H15.js} +256 -181
  2. package/dist/chunks/close-button-Bq9f-H15.js.map +1 -0
  3. package/dist/chunks/{dialog-Bgb2Z1_v.js → dialog-DolPKFGS.js} +2 -2
  4. package/dist/chunks/{dialog-Bgb2Z1_v.js.map → dialog-DolPKFGS.js.map} +1 -1
  5. package/dist/chunks/{menu-COLB_liS.js → menu-Dan0oRvL.js} +79 -76
  6. package/dist/chunks/menu-Dan0oRvL.js.map +1 -0
  7. package/dist/chunks/settings-B3ilbRcF.js +104 -0
  8. package/dist/chunks/settings-B3ilbRcF.js.map +1 -0
  9. package/dist/chunks/{sidebar-16Pfn7IC.js → sidebar-C5cIhWs1.js} +4 -4
  10. package/dist/chunks/sidebar-C5cIhWs1.js.map +1 -0
  11. package/dist/components/autocomplete.js +1 -1
  12. package/dist/components/autocomplete.js.map +1 -1
  13. package/dist/components/button.js +1 -1
  14. package/dist/components/combobox.js +1 -1
  15. package/dist/components/combobox.js.map +1 -1
  16. package/dist/components/context-menu.js +52 -49
  17. package/dist/components/context-menu.js.map +1 -1
  18. package/dist/components/dialog.js +1 -1
  19. package/dist/components/menu.js +1 -1
  20. package/dist/components/select.js +16 -16
  21. package/dist/components/select.js.map +1 -1
  22. package/dist/components/skeleton.js +18 -0
  23. package/dist/components/skeleton.js.map +1 -0
  24. package/dist/components/theme-toggle.js +22 -18
  25. package/dist/components/theme-toggle.js.map +1 -1
  26. package/dist/index.js +21 -17
  27. package/dist/sections/settings.js +7 -0
  28. package/dist/sections/sidebar.js +1 -1
  29. package/dist/src/components/skeleton/index.d.ts +1 -0
  30. package/dist/src/components/skeleton/skeleton.d.ts +11 -0
  31. package/dist/src/components/theme-toggle/theme-toggle.d.ts +4 -0
  32. package/dist/src/index.d.ts +1 -0
  33. package/dist/src/sections/settings/index.d.ts +1 -0
  34. package/dist/src/sections/settings/settings.d.ts +128 -0
  35. package/dist/src/sections/sidebar/sidebar.d.ts +5 -4
  36. package/package.json +9 -1
  37. package/src/styles/theme.css +13 -2
  38. package/dist/chunks/close-button-8f-oRJXt.js.map +0 -1
  39. package/dist/chunks/menu-COLB_liS.js.map +0 -1
  40. 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", u = "pho-themechange", p = [
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 s = null, g = null, k = /* @__PURE__ */ new Map();
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 s ?? w(e) ?? "device";
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(u));
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
- s = e, r != null && typeof localStorage < "u" && localStorage.setItem(r, e), Q(r ?? null), typeof window < "u" && window.dispatchEvent(new Event(u));
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(u, e), () => window.removeEventListener(u, e));
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, () => s ?? w(t) ?? n, () => n);
126
+ const O = $(X, () => u ?? w(t) ?? n, () => n);
123
127
  v(() => {
124
- e === void 0 && E(s ?? w(t) ?? n, {
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 a = l(i);
144
- a && a !== document.documentElement && N(a);
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: a, label: R, Icon: q }) => {
153
- const f = x === a;
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(a, {
164
+ onClick: () => D(d, {
161
165
  modeProp: e,
162
166
  storageKey: t,
163
167
  target: i,
164
168
  onModeChange: r
165
169
  }),
166
- onKeyDown: (d) => {
170
+ onKeyDown: (a) => {
167
171
  const o = p.map((m) => m.value), L = o.indexOf(x);
168
172
  let c;
169
- if (d.key === "ArrowRight" || d.key === "ArrowDown" ? c = o[(L + 1) % o.length] : d.key === "ArrowLeft" || d.key === "ArrowUp" ? c = o[(L - 1 + o.length) % o.length] : d.key === "Home" ? c = o[0] : d.key === "End" && (c = o[o.length - 1]), !c) return;
170
- d.preventDefault(), D(c, {
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 = d.currentTarget.parentElement?.querySelector(`[aria-label="${p.find((m) => m.value === c)?.label}"]`);
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
- }, a);
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 e } from "./chunks/resolve-variant-CcD8iG5l.js";
4
+ import { t as T } from "./chunks/resolve-variant-CcD8iG5l.js";
5
5
  import "./utils.js";
6
- import { a as E, c as B, d as f, f as I, i as S, l as p, n as u, o as _, r as A, s as O, t as R, u as c } from "./chunks/close-button-8f-oRJXt.js";
7
- import { a as N, i as l, n as D, r as V, t as L } from "./chunks/sidebar-16Pfn7IC.js";
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 g, t as M } from "./chunks/dialog-Bgb2Z1_v.js";
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
- u as BUTTON_COLORS,
12
+ p as BUTTON_COLORS,
12
13
  A as BUTTON_SHAPES,
13
- S as BUTTON_SIZES,
14
+ B as BUTTON_SIZES,
14
15
  E as BUTTON_VARIANTS,
15
- _ as Button,
16
+ u as Button,
16
17
  O as ButtonLink,
17
18
  R as CloseButton,
18
19
  M as Dialog,
19
- B as EFFECT_BUTTON_SIZES,
20
- p as EffectButton,
21
- c as EffectButtonLink,
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
- D as SIDEBAR_ITEM_ACTIVE,
28
+ l as SIDEBAR_ITEM_ACTIVE,
26
29
  V as SIDEBAR_ITEM_INACTIVE,
27
- l as SIDEBAR_ROOT,
28
- N as Sidebar,
29
- f as buttonVariants,
30
+ D as SIDEBAR_ROOT,
31
+ v as Settings,
32
+ c as Sidebar,
33
+ I as buttonVariants,
30
34
  a as cn,
31
- I as effectButtonVariants,
35
+ f as effectButtonVariants,
32
36
  n as isReactComponent,
33
- e as resolveVariant,
34
- g as useDialogPending,
37
+ T as resolveVariant,
38
+ k as useDialogPending,
35
39
  o as useLinkComponent
36
40
  };
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { n as s, r as S, t as e } from "../chunks/settings-B3ilbRcF.js";
3
+ export {
4
+ e as SETTINGS_CARD,
5
+ s as SETTINGS_ROW,
6
+ S as Settings
7
+ };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { a as E, i as a, n as s, r as A, t as T } from "../chunks/sidebar-16Pfn7IC.js";
2
+ import { a as E, i as a, n as s, r as A, t as T } from "../chunks/sidebar-C5cIhWs1.js";
3
3
  export {
4
4
  T as SIDEBAR_ITEM,
5
5
  s as SIDEBAR_ITEM_ACTIVE,
@@ -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";
@@ -10,3 +10,4 @@ export * from './components/button';
10
10
  export * from './components/input';
11
11
  export * from './components/dialog';
12
12
  export * from './sections/sidebar';
13
+ export * from './sections/settings';
@@ -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 py-5";
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%), ghost wash on hover. Icon tracks the
26
- * label's ink at every state.
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.0.0",
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"
@@ -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
- 12/14/16/18/20 (Pho `text-base` is 14px, not Tailwind's 16px default).
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.75rem;
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
  }