react-os-shell 4.79.3 → 4.82.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 (68) hide show
  1. package/README.md +18 -0
  2. package/dist/{Browser-2DFSZ3RP.js → Browser-5FNVWP3Q.js} +7 -7
  3. package/dist/{Browser-2DFSZ3RP.js.map → Browser-5FNVWP3Q.js.map} +1 -1
  4. package/dist/{Calculator-PAPSDNSJ.js → Calculator-2FCNKTPN.js} +6 -6
  5. package/dist/{Calculator-PAPSDNSJ.js.map → Calculator-2FCNKTPN.js.map} +1 -1
  6. package/dist/{CurrencyConverter-VJQI2XVV.js → CurrencyConverter-KZU7MTWE.js} +6 -6
  7. package/dist/{CurrencyConverter-VJQI2XVV.js.map → CurrencyConverter-KZU7MTWE.js.map} +1 -1
  8. package/dist/{Documents-GIP67CHC.js → Documents-ZTK6SSDK.js} +7 -7
  9. package/dist/{Documents-GIP67CHC.js.map → Documents-ZTK6SSDK.js.map} +1 -1
  10. package/dist/{Files-JAE2LL65.js → Files-4PNE3MBV.js} +12 -12
  11. package/dist/{Files-JAE2LL65.js.map → Files-4PNE3MBV.js.map} +1 -1
  12. package/dist/{Notepad-SXHXJ6PC.js → Notepad-63JJ2WB5.js} +7 -7
  13. package/dist/{Notepad-SXHXJ6PC.js.map → Notepad-63JJ2WB5.js.map} +1 -1
  14. package/dist/{PomodoroTimer-GFL4L3XS.js → PomodoroTimer-SL372V3I.js} +7 -7
  15. package/dist/{PomodoroTimer-GFL4L3XS.js.map → PomodoroTimer-SL372V3I.js.map} +1 -1
  16. package/dist/{Preview-5HFE6MCG.js → Preview-QZL2BXLP.js} +9 -9
  17. package/dist/Preview-QZL2BXLP.js.map +1 -0
  18. package/dist/{Sidebar-B3LY5T2U.js → Sidebar-GFDPPC4X.js} +24 -6
  19. package/dist/Sidebar-GFDPPC4X.js.map +1 -0
  20. package/dist/{Spreadsheet-KZLORYAA.js → Spreadsheet-RCKDHL66.js} +8 -8
  21. package/dist/{Spreadsheet-KZLORYAA.js.map → Spreadsheet-RCKDHL66.js.map} +1 -1
  22. package/dist/{Stock-WZXVQX3G.js → Stock-YDCHLKSV.js} +6 -6
  23. package/dist/{Stock-WZXVQX3G.js.map → Stock-YDCHLKSV.js.map} +1 -1
  24. package/dist/{Weather-FQ7ZVXVR.js → Weather-HM3Y5LMI.js} +6 -6
  25. package/dist/{Weather-FQ7ZVXVR.js.map → Weather-HM3Y5LMI.js.map} +1 -1
  26. package/dist/{WorldClock-XAFQ5ZQF.js → WorldClock-RXKMY3YJ.js} +6 -6
  27. package/dist/{WorldClock-XAFQ5ZQF.js.map → WorldClock-RXKMY3YJ.js.map} +1 -1
  28. package/dist/apps/index.js +19 -19
  29. package/dist/{chunk-GO2LCDHS.js → chunk-26S2Q6GA.js} +3 -3
  30. package/dist/{chunk-GO2LCDHS.js.map → chunk-26S2Q6GA.js.map} +1 -1
  31. package/dist/{chunk-EWDYCO65.js → chunk-35RG2LD2.js} +3 -3
  32. package/dist/{chunk-EWDYCO65.js.map → chunk-35RG2LD2.js.map} +1 -1
  33. package/dist/{chunk-ZO4XGUZQ.js → chunk-3TSIRZUW.js} +3 -3
  34. package/dist/{chunk-ZO4XGUZQ.js.map → chunk-3TSIRZUW.js.map} +1 -1
  35. package/dist/{chunk-BLLINYDD.js → chunk-5TLIIDRZ.js} +186 -5
  36. package/dist/chunk-5TLIIDRZ.js.map +1 -0
  37. package/dist/chunk-6XQHH7D2.js +105 -0
  38. package/dist/chunk-6XQHH7D2.js.map +1 -0
  39. package/dist/{chunk-OQ7GRU7G.js → chunk-7RUYFI6O.js} +3 -3
  40. package/dist/{chunk-OQ7GRU7G.js.map → chunk-7RUYFI6O.js.map} +1 -1
  41. package/dist/{chunk-A42MTWLI.js → chunk-F54RMKRL.js} +5 -5
  42. package/dist/{chunk-A42MTWLI.js.map → chunk-F54RMKRL.js.map} +1 -1
  43. package/dist/{chunk-TT2WNTDS.js → chunk-IZ2FABWU.js} +3 -3
  44. package/dist/{chunk-TT2WNTDS.js.map → chunk-IZ2FABWU.js.map} +1 -1
  45. package/dist/chunk-KLSUJ46L.js +7 -0
  46. package/dist/{chunk-M2IJT4OA.js.map → chunk-KLSUJ46L.js.map} +1 -1
  47. package/dist/{chunk-YZVJCHXV.js → chunk-LEMEFL6U.js} +300 -194
  48. package/dist/chunk-LEMEFL6U.js.map +1 -0
  49. package/dist/{chunk-EXSEW5F3.js → chunk-MHUVU7ZX.js} +102 -16
  50. package/dist/chunk-MHUVU7ZX.js.map +1 -0
  51. package/dist/{chunk-QJS6RBH4.js → chunk-XWLEA4B7.js} +3 -3
  52. package/dist/{chunk-QJS6RBH4.js.map → chunk-XWLEA4B7.js.map} +1 -1
  53. package/dist/{chunk-WNLPTLT6.js → chunk-ZESNO4TZ.js} +2 -2
  54. package/dist/chunk-ZESNO4TZ.js.map +1 -0
  55. package/dist/{index-BjEcKEsJ.d.ts → index-D9ZuIsBe.d.ts} +130 -1
  56. package/dist/index.d.ts +53 -3
  57. package/dist/index.js +157 -56
  58. package/dist/index.js.map +1 -1
  59. package/dist/ui/index.d.ts +1 -1
  60. package/dist/ui/index.js +4 -3
  61. package/package.json +1 -1
  62. package/dist/Preview-5HFE6MCG.js.map +0 -1
  63. package/dist/Sidebar-B3LY5T2U.js.map +0 -1
  64. package/dist/chunk-BLLINYDD.js.map +0 -1
  65. package/dist/chunk-EXSEW5F3.js.map +0 -1
  66. package/dist/chunk-M2IJT4OA.js +0 -7
  67. package/dist/chunk-WNLPTLT6.js.map +0 -1
  68. package/dist/chunk-YZVJCHXV.js.map +0 -1
