@flytedan/flytebot-design-system 0.15.0 → 0.16.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/index.js CHANGED
@@ -5403,6 +5403,43 @@ function savePrefs(prefs, opts) {
5403
5403
  } catch {
5404
5404
  }
5405
5405
  }
5406
+ function createLocalStorageAppearanceStore(opts) {
5407
+ const key = opts?.storageKey ?? DEFAULT_STORAGE_KEY;
5408
+ const storage = resolveStorage(opts);
5409
+ let snapshot = loadPrefs(opts);
5410
+ const listeners3 = /* @__PURE__ */ new Set();
5411
+ const notify = () => listeners3.forEach((l) => l());
5412
+ const onStorageEvent = (e) => {
5413
+ if (e.key !== null && e.key !== key) return;
5414
+ snapshot = loadPrefs(opts);
5415
+ notify();
5416
+ };
5417
+ return {
5418
+ getSnapshot: () => snapshot,
5419
+ write(prefs) {
5420
+ snapshot = prefs;
5421
+ if (storage) {
5422
+ try {
5423
+ storage.setItem(key, JSON.stringify(prefs));
5424
+ } catch {
5425
+ }
5426
+ }
5427
+ notify();
5428
+ },
5429
+ subscribe(onChange) {
5430
+ listeners3.add(onChange);
5431
+ if (listeners3.size === 1 && typeof window !== "undefined") {
5432
+ window.addEventListener("storage", onStorageEvent);
5433
+ }
5434
+ return () => {
5435
+ listeners3.delete(onChange);
5436
+ if (listeners3.size === 0 && typeof window !== "undefined") {
5437
+ window.removeEventListener("storage", onStorageEvent);
5438
+ }
5439
+ };
5440
+ }
5441
+ };
5442
+ }
5406
5443
  function applyPrefs(prefs, root = document.documentElement) {
5407
5444
  root.setAttribute("data-theme", resolveTheme(prefs.theme));
5408
5445
  root.setAttribute("data-text-scale", prefs.textScale);
@@ -5411,11 +5448,10 @@ function applyPrefs(prefs, root = document.documentElement) {
5411
5448
  if (prefs.reduceMotion) root.setAttribute("data-motion", "reduce");
5412
5449
  else root.removeAttribute("data-motion");
5413
5450
  }
5414
- function useAppearancePrefs(opts) {
5415
- const [prefs, setPrefs] = React33.useState(() => loadPrefs(opts));
5451
+ function useAppearancePrefs(store) {
5452
+ const prefs = React33.useSyncExternalStore(store.subscribe, store.getSnapshot);
5416
5453
  React33.useEffect(() => {
5417
5454
  applyPrefs(prefs);
5418
- savePrefs(prefs, opts);
5419
5455
  }, [prefs]);
5420
5456
  React33.useEffect(() => {
5421
5457
  if (prefs.theme !== "system" || typeof window === "undefined" || typeof window.matchMedia !== "function") {
@@ -5426,9 +5462,12 @@ function useAppearancePrefs(opts) {
5426
5462
  mq.addEventListener("change", onChange);
5427
5463
  return () => mq.removeEventListener("change", onChange);
5428
5464
  }, [prefs]);
5429
- const update = React33.useCallback((patch) => {
5430
- setPrefs((p) => ({ ...p, ...patch }));
5431
- }, []);
5465
+ const update = React33.useCallback(
5466
+ (patch) => {
5467
+ store.write({ ...store.getSnapshot(), ...patch });
5468
+ },
5469
+ [store]
5470
+ );
5432
5471
  return [prefs, update];
5433
5472
  }
5434
5473
 
@@ -5445,8 +5484,12 @@ var TEXT_SCALE_OPTIONS = [
5445
5484
  { value: "lg", label: "L" },
5446
5485
  { value: "xl", label: "XL" }
5447
5486
  ];
5448
- function AppearanceMenu({ className = "", ...storeOptions }) {
5449
- const [prefs, update] = useAppearancePrefs(storeOptions);
5487
+ function AppearanceMenu({ className = "", store: storeProp, storageKey, storage, defaults }) {
5488
+ const store = React34.useMemo(
5489
+ () => storeProp ?? createLocalStorageAppearanceStore({ storageKey, storage, defaults }),
5490
+ [storeProp, storageKey, storage, defaults]
5491
+ );
5492
+ const [prefs, update] = useAppearancePrefs(store);
5450
5493
  const [open, setOpen] = React34.useState(false);
5451
5494
  const anchorRef = React34.useRef(null);
5452
5495
  const patch = (p) => update(p);
@@ -5683,137 +5726,172 @@ function ApiSpecBrowser({
5683
5726
  // src/components/platform/ProfilePage.tsx
5684
5727
  import * as React36 from "react";
5685
5728
  import { jsx as jsx61, jsxs as jsxs57 } from "react/jsx-runtime";
5686
- var TIMEZONES = ["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles", "America/Anchorage", "Pacific/Honolulu", "Europe/London", "Europe/Berlin"];
5687
- var NOTIFY = [
5688
- { key: "planShared", label: "A plan is shared with me", detail: "Someone sends you a plan or a client link." },
5689
- { key: "planChanged", label: "A plan I own changes", detail: "Budget moves, campuses added or removed." },
5690
- { key: "goalMissed", label: "The reviewer flags a missed criterion", detail: "After every solve, if a criterion is short." },
5691
- { key: "flagChanged", label: "A feature flag affecting me changes", detail: "Someone toggles a feature you can see." },
5692
- { key: "weekly", label: "Weekly digest", detail: "Monday summary of plan activity." }
5693
- ];
5694
- function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSessions = true, className = "", ...rest }) {
5729
+ function fieldInitialValue(field, user) {
5730
+ if (field.value) return field.value(user);
5731
+ const raw = user[field.key];
5732
+ return raw == null ? "" : String(raw);
5733
+ }
5734
+ function groupFields(fields) {
5735
+ const order = [];
5736
+ const byGroup = /* @__PURE__ */ new Map();
5737
+ for (const field of fields) {
5738
+ const group = field.group ?? "Identity";
5739
+ if (!byGroup.has(group)) {
5740
+ byGroup.set(group, { group, description: field.groupDescription, fields: [] });
5741
+ order.push(group);
5742
+ }
5743
+ const entry = byGroup.get(group);
5744
+ if (entry.description === void 0) entry.description = field.groupDescription;
5745
+ entry.fields.push(field);
5746
+ }
5747
+ return order.map((g) => byGroup.get(g));
5748
+ }
5749
+ function ProfilePage({
5750
+ user: userProp,
5751
+ fields = [],
5752
+ notifications = [],
5753
+ extra,
5754
+ onChangePhoto,
5755
+ onSave,
5756
+ onNavigate,
5757
+ sessions,
5758
+ onSignOutEverywhere,
5759
+ className = ""
5760
+ }) {
5695
5761
  const { user: sessionUser, roles, permissions, saveProfile: saveProfile2 } = useSessionContext();
5696
5762
  const user = userProp || sessionUser;
5697
- const [draft, setDraft] = React36.useState(() => ({
5698
- name: user.name || "",
5699
- title: user.title || "",
5700
- email: user.email || "",
5701
- phone: user.phone || "",
5702
- timezone: user.timezone || TIMEZONES[2],
5703
- bio: user.bio || "",
5704
- notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true }
5705
- }));
5763
+ const initialFieldValues = React36.useMemo(() => {
5764
+ const values2 = {};
5765
+ for (const field of fields) values2[field.key] = fieldInitialValue(field, user);
5766
+ return values2;
5767
+ }, [user.id, fields]);
5768
+ const initialNotify = React36.useMemo(() => {
5769
+ const values2 = {};
5770
+ for (const n of notifications) values2[n.key] = !!user.notify?.[n.key];
5771
+ return values2;
5772
+ }, [user.id, notifications]);
5773
+ const [values, setValues] = React36.useState(initialFieldValues);
5774
+ const [notify, setNotify] = React36.useState(initialNotify);
5706
5775
  const [saving, setSaving] = React36.useState(false);
5707
5776
  const [saved, setSaved] = React36.useState(false);
5708
- const set = (k, v) => {
5709
- setDraft((d) => Object.assign({}, d, { [k]: v }));
5777
+ const [prevUserId, setPrevUserId] = React36.useState(user.id);
5778
+ if (user.id !== prevUserId) {
5779
+ setPrevUserId(user.id);
5780
+ setValues(initialFieldValues);
5781
+ setNotify(initialNotify);
5782
+ setSaved(false);
5783
+ }
5784
+ const setField = (key, v) => {
5785
+ setValues((d) => ({ ...d, [key]: v }));
5710
5786
  setSaved(false);
5711
5787
  };
5712
- const setNotify = (k) => {
5713
- setDraft((d) => Object.assign({}, d, { notify: Object.assign({}, d.notify, { [k]: !d.notify[k] }) }));
5788
+ const toggleNotify = (key) => {
5789
+ setNotify((d) => ({ ...d, [key]: !d[key] }));
5714
5790
  setSaved(false);
5715
5791
  };
5716
- const dirty = draft.name !== (user.name || "") || draft.title !== (user.title || "") || draft.phone !== (user.phone || "") || draft.timezone !== (user.timezone || TIMEZONES[2]) || draft.bio !== (user.bio || "") || JSON.stringify(draft.notify) !== JSON.stringify(user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true });
5717
- const canSave = !!(onSave || saveProfile2);
5792
+ const dirty = fields.some((f) => values[f.key] !== initialFieldValues[f.key]) || notifications.some((n) => notify[n.key] !== initialNotify[n.key]);
5793
+ const canSave = !!(onSave || saveProfile2) && (fields.length > 0 || notifications.length > 0);
5718
5794
  const save = async () => {
5719
5795
  if (!canSave) return;
5720
5796
  setSaving(true);
5721
- if (onSave) await onSave(draft);
5722
- else await saveProfile2(draft);
5723
- setSaving(false);
5724
- setSaved(true);
5797
+ const draft = { ...values };
5798
+ if (notifications.length > 0) draft.notify = notify;
5799
+ try {
5800
+ if (onSave) await onSave(draft);
5801
+ else await saveProfile2(draft);
5802
+ setSaved(true);
5803
+ } finally {
5804
+ setSaving(false);
5805
+ }
5725
5806
  };
5726
- const liveSessions = sessions || [
5727
- { id: "s1", device: "MacBook Pro \xB7 Chrome", where: "Denver, CO", when: "Active now", current: true },
5728
- { id: "s2", device: "iPhone 15 \xB7 Safari", where: "Denver, CO", when: "2 hours ago" },
5729
- { id: "s3", device: "Windows \xB7 Edge", where: "Chicago, IL", when: "Aug 12" }
5730
- ];
5731
- return /* @__PURE__ */ jsxs57("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, ...rest, children: [
5807
+ const discard = () => {
5808
+ setValues(initialFieldValues);
5809
+ setNotify(initialNotify);
5810
+ setSaved(false);
5811
+ };
5812
+ const displayName = fields.some((f) => f.key === "name") ? values.name || user.name : user.name;
5813
+ const fieldGroups = groupFields(fields);
5814
+ return /* @__PURE__ */ jsxs57("div", { className: ["fd-stack", className].filter(Boolean).join(" "), style: { gap: 20, maxWidth: 860 }, children: [
5732
5815
  /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 10 }, children: [
5733
5816
  /* @__PURE__ */ jsx61("span", { className: "fd-overline fd-muted", children: "Account" }),
5734
5817
  /* @__PURE__ */ jsx61("h1", { className: "fd-h1", style: { margin: 0 }, children: "Your profile" }),
5735
5818
  /* @__PURE__ */ jsx61("p", { className: "fd-body fd-secondary fd-prose", style: { margin: 0 }, children: "How you appear to colleagues and clients across every flytedesk app, and what we send you." })
5736
5819
  ] }),
5737
5820
  /* @__PURE__ */ jsx61(Card, { elevation: "flat", children: /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 16, flexWrap: "wrap", alignItems: "flex-start" }, children: [
5738
- /* @__PURE__ */ jsx61(Avatar, { name: draft.name || user.name, size: "lg" }),
5821
+ /* @__PURE__ */ jsx61(Avatar, { name: displayName, size: "lg" }),
5739
5822
  /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 4, flex: 1, minWidth: 200 }, children: [
5740
- /* @__PURE__ */ jsx61("span", { className: "fd-h3", style: { margin: 0 }, children: draft.name || user.name }),
5741
- /* @__PURE__ */ jsxs57("span", { className: "fd-body-sm fd-muted", children: [
5742
- draft.title || "No title set",
5743
- " \xB7 ",
5744
- user.team || "No team"
5745
- ] }),
5823
+ /* @__PURE__ */ jsx61("span", { className: "fd-h3", style: { margin: 0 }, children: displayName }),
5746
5824
  /* @__PURE__ */ jsxs57("span", { className: "fd-row", style: { gap: 6, flexWrap: "wrap", paddingTop: 4 }, children: [
5747
5825
  roles.map((r) => /* @__PURE__ */ jsx61(Badge, { tone: r.id === "owner" ? "success" : "neutral", children: r.name }, r.id)),
5748
5826
  user.sso ? /* @__PURE__ */ jsx61(Badge, { tone: "info", icon: "shield-check", children: "SSO" }) : null
5749
5827
  ] })
5750
5828
  ] }),
5751
- /* @__PURE__ */ jsx61(Button, { variant: "secondary", size: "sm", icon: "image", children: "Change photo" })
5829
+ onChangePhoto ? /* @__PURE__ */ jsx61(Button, { variant: "secondary", size: "sm", icon: "image", onClick: onChangePhoto, children: "Change photo" }) : null
5752
5830
  ] }) }),
5753
- /* @__PURE__ */ jsx61(
5831
+ fieldGroups.map(({ group, description, fields: groupFieldDefs }) => /* @__PURE__ */ jsx61(
5754
5832
  Card,
5755
5833
  {
5756
- title: /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
5757
- /* @__PURE__ */ jsx61("span", { children: "Identity" }),
5758
- /* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Name and title appear on plans you share" })
5759
- ] }),
5834
+ title: description ? /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
5835
+ /* @__PURE__ */ jsx61("span", { children: group }),
5836
+ /* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: description })
5837
+ ] }) : group,
5760
5838
  elevation: "flat",
5761
5839
  children: /* @__PURE__ */ jsxs57("div", { className: "fd-stack", style: { gap: 14 }, children: [
5762
- /* @__PURE__ */ jsxs57("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5763
- /* @__PURE__ */ jsx61(Input, { label: "Full name", value: draft.name, onChange: (e) => set("name", e.target.value) }),
5764
- /* @__PURE__ */ jsx61(Input, { label: "Job title", value: draft.title, onChange: (e) => set("title", e.target.value), placeholder: "e.g. Senior media planner" })
5765
- ] }),
5766
- /* @__PURE__ */ jsxs57("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5767
- /* @__PURE__ */ jsx61(
5840
+ /* @__PURE__ */ jsx61("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: groupFieldDefs.filter((f) => f.type !== "textarea").map((field) => {
5841
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
5842
+ const readOnly = field.readOnly || field.type === "readonly";
5843
+ if (field.type === "select") {
5844
+ return /* @__PURE__ */ jsx61(
5845
+ Select,
5846
+ {
5847
+ label: field.label,
5848
+ value: values[field.key],
5849
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
5850
+ options: field.options ?? [],
5851
+ placeholder: field.placeholder,
5852
+ help,
5853
+ disabled: readOnly
5854
+ },
5855
+ field.key
5856
+ );
5857
+ }
5858
+ return /* @__PURE__ */ jsx61(
5768
5859
  Input,
5769
5860
  {
5770
- label: "Work email",
5771
- value: draft.email,
5772
- readOnly: true,
5773
- disabled: true,
5774
- help: user.sso ? "Managed by your identity provider \u2014 change it there." : "Contact an administrator to change this."
5775
- }
5776
- ),
5777
- /* @__PURE__ */ jsx61(Input, { label: "Phone", value: draft.phone, onChange: (e) => set("phone", e.target.value), placeholder: "Optional" })
5778
- ] }),
5779
- /* @__PURE__ */ jsx61(
5780
- Textarea,
5781
- {
5782
- label: "Short bio",
5783
- value: draft.bio,
5784
- onChange: (e) => set("bio", e.target.value),
5785
- rows: 3,
5786
- placeholder: "Optional \u2014 shown to colleagues on shared plans.",
5787
- help: "Never shown to clients."
5788
- }
5789
- )
5861
+ label: field.label,
5862
+ value: values[field.key],
5863
+ onChange: readOnly ? void 0 : (e) => setField(field.key, e.target.value),
5864
+ readOnly,
5865
+ disabled: readOnly,
5866
+ placeholder: field.placeholder,
5867
+ help
5868
+ },
5869
+ field.key
5870
+ );
5871
+ }) }),
5872
+ groupFieldDefs.filter((f) => f.type === "textarea").map((field) => {
5873
+ const help = typeof field.help === "function" ? field.help(user) : field.help;
5874
+ return /* @__PURE__ */ jsx61(
5875
+ Textarea,
5876
+ {
5877
+ label: field.label,
5878
+ value: values[field.key],
5879
+ onChange: field.readOnly ? void 0 : (e) => setField(field.key, e.target.value),
5880
+ readOnly: field.readOnly,
5881
+ disabled: field.readOnly,
5882
+ rows: field.rows ?? 3,
5883
+ placeholder: field.placeholder,
5884
+ help
5885
+ },
5886
+ field.key
5887
+ );
5888
+ })
5790
5889
  ] })