@@ -1,121 +1,13 @@
1
1
  import { Breadcrumbs } from './chunk-HNH7FOYB.js';
2
2
  import { SidebarLayout } from './chunk-YG45DAUT.js';
3
- import { useIsMobile, useShellStrings, PopupMenu, PopupMenuItem, useShellPrefs } from './chunk-BLLINYDD.js';
3
+ import { BrandMark } from './chunk-6XQHH7D2.js';
4
+ import { BUTTON_BASE, BUTTON_VARIANTS, useIsMobile, useShellStrings, Button_default, PopupMenu, PopupMenuItem, useShellPrefs } from './chunk-5TLIIDRZ.js';
4
5
  import { registerModalEscapeInterceptor, useFocusTrap, useScrollLock } from './chunk-CDR2Q64Q.js';
5
6
  import { glassStyle } from './chunk-3CUSBZWG.js';
6
- import { forwardRef, useId, useCallback, useState, useRef, useEffect, createContext, useLayoutEffect, useMemo, Children, isValidElement, cloneElement, useContext, Fragment as Fragment$1, Component, useSyncExternalStore } from 'react';
7
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
+ import { forwardRef, useCallback, useState, useRef, useId, useEffect, createContext, useLayoutEffect, useMemo, useContext, Children, isValidElement, cloneElement, Fragment as Fragment$1, Component, useSyncExternalStore } from 'react';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
9
  import { createPortal } from 'react-dom';
9
10
 
10
- var BUTTON_BASE = "inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
11
- var BUTTON_VARIANTS = {
12
- primary: "bg-blue-600 text-white shadow-sm hover:bg-blue-700 focus:ring-blue-400/40",
13
- secondary: "border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:ring-blue-400/30",
14
- ghost: "text-gray-700 hover:bg-gray-100 focus:ring-blue-400/30",
15
- "ghost-danger": "text-red-600 hover:bg-red-50 focus:ring-red-400/30",
16
- danger: "bg-red-600 text-white shadow-sm hover:bg-red-700 focus:ring-red-400/40",
17
- /**
18
- * A button that reads as a link: no box, no padding, underlined on hover.
19
- * For an action that belongs in running text or beside a field — "Forgot
20
- * your password?", "Use a different address" — where a boxed button would
21
- * claim more of the page than the action deserves.
22
- *
23
- * It is still a <button>. Anything that NAVIGATES should be an anchor, so it
24
- * can be middle-clicked and read off the status bar; this is for actions
25
- * that merely look quiet.
26
- */
27
- link: "text-blue-600 underline-offset-4 hover:underline focus:ring-blue-400/30"
28
- };
29
- var SIZES = {
30
- sm: "gap-1 px-2.5 py-1 text-xs",
31
- md: "gap-1.5 px-3 py-1.5 text-sm",
32
- // `py-2.5`, not `py-2`: this rung has to come out 40px to match IconButton's
33
- // `h-10`, which is the promise IconButton's docblock makes for every rung.
34
- // `py-2` gives 36px (20px line box + 8px twice) and puts a 4px step between a
35
- // Button and an IconButton sitting in the same row.
36
- lg: "gap-2 px-4 py-2.5 text-sm",
37
- // 44px is the WCAG 2.5.5 floor, and a floor is not a comfortable size. Use it
38
- // ONLY for chrome that sits outside the task — switching screens, ending a
39
- // shift — and never on the path someone is actually working through. It
40
- // exists so a dense toolbar has something legitimate to reach for instead of
41
- // borrowing the desktop `sm`, which is half this height.
42
- "touch-sm": "gap-1.5 h-11 px-4 text-base",
43
- "touch": "gap-2 h-14 px-5 text-base",
44
- // 56px — the comfortable default
45
- "touch-lg": "gap-2 h-16 px-6 text-lg",
46
- // 64px — primary action on a page
47
- "touch-xl": "gap-3 h-20 px-8 text-2xl"
48
- // 80px — keypad keys, tender
49
- };
50
- var LINK_BASE = "inline-flex items-center font-medium transition-colors focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-60";
51
- var LINK_SIZES = {
52
- sm: "gap-1 text-xs",
53
- md: "gap-1.5 text-sm",
54
- lg: "gap-2 text-sm",
55
- "touch-sm": "gap-1.5 text-base",
56
- "touch": "gap-2 text-base",
57
- "touch-lg": "gap-2 text-lg",
58
- "touch-xl": "gap-3 text-2xl"
59
- };
60
- function Spinner() {
61
- return /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5 animate-spin", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
62
- /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
63
- /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
64
- ] });
65
- }
66
- var Button = forwardRef(function Button2({
67
- variant = "primary",
68
- size = "md",
69
- loading = false,
70
- leftIcon,
71
- rightIcon,
72
- block = false,
73
- disabled,
74
- type = "button",
75
- children,
76
- className = "",
77
- disabledReason,
78
- ...rest
79
- }, ref) {
80
- const generatedId = useId();
81
- const isDisabled = disabled || loading;
82
- const showReason = isDisabled && disabledReason != null && disabledReason !== "";
83
- const reasonId = showReason ? generatedId : void 0;
84
- const button = /* @__PURE__ */ jsxs(
85
- "button",
86
- {
87
- ref,
88
- type,
89
- disabled: isDisabled,
90
- "aria-busy": loading || void 0,
91
- "aria-describedby": reasonId,
92
- className: [
93
- // `link` takes the base WITHOUT the box: no rounding, no padding from
94
- // the size table. It cannot cancel those by overriding — two padding
95
- // utilities in one class attribute resolve by compiled-stylesheet
96
- // order, not by the order they were written — so it never receives
97
- // them, and keeps only the size's gap and text size.
98
- variant === "link" ? LINK_BASE : BUTTON_BASE,
99
- BUTTON_VARIANTS[variant],
100
- variant === "link" ? LINK_SIZES[size] : SIZES[size],
101
- block ? "w-full" : "",
102
- className
103
- ].filter(Boolean).join(" "),
104
- ...rest,
105
- children: [
106
- loading ? /* @__PURE__ */ jsx(Spinner, {}) : leftIcon,
107
- children,
108
- !loading && rightIcon
109
- ]
110
- }
111
- );
112
- if (!showReason) return button;
113
- return /* @__PURE__ */ jsxs("span", { className: block ? "flex w-full flex-col gap-1" : "inline-flex flex-col items-start gap-1", children: [
114
- button,
115
- /* @__PURE__ */ jsx("span", { id: reasonId, className: "text-xs text-red-600", children: disabledReason })
116
- ] });
117
- });
118
- var Button_default = Button;
119
11
  var SQUARE = {
120
12
  sm: "h-6 w-6",
121
13
  md: "h-8 w-8",
@@ -714,7 +606,7 @@ function isVideoUrl(url, accept = "") {
714
606
  const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
715
607
  return VIDEO_EXT_RE.test(url) || acceptsVideo && !acceptsImage;
716
608
  }
717
- function Spinner2({ className = "h-5 w-5" }) {
609
+ function Spinner({ className = "h-5 w-5" }) {
718
610
  return /* @__PURE__ */ jsxs("svg", { className: `${className} animate-spin text-gray-400`, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
719
611
  /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
720
612
  /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
@@ -863,7 +755,7 @@ function MediaUploadField({
863
755
  className: "absolute inset-0 flex items-center justify-center gap-2",
864
756
  style: { background: "rgba(255,255,255,0.6)" },
865
757
  children: [
866
- /* @__PURE__ */ jsx(Spinner2, {}),
758
+ /* @__PURE__ */ jsx(Spinner, {}),
867
759
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-600", children: busyLabel })
868
760
  ]
869
761
  }
@@ -902,7 +794,7 @@ function MediaUploadField({
902
794
  boxShadow: focused && !locked ? "0 0 0 2px rgba(59,130,246,0.45)" : void 0
903
795
  },
904
796
  children: busy ? /* @__PURE__ */ jsxs("span", { role: "status", className: "flex flex-col items-center gap-1.5", children: [
905
- /* @__PURE__ */ jsx(Spinner2, {}),
797
+ /* @__PURE__ */ jsx(Spinner, {}),
906
798
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: busyLabel })
907
799
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
908
800
  /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
@@ -917,6 +809,292 @@ function MediaUploadField({
917
809
  !onPick && /* @__PURE__ */ jsx("input", { ref: inputRef, type: "file", accept, className: "hidden", onChange: onNativeFile })
918
810
  ] });
919
811
  }
812
+ function byteLimitMessage(maxBytes) {
813
+ return `Image must be smaller than ${maxBytes.toLocaleString()} bytes.`;
814
+ }
815
+ function actionNoun(assetName) {
816
+ const words = assetName.trim().toLocaleLowerCase().split(/\s+/);
817
+ return words.at(-1) || "image";
818
+ }
819
+ function acceptsFile(file, accept) {
820
+ const fileName = file.name.toLocaleLowerCase();
821
+ const fileType = file.type.toLocaleLowerCase();
822
+ return accept.split(",").map((value) => value.trim().toLocaleLowerCase()).some((rule) => {
823
+ if (!rule) return false;
824
+ if (rule.startsWith(".")) return fileName.endsWith(rule);
825
+ if (rule.endsWith("/*")) return fileType.startsWith(rule.slice(0, -1));
826
+ return fileType === rule;
827
+ });
828
+ }
829
+ function BrandAssetEditor({
830
+ committedUrl,
831
+ committedHasAlpha = null,
832
+ committedIsLight = null,
833
+ fallbackUrl,
834
+ fallbackHasAlpha = null,
835
+ fallbackIsLight = null,
836
+ subjectName,
837
+ assetName,
838
+ onSave,
839
+ onRemove,
840
+ // `image/x-icon` alone rejects most real .ico files: browsers report them as
841
+ // `image/vnd.microsoft.icon`, and some report nothing at all — hence the
842
+ // extension rule, which `acceptsFile` matches on the file name.
843
+ accept = "image/png,image/x-icon,image/vnd.microsoft.icon,.ico,image/svg+xml,image/jpeg,image/webp",
844
+ acceptHint = "PNG \xB7 ICO \xB7 SVG \xB7 JPG \xB7 WEBP",
845
+ maxBytes = 5 * 1024 * 1024,
846
+ previews = [],
847
+ disabled = false
848
+ }) {
849
+ const inputRef = useRef(null);
850
+ const objectUrlRef = useRef(null);
851
+ const [stagedFile, setStagedFile] = useState(null);
852
+ const [stagedUrl, setStagedUrl] = useState("");
853
+ const [error, setError] = useState("");
854
+ const [saving, setSaving] = useState(false);
855
+ const [removing, setRemoving] = useState(false);
856
+ const noun = actionNoun(assetName);
857
+ const visibleUrl = stagedUrl || committedUrl || fallbackUrl || "";
858
+ const visibleHasAlpha = stagedFile ? null : committedUrl ? committedHasAlpha : fallbackHasAlpha;
859
+ const visibleIsLight = stagedFile ? null : committedUrl ? committedIsLight : fallbackIsLight;
860
+ useEffect(() => () => {
861
+ if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
862
+ }, []);
863
+ const discardObjectUrl = () => {
864
+ if (!objectUrlRef.current) return;
865
+ URL.revokeObjectURL(objectUrlRef.current);
866
+ objectUrlRef.current = null;
867
+ };
868
+ const stage = (file) => {
869
+ if (!file || disabled) return;
870
+ if (!acceptsFile(file, accept)) {
871
+ setError(`Choose a supported image (${acceptHint}).`);
872
+ return;
873
+ }
874
+ if (file.size > maxBytes) {
875
+ setError(byteLimitMessage(maxBytes));
876
+ return;
877
+ }
878
+ discardObjectUrl();
879
+ const nextUrl = URL.createObjectURL(file);
880
+ objectUrlRef.current = nextUrl;
881
+ setStagedFile(file);
882
+ setStagedUrl(nextUrl);
883
+ setError("");
884
+ };
885
+ const onInput = (event) => {
886
+ stage(event.target.files?.[0]);
887
+ event.target.value = "";
888
+ };
889
+ const save = async () => {
890
+ if (!stagedFile || disabled || saving) return;
891
+ setSaving(true);
892
+ setError("");
893
+ try {
894
+ await onSave(stagedFile);
895
+ discardObjectUrl();
896
+ setStagedFile(null);
897
+ setStagedUrl("");
898
+ } catch (cause) {
899
+ setError(cause instanceof Error ? cause.message : `Could not save ${noun}.`);
900
+ } finally {
901
+ setSaving(false);
902
+ }
903
+ };
904
+ const remove = async () => {
905
+ if (disabled || removing) return;
906
+ if (stagedFile) {
907
+ discardObjectUrl();
908
+ setStagedFile(null);
909
+ setStagedUrl("");
910
+ setError("");
911
+ return;
912
+ }
913
+ if (!committedUrl) return;
914
+ setRemoving(true);
915
+ setError("");
916
+ try {
917
+ await onRemove();
918
+ } catch (cause) {
919
+ setError(cause instanceof Error ? cause.message : `Could not remove ${noun}.`);
920
+ } finally {
921
+ setRemoving(false);
922
+ }
923
+ };
924
+ return /* @__PURE__ */ jsxs("section", { "aria-label": `${assetName} for ${subjectName}`, className: "grid gap-4 lg:grid-cols-2", children: [
925
+ /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", children: [
926
+ /* @__PURE__ */ jsx(
927
+ MediaUploadField,
928
+ {
929
+ value: visibleUrl,
930
+ onChange: () => {
931
+ },
932
+ onPick: (file) => file ? stage(file) : inputRef.current?.click(),
933
+ label: assetName,
934
+ hint: error || `Used for ${subjectName}.`,
935
+ error: error || void 0,
936
+ accept,
937
+ acceptHint,
938
+ fit: "contain",
939
+ height: 180,
940
+ allowRemove: false,
941
+ busy: saving || removing,
942
+ disabled
943
+ }
944
+ ),
945
+ /* @__PURE__ */ jsx("input", { ref: inputRef, type: "file", accept, hidden: true, onChange: onInput }),
946
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
947
+ /* @__PURE__ */ jsxs(Button_default, { onClick: save, disabled: !stagedFile || disabled, loading: saving, children: [
948
+ "Save ",
949
+ noun
950
+ ] }),
951
+ (stagedFile || committedUrl) && /* @__PURE__ */ jsx(Button_default, { variant: "ghost-danger", onClick: remove, disabled, loading: removing, children: stagedFile ? "Discard change" : `Remove ${noun}` })
952
+ ] })
953
+ ] }),
954
+ previews.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", "aria-label": `${assetName} previews`, children: [
955
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900", children: "Preview" }),
956
+ /* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-3", children: previews.map((preview) => {
957
+ const mark = /* @__PURE__ */ jsx(
958
+ BrandMark,
959
+ {
960
+ src: visibleUrl,
961
+ alt: `${subjectName} ${assetName}`,
962
+ slot: preview.slot,
963
+ surface: preview.surface,
964
+ adaptive: true,
965
+ hasAlpha: visibleHasAlpha,
966
+ isLight: visibleIsLight,
967
+ size: preview.kind === "browser-tab" ? 16 : void 0
968
+ }
969
+ );
970
+ return /* @__PURE__ */ jsxs(
971
+ "div",
972
+ {
973
+ "data-brand-preview": true,
974
+ className: preview.surface === "dark" ? "text-white" : "text-gray-900",
975
+ children: [
976
+ /* @__PURE__ */ jsx("div", { className: "mb-2 text-xs font-medium text-gray-500", children: preview.label }),
977
+ preview.kind === "browser-tab" ? /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1 border-b border-gray-200", children: [
978
+ /* @__PURE__ */ jsxs("div", { className: "flex max-w-[220px] items-center gap-2 rounded-t-lg border border-b-0 border-gray-200 bg-white px-3 py-2", children: [
979
+ mark,
980
+ /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-gray-700", children: preview.title ?? subjectName }),
981
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "\xD7" })
982
+ ] }),
983
+ /* @__PURE__ */ jsx("span", { className: "mb-1 ml-1 text-gray-400", children: "+" })
984
+ ] }) : preview.kind === "search-result" ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-3", children: [
985
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
986
+ /* @__PURE__ */ jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full border border-gray-200 bg-gray-50", children: mark }),
987
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 leading-tight", children: [
988
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs font-medium text-gray-700", children: preview.title ?? subjectName }),
989
+ preview.url && /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-gray-400", children: preview.url })
990
+ ] })
991
+ ] }),
992
+ /* @__PURE__ */ jsx("div", { className: "mt-1.5 text-sm font-medium text-blue-700", children: preview.title ?? subjectName }),
993
+ preview.description && /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500", children: preview.description })
994
+ ] }) : /* @__PURE__ */ jsxs("div", { className: [
995
+ "flex items-center gap-3 rounded-md border border-gray-200 p-3",
996
+ preview.surface === "dark" ? "bg-gray-900 text-white" : "bg-white text-gray-900"
997
+ ].join(" "), children: [
998
+ mark,
999
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: preview.title ?? preview.label })
1000
+ ] })
1001
+ ]
1002
+ },
1003
+ `${preview.label}-${preview.slot}-${preview.surface ?? "light"}`
1004
+ );
1005
+ }) })
1006
+ ] })
1007
+ ] });
1008
+ }
1009
+ function resolvePortalBrandAsset(branding, options = {}) {
1010
+ if (options.preferSquare && branding.logo_square_url) {
1011
+ return {
1012
+ src: branding.logo_square_url,
1013
+ hasAlpha: branding.logo_square_has_alpha,
1014
+ isLight: branding.logo_square_is_light,
1015
+ adaptive: true
1016
+ };
1017
+ }
1018
+ if (options.surface === "dark" && branding.logo_on_dark_url) {
1019
+ return {
1020
+ src: branding.logo_on_dark_url,
1021
+ hasAlpha: null,
1022
+ isLight: null,
1023
+ adaptive: false
1024
+ };
1025
+ }
1026
+ return {
1027
+ src: branding.logo_url,
1028
+ hasAlpha: branding.logo_has_alpha,
1029
+ isLight: branding.logo_is_light,
1030
+ adaptive: true
1031
+ };
1032
+ }
1033
+ var PortalBrandingContext = createContext(null);
1034
+ function applyFavicon(url) {
1035
+ const selector = 'link[data-portal-branding="favicon"]';
1036
+ const existing = document.head.querySelector(selector);
1037
+ if (!url) {
1038
+ existing?.remove();
1039
+ return;
1040
+ }
1041
+ const link = existing ?? document.createElement("link");
1042
+ link.rel = "icon";
1043
+ link.href = url;
1044
+ link.dataset.portalBranding = "favicon";
1045
+ if (!existing) document.head.appendChild(link);
1046
+ }
1047
+ function PortalBrandingProvider({
1048
+ load,
1049
+ fallback,
1050
+ documentTitle,
1051
+ children
1052
+ }) {
1053
+ const [branding, setBranding] = useState(fallback);
1054
+ const [loading, setLoading] = useState(true);
1055
+ const [error, setError] = useState(null);
1056
+ const loadRef = useRef(load);
1057
+ const fallbackRef = useRef(fallback);
1058
+ loadRef.current = load;
1059
+ fallbackRef.current = fallback;
1060
+ useEffect(() => {
1061
+ const controller = new AbortController();
1062
+ let current = true;
1063
+ setBranding(fallbackRef.current);
1064
+ setLoading(true);
1065
+ setError(null);
1066
+ loadRef.current(controller.signal).then(
1067
+ (result) => {
1068
+ if (!current) return;
1069
+ setBranding(result);
1070
+ setLoading(false);
1071
+ },
1072
+ (cause) => {
1073
+ if (!current) return;
1074
+ setError(cause instanceof Error ? cause : new Error("Could not load portal branding."));
1075
+ setLoading(false);
1076
+ }
1077
+ );
1078
+ return () => {
1079
+ current = false;
1080
+ controller.abort();
1081
+ };
1082
+ }, []);
1083
+ useEffect(() => {
1084
+ applyFavicon(branding.favicon_url);
1085
+ return () => applyFavicon(null);
1086
+ }, [branding.favicon_url]);
1087
+ useEffect(() => {
1088
+ if (documentTitle) document.title = documentTitle(branding);
1089
+ }, [branding, documentTitle]);
1090
+ const value = useMemo(() => ({ branding, loading, error }), [branding, loading, error]);
1091
+ return /* @__PURE__ */ jsx(PortalBrandingContext.Provider, { value, children });
1092
+ }
1093
+ function usePortalBranding() {
1094
+ const value = useContext(PortalBrandingContext);
1095
+ if (!value) throw new Error("usePortalBranding must be used inside PortalBrandingProvider.");
1096
+ return value;
1097
+ }
920
1098
  var REMOVE_ICON = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", "aria-hidden": "true", className: "h-3 w-3", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" }) });