5791
- }
5792
- ),
5793
- /* @__PURE__ */ jsx61(Card, { title: "Working preferences", elevation: "flat", children: /* @__PURE__ */ jsxs57("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 14 }, children: [
5794
- /* @__PURE__ */ jsx61(
5795
- Select,
5796
- {
5797
- label: "Time zone",
5798
- value: draft.timezone,
5799
- onChange: (e) => set("timezone", e.target.value),
5800
- options: TIMEZONES,
5801
- help: "Flight dates and schedules render in this zone."
5802
- }
5803
- ),
5804
- /* @__PURE__ */ jsx61(Select, { label: "Start of week", options: ["Monday", "Sunday"], placeholder: "Monday" })
5805
- ] }) }),
5806
- /* @__PURE__ */ jsx61(
5807
- Card,
5808
- {
5809
- title: /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 2 }, children: [
5810
- /* @__PURE__ */ jsx61("span", { children: "Notifications" }),
5811
- /* @__PURE__ */ jsx61("span", { className: "fd-body-sm fd-muted", style: { fontWeight: 400 }, children: "Email only for now \u2014 in-app notifications are on the roadmap" })
5812
- ] }),
5813
- elevation: "flat",
5814
- children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 14 }, children: NOTIFY.map((n) => /* @__PURE__ */ jsx61(Switch, { checked: !!draft.notify[n.key], onChange: () => setNotify(n.key), label: n.label, description: n.detail }, n.key)) })
5815
- }
5816
- ),
5890
+ },
5891
+ group
5892
+ )),
5893
+ extra,
5894
+ notifications.length > 0 ? /* @__PURE__ */ jsx61(Card, { title: "Notifications", elevation: "flat", children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 14 }, children: notifications.map((n) => /* @__PURE__ */ jsx61(Switch, { checked: !!notify[n.key], onChange: () => toggleNotify(n.key), label: n.label, description: n.description }, n.key)) }) }) : null,
5817
5895
  /* @__PURE__ */ jsxs57(
5818
5896
  Card,
5819
5897
  {
@@ -5845,13 +5923,13 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
5845
5923
  ]
5846
5924
  }