921
1099
  function MediaUploadGrid({
922
1100
  items,
@@ -1132,7 +1310,7 @@ function MediaUploadGrid({
1132
1310
  className: "absolute inset-0 flex items-center justify-center gap-2 rounded-lg",
1133
1311
  style: { background: "rgba(255,255,255,0.6)" },
1134
1312
  children: [
1135
- /* @__PURE__ */ jsx(Spinner2, {}),
1313
+ /* @__PURE__ */ jsx(Spinner, {}),
1136
1314
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-600", children: busyLabel })
1137
1315
  ]
1138
1316
  }
@@ -1860,7 +2038,7 @@ var DatePicker = forwardRef(function DatePicker2({
1860
2038
  id: panelId,
1861
2039
  role: "dialog",
1862
2040
  "aria-label": ariaLabel ?? "Choose a date",
1863
- className: "fixed z-[400] w-72 rounded-xl border border-gray-200 bg-white p-3 shadow-lg",
2041
+ className: "fixed z-[10000] w-72 rounded-xl border border-gray-200 bg-white p-3 shadow-lg",
1864
2042
  style: {
1865
2043
  left: pos?.left,
1866
2044
  right: pos?.right,
@@ -2021,7 +2199,7 @@ function NumericKeypad({
2021
2199
  )
2022
2200
  ] });
2023
2201
  }
2024
- var SIZES2 = {
2202
+ var SIZES = {
2025
2203
  md: "h-24 p-3",
2026
2204
  lg: "h-32 p-4"
2027
2205
  };
@@ -2038,7 +2216,7 @@ var TileButton = forwardRef(function TileButton2({ title, subtitle, size = "lg",
2038
2216
  "focus:outline-none focus:ring-2 focus:ring-blue-400/40",
2039
2217
  "disabled:cursor-not-allowed disabled:opacity-50",
2040
2218
  selected ? "border-blue-600 bg-blue-50 ring-2 ring-blue-400/40" : "border-gray-200 bg-white hover:bg-gray-50",
2041
- SIZES2[size],
2219
+ SIZES[size],
2042
2220
  className
2043
2221
  ].filter(Boolean).join(" "),
2044
2222
  ...rest,
@@ -3083,67 +3261,6 @@ function DescriptionList({
3083
3261
  )) })
3084
3262
  ] });
3085
3263
  }
3086
- var STATUS_TONE = {
3087
- success: "success",
3088
- error: "error",
3089
- warning: "warning",
3090
- info: "info",
3091
- // A missing page is not an error the user made, and colouring it red says it
3092
- // was. Refused and broken are genuinely different from not-found.
3093
- 404: "info",
3094
- 403: "warning",
3095
- 500: "error"
3096
- };
3097
- var TONE_CLASS = {
3098
- success: "text-green-600 bg-green-100",
3099
- error: "text-red-600 bg-red-100",
3100
- warning: "text-amber-600 bg-amber-100",
3101
- info: "text-blue-600 bg-blue-100"
3102
- };
3103
- var DEFAULT_TITLE = {
3104
- 404: "Page not found",
3105
- 403: "You don't have access to this",
3106
- 500: "Something went wrong"
3107
- };
3108
- function StatusIcon({ tone }) {
3109
- const common = { className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, "aria-hidden": true };
3110
- switch (tone) {
3111
- case "success":
3112
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) });
3113
- case "error":
3114
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) });
3115
- case "warning":
3116
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z" }) });
3117
- default:
3118
- return /* @__PURE__ */ jsx("svg", { ...common, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) });
3119
- }
3120
- }
3121
- function Result({
3122
- status = "info",
3123
- title,
3124
- subTitle,
3125
- extra,
3126
- icon,
3127
- children,
3128
- className = ""
3129
- }) {
3130
- const tone = STATUS_TONE[status];
3131
- const announces = tone === "error";
3132
- return /* @__PURE__ */ jsxs(
3133
- "div",
3134
- {
3135
- role: announces ? "alert" : void 0,
3136
- className: `flex flex-col items-center justify-center px-6 py-12 text-center ${className}`.trim(),
3137
- children: [
3138
- /* @__PURE__ */ jsx("div", { className: `flex h-14 w-14 items-center justify-center rounded-full ${TONE_CLASS[tone]}`, children: icon ?? /* @__PURE__ */ jsx(StatusIcon, { tone }) }),
3139
- /* @__PURE__ */ jsx("h2", { className: "mt-4 text-lg font-semibold text-gray-900", children: title ?? DEFAULT_TITLE[status] ?? "" }),
3140
- subTitle && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-md text-sm text-gray-500", children: subTitle }),
3141
- children && /* @__PURE__ */ jsx("div", { className: "mt-4 w-full max-w-md text-left", children }),
3142
- extra && /* @__PURE__ */ jsx("div", { className: "mt-6 flex flex-wrap items-center justify-center gap-3", children: extra })
3143
- ]
3144
- }
3145
- );
3146
- }
3147
3264
  var TONE2 = {
3148
3265
  danger: "bg-red-600 text-white",
3149
3266
  neutral: "bg-gray-600 text-white",
@@ -3721,17 +3838,6 @@ function PageHeader({
3721
3838
  ] })
3722
3839
  ] });
3723
3840
  }
3724
- function LoadingSpinner({ size = "md", padding = "py-12", label, className = "" }) {
3725
- const dim = size === "sm" ? "h-5 w-5" : size === "lg" ? "h-12 w-12" : "h-8 w-8";
3726
- const ring = /* @__PURE__ */ jsx("div", { className: `${dim} animate-spin rounded-full border-2 border-gray-200 border-t-blue-600` });
3727
- if (label == null) {
3728
- return /* @__PURE__ */ jsx("div", { className: `flex items-center justify-center ${padding} ${className}`, children: ring });
3729
- }
3730
- return /* @__PURE__ */ jsxs("div", { role: "status", className: `flex flex-col items-center justify-center gap-3 ${padding} ${className}`, children: [
3731
- ring,
3732
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500", children: label })
3733
- ] });
3734
- }
3735
3841
 
3736
3842
  // src/shell/severity.ts
3737
3843
  var SEVERITY_TONES = ["success", "warning", "danger"];
@@ -6554,6 +6660,6 @@ var ALT_SHIFT_D = isMac ? "\u2325\u21E7D" : "Alt+Shift+D";
6554
6660
  var ALT_SHIFT_E = isMac ? "\u2325\u21E7E" : "Alt+Shift+E";