5847
5925
  ),
5848
- showSessions ? /* @__PURE__ */ jsx61(
5926
+ sessions ? /* @__PURE__ */ jsx61(
5849
5927
  Card,
5850
5928
  {
5851
5929
  title: "Signed-in devices",
5852
5930
  elevation: "flat",
5853
- action: /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", icon: "sign-out", children: "Sign out everywhere" }),
5854
- children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 0 }, children: liveSessions.map((s) => /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
5931
+ action: /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", icon: "sign-out", onClick: onSignOutEverywhere, children: "Sign out everywhere" }),
5932
+ children: /* @__PURE__ */ jsx61("div", { className: "fd-stack", style: { gap: 0 }, children: sessions.length === 0 ? /* @__PURE__ */ jsx61("p", { className: "fd-body-sm fd-muted", style: { margin: 0 }, children: "No other signed-in devices." }) : sessions.map((s) => /* @__PURE__ */ jsxs57("div", { className: "fd-row", style: { gap: 12, padding: "10px 0", borderTop: "1px solid var(--border)", flexWrap: "wrap" }, children: [
5855
5933
  /* @__PURE__ */ jsx61("i", { className: "ph ph-device-mobile", style: { fontSize: 16, color: "var(--text-muted)" }, "aria-hidden": "true" }),
5856
5934
  /* @__PURE__ */ jsxs57("span", { className: "fd-stack", style: { gap: 1, flex: 1, minWidth: 160 }, children: [
5857
5935
  /* @__PURE__ */ jsx61("span", { className: "fd-body-sm", style: { fontWeight: 600 }, children: s.device }),
@@ -5877,19 +5955,7 @@ function ProfilePage({ user: userProp, onSave, onNavigate, sessions, showSession
5877
5955
  boxShadow: "0 -4px 16px rgba(11,13,17,.06)"
5878
5956
  }, children: [
5879
5957
  /* @__PURE__ */ jsx61("span", { className: "fd-body-sm", style: { fontWeight: 600, flex: 1 }, children: saved ? "Saved." : dirty ? "Unsaved changes" : "No pending changes" }),
5880
- /* @__PURE__ */ jsx61(
5881
- Button,
5882
- {
5883
- size: "sm",
5884
- variant: "ghost",
5885
- disabled: !dirty || saving,
5886
- onClick: () => {
5887
- setDraft({ name: user.name || "", title: user.title || "", email: user.email || "", phone: user.phone || "", timezone: user.timezone || TIMEZONES[2], bio: user.bio || "", notify: user.notify || { planShared: true, planChanged: true, goalMissed: true, flagChanged: false, weekly: true } });
5888
- setSaved(false);
5889
- },
5890
- children: "Discard"
5891
- }
5892
- ),
5958
+ /* @__PURE__ */ jsx61(Button, { size: "sm", variant: "ghost", disabled: !dirty || saving, onClick: discard, children: "Discard" }),
5893
5959
  /* @__PURE__ */ jsx61(Button, { size: "sm", icon: "check", disabled: !dirty || !canSave, loading: saving, onClick: save, children: "Save changes" })
5894
5960
  ] })
5895
5961
  ] });
@@ -11734,6 +11800,7 @@ export {
11734
11800
  channelWeightOf,
11735
11801
  computeNeedMore,
11736
11802
  computeVirtualWindow,
11803
+ createLocalStorageAppearanceStore,
11737
11804
  createVersionStore,
11738
11805
  eqFilter,
11739
11806
  extensionOf,