6555
6661
  var ALT_SHIFT_N = isMac ? "\u2325\u21E7N" : "Alt+Shift+N";
6556
6662
 
6557
- export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, Button_default, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, CMD_SHIFT_Z, CMD_Z, Calendar, Card, ChatTemplate, Checkbox_default, CheckoutTemplate, ColoredBadge, ContainerFillChart, CountBadge, DashboardTemplate, DataTable, DataTablePage, DatePicker_default, DateRangePicker, DateTimePicker_default, DescriptionList, Divider, DonutChart, Drawer, DropdownMenu, ENTER, EmailTemplate, EmptyState, ErrorBoundary, ErrorPage, FilePicker, FormErrorSummary, FormField, FormLayoutPage, GROUP_COLORS, GalleryTemplate, GlobalSearch, Grid, HelpCenter, INPUT_BASE, INPUT_SIZES, IconButton_default, Inline, InputNumber_default, Input_default, Kanban, Label, LineChart, ListFooter, ListLoadError, LoadingSpinner, MAX_FRACTION_DIGITS, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NumericKeypad, PageHeader, Pagination, Paragraph, Radio_default, Result, SHIFT, SearchableSelect, Segmented, Select_default, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, Skeleton, Sparkline, Stack, StatCard, Statistic, StatusBadge, StatusBadgeProvider, Stepper, Switch_default, Tabs, TagInput, Text, Textarea_default, TileButton_default, TimePicker_default, Title, Tooltip, TopNav, appendKey, applyThemePrefs, backspace, firstEnabledIndex, formatDate, fromKey, inputClasses, isMac, isSeverityTone, lastEnabledIndex, matchTypeahead, mediaFileName, nextEnabledIndex, resolveTheme, severityOf, tabButtonId, tabPanelId, toISODate, toKey, useClickOutside, useTheme };
6558
- //# sourceMappingURL=chunk-YZVJCHXV.js.map
6559
- //# sourceMappingURL=chunk-YZVJCHXV.js.map
6663
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, BrandAssetEditor, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, CMD_SHIFT_Z, CMD_Z, Calendar, Card, ChatTemplate, Checkbox_default, CheckoutTemplate, ColoredBadge, ContainerFillChart, CountBadge, DashboardTemplate, DataTable, DataTablePage, DatePicker_default, DateRangePicker, DateTimePicker_default, DescriptionList, Divider, DonutChart, Drawer, DropdownMenu, ENTER, EmailTemplate, EmptyState, ErrorBoundary, ErrorPage, FilePicker, FormErrorSummary, FormField, FormLayoutPage, GROUP_COLORS, GalleryTemplate, GlobalSearch, Grid, HelpCenter, INPUT_BASE, INPUT_SIZES, IconButton_default, Inline, InputNumber_default, Input_default, Kanban, Label, LineChart, ListFooter, ListLoadError, MAX_FRACTION_DIGITS, MOD, Markdown, MediaUploadField, MediaUploadGrid, MetricBar, MilestoneTimeline, NativeSelect, NumericKeypad, PageHeader, Pagination, Paragraph, PortalBrandingProvider, Radio_default, SHIFT, SearchableSelect, Segmented, Select_default, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, Skeleton, Sparkline, Stack, StatCard, Statistic, StatusBadge, StatusBadgeProvider, Stepper, Switch_default, Tabs, TagInput, Text, Textarea_default, TileButton_default, TimePicker_default, Title, Tooltip, TopNav, appendKey, applyThemePrefs, backspace, firstEnabledIndex, formatDate, fromKey, inputClasses, isMac, isSeverityTone, lastEnabledIndex, matchTypeahead, mediaFileName, nextEnabledIndex, resolvePortalBrandAsset, resolveTheme, severityOf, tabButtonId, tabPanelId, toISODate, toKey, useClickOutside, usePortalBranding, useTheme };
6664
+ //# sourceMappingURL=chunk-LEMEFL6U.js.map
6665
+ //# sourceMappingURL=chunk-LEMEFL6U.js.map