react-os-shell 4.41.0 → 4.55.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/README.md +2 -1
- package/dist/{Browser-TKBH4DZ3.js → Browser-WHBSYGLC.js} +6 -6
- package/dist/{Browser-TKBH4DZ3.js.map → Browser-WHBSYGLC.js.map} +1 -1
- package/dist/{Calculator-XERXEV35.js → Calculator-PHKNMRLX.js} +5 -5
- package/dist/{Calculator-XERXEV35.js.map → Calculator-PHKNMRLX.js.map} +1 -1
- package/dist/{CurrencyConverter-OSZISLHT.js → CurrencyConverter-MEG7RHEK.js} +5 -5
- package/dist/{CurrencyConverter-OSZISLHT.js.map → CurrencyConverter-MEG7RHEK.js.map} +1 -1
- package/dist/{Documents-YRTLDTLX.js → Documents-Y32RNCTU.js} +7 -7
- package/dist/{Documents-YRTLDTLX.js.map → Documents-Y32RNCTU.js.map} +1 -1
- package/dist/{Files-WHYCDRYU.js → Files-LTG2S2KU.js} +8 -8
- package/dist/{Files-WHYCDRYU.js.map → Files-LTG2S2KU.js.map} +1 -1
- package/dist/{Notepad-6UAWRNZH.js → Notepad-P7J2OAXD.js} +7 -7
- package/dist/{Notepad-6UAWRNZH.js.map → Notepad-P7J2OAXD.js.map} +1 -1
- package/dist/{PomodoroTimer-5ZSIG7SQ.js → PomodoroTimer-5B5AFXQH.js} +7 -7
- package/dist/{PomodoroTimer-5ZSIG7SQ.js.map → PomodoroTimer-5B5AFXQH.js.map} +1 -1
- package/dist/{Preview-VBIWWJLK.js → Preview-XZQ55YGY.js} +8 -8
- package/dist/{Preview-VBIWWJLK.js.map → Preview-XZQ55YGY.js.map} +1 -1
- package/dist/{Spreadsheet-GRD4P3OI.js → Spreadsheet-KKSLCSIK.js} +6 -6
- package/dist/{Spreadsheet-GRD4P3OI.js.map → Spreadsheet-KKSLCSIK.js.map} +1 -1
- package/dist/{Stock-4BORXILI.js → Stock-KTWRNBI6.js} +5 -5
- package/dist/{Stock-4BORXILI.js.map → Stock-KTWRNBI6.js.map} +1 -1
- package/dist/{Weather-ZPPSEUIY.js → Weather-B7MO7N4N.js} +5 -5
- package/dist/{Weather-ZPPSEUIY.js.map → Weather-B7MO7N4N.js.map} +1 -1
- package/dist/{WorldClock-LBKYWDWE.js → WorldClock-M5MEV74F.js} +5 -5
- package/dist/{WorldClock-LBKYWDWE.js.map → WorldClock-M5MEV74F.js.map} +1 -1
- package/dist/apps/index.js +16 -16
- package/dist/{chunk-22BNPLM4.js → chunk-3OCL3DB4.js} +3 -3
- package/dist/{chunk-22BNPLM4.js.map → chunk-3OCL3DB4.js.map} +1 -1
- package/dist/{chunk-GUWAKATU.js → chunk-5S65ZC5W.js} +3 -3
- package/dist/{chunk-GUWAKATU.js.map → chunk-5S65ZC5W.js.map} +1 -1
- package/dist/{chunk-D4WFGAUW.js → chunk-CTTHBTOX.js} +326 -61
- package/dist/chunk-CTTHBTOX.js.map +1 -0
- package/dist/{chunk-X5PZYTAK.js → chunk-ECE3B3FZ.js} +3 -3
- package/dist/{chunk-X5PZYTAK.js.map → chunk-ECE3B3FZ.js.map} +1 -1
- package/dist/{chunk-X6DOWIY2.js → chunk-FHPPPDHL.js} +3 -3
- package/dist/{chunk-X6DOWIY2.js.map → chunk-FHPPPDHL.js.map} +1 -1
- package/dist/{chunk-OI253GOA.js → chunk-IOCOH2QQ.js} +4 -4
- package/dist/{chunk-OI253GOA.js.map → chunk-IOCOH2QQ.js.map} +1 -1
- package/dist/chunk-LCLB7YBY.js +7 -0
- package/dist/{chunk-TQFUNASX.js.map → chunk-LCLB7YBY.js.map} +1 -1
- package/dist/{chunk-COTCIYLS.js → chunk-S3ZIBWRY.js} +4 -4
- package/dist/{chunk-COTCIYLS.js.map → chunk-S3ZIBWRY.js.map} +1 -1
- package/dist/{chunk-4AKVHY4B.js → chunk-TK6HI2U4.js} +23 -3
- package/dist/chunk-TK6HI2U4.js.map +1 -0
- package/dist/{chunk-C22EFSND.js → chunk-ZCDFNZQS.js} +21 -6
- package/dist/chunk-ZCDFNZQS.js.map +1 -0
- package/dist/{index-B4RRJ5bp.d.ts → index-BE0z2D6y.d.ts} +212 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -14
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +4 -4
- package/dist/ui.css +117 -43
- package/package.json +1 -1
- package/dist/chunk-4AKVHY4B.js.map +0 -1
- package/dist/chunk-C22EFSND.js.map +0 -1
- package/dist/chunk-D4WFGAUW.js.map +0 -1
- package/dist/chunk-TQFUNASX.js +0 -7
package/dist/ui.css
CHANGED
|
@@ -28,6 +28,40 @@
|
|
|
28
28
|
|
|
29
29
|
@import "./themes.css";
|
|
30
30
|
|
|
31
|
+
/*
|
|
32
|
+
* Shape and type, as tokens the consuming app inherits.
|
|
33
|
+
*
|
|
34
|
+
* Every value here equals Tailwind's own default, so adding this changes
|
|
35
|
+
* nothing that renders. What it changes is WHERE the value comes from: a
|
|
36
|
+
* portal that deletes its own `@theme` block now takes its radius and its
|
|
37
|
+
* typeface from this package, and one edit here moves all of them.
|
|
38
|
+
*
|
|
39
|
+
* A consumer's `@theme` still wins — Tailwind merges the blocks and the later
|
|
40
|
+
* declaration replaces this one — so a portal that wants its own shape keeps
|
|
41
|
+
* it by saying so, rather than by this file not having an opinion.
|
|
42
|
+
*
|
|
43
|
+
* "Later" is a REQUIREMENT, not an observation: import this file ABOVE your own
|
|
44
|
+
* `@theme`. A portal that declares `--font-sans` and then imports the kit below
|
|
45
|
+
* it has its typeface silently reset to the system stack, with nothing in the
|
|
46
|
+
* diff to explain it. All five consuming portals import the kit near the top of
|
|
47
|
+
* `index.css` and declare after it, which is why this reads as a no-op today.
|
|
48
|
+
*
|
|
49
|
+
* On type: the token is here, the TYPEFACE is not. Shipping a face means
|
|
50
|
+
* hosting and licensing it, which is a product decision rather than a
|
|
51
|
+
* packaging one. Until that is made, a portal that sets nothing gets the
|
|
52
|
+
* system stack — the same thing it got before this block existed.
|
|
53
|
+
*/
|
|
54
|
+
@theme {
|
|
55
|
+
--radius-sm: 0.25rem;
|
|
56
|
+
--radius-md: 0.375rem;
|
|
57
|
+
--radius-lg: 0.5rem;
|
|
58
|
+
--radius-xl: 0.75rem;
|
|
59
|
+
--radius-2xl: 1rem;
|
|
60
|
+
|
|
61
|
+
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
62
|
+
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
63
|
+
}
|
|
64
|
+
|
|
31
65
|
/* Tailwind v4 doesn't scan node_modules by default — tell it to scan this
|
|
32
66
|
package's compiled JS so utility classes used in kit components get
|
|
33
67
|
generated when consumers import react-os-shell/ui.css. */
|
|
@@ -73,39 +107,79 @@ html.rosh-reduce-transparency *::after {
|
|
|
73
107
|
═══════════════════════════════════════════════════ */
|
|
74
108
|
[data-theme="dark"] {
|
|
75
109
|
color-scheme: dark;
|
|
110
|
+
|
|
111
|
+
/*
|
|
112
|
+
* The neutral ramp, as variables.
|
|
113
|
+
*
|
|
114
|
+
* Every rule below reads these rather than a literal, so a consumer that
|
|
115
|
+
* wants a different dark — warmer, higher contrast, its own brand — sets
|
|
116
|
+
* twelve values instead of forking 100 rules. The status hues (red for
|
|
117
|
+
* danger, amber for warning) stay literal on purpose: they mean the same
|
|
118
|
+
* thing in every product, and a consumer redefining "danger" is a different
|
|
119
|
+
* conversation from one restyling its greys.
|
|
120
|
+
*
|
|
121
|
+
* The names say what a step DOES rather than which Tailwind number it
|
|
122
|
+
* replaces, because in a light theme the numbers run the other way round
|
|
123
|
+
* while the roles do not.
|
|
124
|
+
*/
|
|
125
|
+
--surface: #1e1e2e;
|
|
126
|
+
--surface-sunken: #181825;
|
|
127
|
+
--surface-raised: #313244;
|
|
128
|
+
--line-subtle: #2a2a3c;
|
|
129
|
+
--line: #313244;
|
|
130
|
+
--line-strong: #45475a;
|
|
131
|
+
--ink-faintest: #6c7086;
|
|
132
|
+
--ink-faint: #7f849c;
|
|
133
|
+
/*
|
|
134
|
+
* Currently unreachable, and worth knowing before you set it.
|
|
135
|
+
*
|
|
136
|
+
* Its only consumer is `.text-gray-600` below, which a later rule overrides
|
|
137
|
+
* to `--ink` so short taskbar-button labels stay readable on dark glass.
|
|
138
|
+
* That override is global by design — shell.css's docblock spells out why a
|
|
139
|
+
* bare utility selector cannot live there — so `text-gray-600` renders
|
|
140
|
+
* `--ink` everywhere and this step renders nowhere.
|
|
141
|
+
*
|
|
142
|
+
* Left declared rather than deleted: it names a real rung, and a consumer
|
|
143
|
+
* scoping its own `.text-gray-600` rule can reach it. Deleting it would be a
|
|
144
|
+
* rendering change, which this release deliberately is not.
|
|
145
|
+
*/
|
|
146
|
+
--ink-muted: #9399b2;
|
|
147
|
+
--ink: #a6adc8;
|
|
148
|
+
--ink-strong: #bac2de;
|
|
149
|
+
--ink-strongest: #cdd6f4;
|
|
76
150
|
}
|
|
77
151
|
|
|
78
152
|
/* Backgrounds */
|
|
79
|
-
[data-theme="dark"] .bg-white { background-color:
|
|
80
|
-
[data-theme="dark"] .bg-gray-50 { background-color:
|
|
81
|
-
[data-theme="dark"] .bg-gray-100 { background-color:
|
|
82
|
-
[data-theme="dark"] .bg-gray-200 { background-color:
|
|
153
|
+
[data-theme="dark"] .bg-white { background-color: var(--surface) !important; }
|
|
154
|
+
[data-theme="dark"] .bg-gray-50 { background-color: var(--surface-sunken) !important; }
|
|
155
|
+
[data-theme="dark"] .bg-gray-100 { background-color: var(--surface) !important; }
|
|
156
|
+
[data-theme="dark"] .bg-gray-200 { background-color: var(--surface-raised) !important; }
|
|
83
157
|
|
|
84
158
|
/* Text */
|
|
85
|
-
[data-theme="dark"] .text-gray-900 { color:
|
|
86
|
-
[data-theme="dark"] .text-gray-800 { color:
|
|
87
|
-
[data-theme="dark"] .text-gray-700 { color:
|
|
88
|
-
[data-theme="dark"] .text-gray-600 { color:
|
|
89
|
-
[data-theme="dark"] .text-gray-500 { color:
|
|
90
|
-
[data-theme="dark"] .text-gray-400 { color:
|
|
159
|
+
[data-theme="dark"] .text-gray-900 { color: var(--ink-strongest) !important; }
|
|
160
|
+
[data-theme="dark"] .text-gray-800 { color: var(--ink-strong) !important; }
|
|
161
|
+
[data-theme="dark"] .text-gray-700 { color: var(--ink) !important; }
|
|
162
|
+
[data-theme="dark"] .text-gray-600 { color: var(--ink-muted) !important; }
|
|
163
|
+
[data-theme="dark"] .text-gray-500 { color: var(--ink-faint) !important; }
|
|
164
|
+
[data-theme="dark"] .text-gray-400 { color: var(--ink-faintest) !important; }
|
|
91
165
|
|
|
92
166
|
/* Borders */
|
|
93
|
-
[data-theme="dark"] .border-gray-200 { border-color:
|
|
94
|
-
[data-theme="dark"] .border-gray-300 { border-color:
|
|
95
|
-
[data-theme="dark"] .border-gray-100 { border-color:
|
|
167
|
+
[data-theme="dark"] .border-gray-200 { border-color: var(--line) !important; }
|
|
168
|
+
[data-theme="dark"] .border-gray-300 { border-color: var(--line-strong) !important; }
|
|
169
|
+
[data-theme="dark"] .border-gray-100 { border-color: var(--line-subtle) !important; }
|
|
96
170
|
|
|
97
171
|
/* Cards, shadows, tables */
|
|
98
172
|
[data-theme="dark"] .shadow { --tw-shadow-color: rgba(0,0,0,0.4) !important; }
|
|
99
173
|
[data-theme="dark"] .shadow-lg { --tw-shadow-color: rgba(0,0,0,0.5) !important; }
|
|
100
174
|
[data-theme="dark"] .shadow-xl { --tw-shadow-color: rgba(0,0,0,0.5) !important; }
|
|
101
175
|
[data-theme="dark"] .shadow-2xl { --tw-shadow-color: rgba(0,0,0,0.6) !important; }
|
|
102
|
-
[data-theme="dark"] .divide-gray-200 > :not(:last-child) { border-color:
|
|
103
|
-
[data-theme="dark"] .divide-gray-100 > :not(:last-child) { border-color:
|
|
176
|
+
[data-theme="dark"] .divide-gray-200 > :not(:last-child) { border-color: var(--line) !important; }
|
|
177
|
+
[data-theme="dark"] .divide-gray-100 > :not(:last-child) { border-color: var(--line-subtle) !important; }
|
|
104
178
|
|
|
105
179
|
/* Hover states */
|
|
106
|
-
[data-theme="dark"] .hover\:bg-gray-50:hover { background-color:
|
|
107
|
-
[data-theme="dark"] .hover\:bg-gray-100:hover { background-color:
|
|
108
|
-
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color:
|
|
180
|
+
[data-theme="dark"] .hover\:bg-gray-50:hover { background-color: var(--line-subtle) !important; }
|
|
181
|
+
[data-theme="dark"] .hover\:bg-gray-100:hover { background-color: var(--surface-raised) !important; }
|
|
182
|
+
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color: var(--line-strong) !important; }
|
|
109
183
|
|
|
110
184
|
/* Blue hover states — used by menu items, profile row, etc.
|
|
111
185
|
In dark mode swap the near-white bg for a dark blue tint and the dark
|
|
@@ -131,7 +205,7 @@ html.rosh-reduce-transparency *::after {
|
|
|
131
205
|
[data-theme="dark"] .\!bg-blue-50 { background-color: rgba(59, 130, 246, 0.26) !important; }
|
|
132
206
|
[data-theme="dark"] .\!bg-blue-100 { background-color: rgba(59, 130, 246, 0.22) !important; }
|
|
133
207
|
[data-theme="dark"] .\!bg-blue-200 { background-color: rgba(59, 130, 246, 0.35) !important; }
|
|
134
|
-
[data-theme="dark"] .\!text-gray-700 { color:
|
|
208
|
+
[data-theme="dark"] .\!text-gray-700 { color: var(--ink) !important; }
|
|
135
209
|
[data-theme="dark"] .bg-green-100 { background-color: rgba(34, 197, 94, 0.18) !important; }
|
|
136
210
|
[data-theme="dark"] .bg-emerald-100 { background-color: rgba(16, 185, 129, 0.18) !important; }
|
|
137
211
|
[data-theme="dark"] .bg-sky-100 { background-color: rgba(14, 165, 233, 0.18) !important; }
|
|
@@ -152,8 +226,8 @@ html.rosh-reduce-transparency *::after {
|
|
|
152
226
|
[data-theme="dark"] .text-purple-700 { color: #d8b4fe !important; }
|
|
153
227
|
|
|
154
228
|
/* Brighten taskbar button text so it stays readable on dark glass.
|
|
155
|
-
The default override (
|
|
156
|
-
[data-theme="dark"] .text-gray-600 { color:
|
|
229
|
+
The default override (var(--ink-muted)) is too dim for short button labels. */
|
|
230
|
+
[data-theme="dark"] .text-gray-600 { color: var(--ink) !important; }
|
|
157
231
|
|
|
158
232
|
/* Translucent button backgrounds — Tailwind generates separate classes for the
|
|
159
233
|
/40 /60 alpha variants (bg-gray-50\/40 etc.) so the bare .bg-gray-50 override
|
|
@@ -163,9 +237,9 @@ html.rosh-reduce-transparency *::after {
|
|
|
163
237
|
/* /50 and /60 are used as in-window PANEL surfaces (sidebars, form sections,
|
|
164
238
|
popup cards) — keep them solid in dark so a bright wallpaper can't bleed
|
|
165
239
|
through the window glass and wash them out. */
|
|
166
|
-
[data-theme="dark"] .bg-gray-50\/50 { background-color:
|
|
167
|
-
[data-theme="dark"] .bg-gray-50\/60 { background-color:
|
|
168
|
-
[data-theme="dark"] .hover\:bg-gray-50\/50:hover { background-color:
|
|
240
|
+
[data-theme="dark"] .bg-gray-50\/50 { background-color: var(--surface-sunken) !important; }
|
|
241
|
+
[data-theme="dark"] .bg-gray-50\/60 { background-color: var(--surface-sunken) !important; }
|
|
242
|
+
[data-theme="dark"] .hover\:bg-gray-50\/50:hover { background-color: var(--surface-sunken) !important; }
|
|
169
243
|
[data-theme="dark"] .bg-gray-100\/40 { background-color: rgba(30, 30, 46, 0.4) !important; }
|
|
170
244
|
[data-theme="dark"] .bg-gray-100\/60 { background-color: rgba(30, 30, 46, 0.6) !important; }
|
|
171
245
|
[data-theme="dark"] .bg-gray-200\/40 { background-color: rgba(49, 50, 68, 0.4) !important; }
|
|
@@ -200,9 +274,9 @@ html.rosh-reduce-transparency *::after {
|
|
|
200
274
|
[data-theme="dark"] input:not(.bg-transparent):not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),
|
|
201
275
|
[data-theme="dark"] select:not(.bg-transparent),
|
|
202
276
|
[data-theme="dark"] textarea:not(.bg-transparent) {
|
|
203
|
-
background-color:
|
|
204
|
-
border-color:
|
|
205
|
-
color:
|
|
277
|
+
background-color: var(--surface-sunken) !important;
|
|
278
|
+
border-color: var(--line-strong) !important;
|
|
279
|
+
color: var(--ink-strongest) !important;
|
|
206
280
|
}
|
|
207
281
|
|
|
208
282
|
/* iOS auto-zooms the viewport when a focused form control's computed
|
|
@@ -221,8 +295,8 @@ html.rosh-reduce-transparency *::after {
|
|
|
221
295
|
}
|
|
222
296
|
|
|
223
297
|
/* Status badges — tone down brightness in dark mode (matches StatusBadge.tsx semantic groups) */
|
|
224
|
-
[data-theme="dark"] .bg-gray-100.text-gray-800 { background-color:
|
|
225
|
-
[data-theme="dark"] .bg-gray-300.text-gray-800 { background-color:
|
|
298
|
+
[data-theme="dark"] .bg-gray-100.text-gray-800 { background-color: var(--surface-raised) !important; color: var(--ink) !important; }
|
|
299
|
+
[data-theme="dark"] .bg-gray-300.text-gray-800 { background-color: var(--line-strong) !important; color: var(--ink-strongest) !important; }
|
|
226
300
|
[data-theme="dark"] .bg-green-100.text-green-800 { background-color: rgba(34, 197, 94, 0.18) !important; color: #86efac !important; }
|
|
227
301
|
[data-theme="dark"] .bg-blue-100.text-blue-800 { background-color: rgba(59, 130, 246, 0.18) !important; color: #93c5fd !important; }
|
|
228
302
|
[data-theme="dark"] .bg-indigo-100.text-indigo-800 { background-color: rgba(99, 102, 241, 0.18) !important; color: #a5b4fc !important; }
|
|
@@ -385,30 +459,30 @@ html.rosh-reduce-transparency *::after {
|
|
|
385
459
|
|
|
386
460
|
/* Gray interaction-state variants (separate class names from the bare remaps) */
|
|
387
461
|
[data-theme="dark"] .hover\:text-gray-600:hover,
|
|
388
|
-
[data-theme="dark"] .hover\:text-gray-700:hover { color:
|
|
389
|
-
[data-theme="dark"] .hover\:text-gray-800:hover { color:
|
|
462
|
+
[data-theme="dark"] .hover\:text-gray-700:hover { color: var(--ink) !important; }
|
|
463
|
+
[data-theme="dark"] .hover\:text-gray-800:hover { color: var(--ink-strong) !important; }
|
|
390
464
|
[data-theme="dark"] .hover\:text-gray-900:hover,
|
|
391
|
-
[data-theme="dark"] .active\:text-gray-900:active { color:
|
|
392
|
-
[data-theme="dark"] .active\:bg-gray-100:active { background-color:
|
|
393
|
-
[data-theme="dark"] .active\:bg-gray-200:active { background-color:
|
|
465
|
+
[data-theme="dark"] .active\:text-gray-900:active { color: var(--ink-strongest) !important; }
|
|
466
|
+
[data-theme="dark"] .active\:bg-gray-100:active { background-color: var(--surface-raised) !important; }
|
|
467
|
+
[data-theme="dark"] .active\:bg-gray-200:active { background-color: var(--line-strong) !important; }
|
|
394
468
|
[data-theme="dark"] .active\:bg-gray-300:active,
|
|
395
469
|
[data-theme="dark"] .hover\:bg-gray-300:hover { background-color: #585b70 !important; }
|
|
396
|
-
[data-theme="dark"] .disabled\:bg-gray-100:disabled { background-color:
|
|
397
|
-
[data-theme="dark"] .disabled\:bg-gray-200:disabled { background-color:
|
|
470
|
+
[data-theme="dark"] .disabled\:bg-gray-100:disabled { background-color: var(--surface) !important; }
|
|
471
|
+
[data-theme="dark"] .disabled\:bg-gray-200:disabled { background-color: var(--surface-raised) !important; }
|
|
398
472
|
[data-theme="dark"] .hover\:bg-gray-200\/50:hover { background-color: rgba(69, 71, 90, 0.5) !important; }
|
|
399
473
|
[data-theme="dark"] .even\:bg-gray-50\/50:nth-child(even) { background-color: rgba(24, 24, 37, 0.5) !important; }
|
|
400
474
|
[data-theme="dark"] .border-gray-50,
|
|
401
|
-
[data-theme="dark"] .divide-gray-50 > :not(:last-child) { border-color:
|
|
475
|
+
[data-theme="dark"] .divide-gray-50 > :not(:last-child) { border-color: var(--line-subtle) !important; }
|
|
402
476
|
[data-theme="dark"] .border-gray-200\/50 { border-color: rgba(49, 50, 68, 0.6) !important; }
|
|
403
|
-
[data-theme="dark"] .hover\:border-gray-200:hover { border-color:
|
|
404
|
-
[data-theme="dark"] .hover\:border-gray-300:hover { border-color:
|
|
477
|
+
[data-theme="dark"] .hover\:border-gray-200:hover { border-color: var(--surface-raised) !important; }
|
|
478
|
+
[data-theme="dark"] .hover\:border-gray-300:hover { border-color: var(--line-strong) !important; }
|
|
405
479
|
|
|
406
480
|
/* White panel surfaces / solid black ink. hover-to-white means "emphasize",
|
|
407
481
|
which on dark glass means a solid panel tint; hover-to-black ink flips to
|
|
408
482
|
brighter, not darker (e.g. the taskbar bell). */
|
|
409
483
|
[data-theme="dark"] .bg-white\/85 { background-color: rgba(30, 30, 46, 0.85) !important; }
|
|
410
|
-
[data-theme="dark"] .hover\:bg-white:hover { background-color:
|
|
411
|
-
[data-theme="dark"] .text-black { color:
|
|
484
|
+
[data-theme="dark"] .hover\:bg-white:hover { background-color: var(--surface) !important; }
|
|
485
|
+
[data-theme="dark"] .text-black { color: var(--ink-strongest) !important; }
|
|
412
486
|
[data-theme="dark"] .hover\:text-black:hover { color: #ffffff !important; }
|
|
413
487
|
|
|
414
488
|
/* Scrollbar */
|
|
@@ -420,6 +494,6 @@ html.rosh-reduce-transparency *::after {
|
|
|
420
494
|
.grid-scroll::-webkit-scrollbar-corner { background: #f1f5f9; }
|
|
421
495
|
|
|
422
496
|
[data-theme="dark"] ::-webkit-scrollbar { width: 8px; height: 8px; }
|
|
423
|
-
[data-theme="dark"] ::-webkit-scrollbar-track { background:
|
|
424
|
-
[data-theme="dark"] ::-webkit-scrollbar-thumb { background:
|
|
497
|
+
[data-theme="dark"] ::-webkit-scrollbar-track { background: var(--surface-sunken); }
|
|
498
|
+
[data-theme="dark"] ::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 4px; }
|
|
425
499
|
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover { background: #585b70; }
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/toast.ts"],"names":[],"mappings":"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","file":"chunk-4AKVHY4B.js","sourcesContent":["/**\n * Two presentations:\n *\n * 1. toast.success / error / info — brief operation feedback, top-center,\n * auto-dismiss (success/error ~3s, info ~4.5s). The everyday \"what just\n * happened\" feedback — most messages want this.\n * 2. toast.notify — system notification, top-right card, stays 10s, dismissible.\n * For an alert worth lingering on; reach for it deliberately, not by default.\n *\n * (Historically `toast.info` rendered the top-right notification card; it now\n * renders a brief toast — that persistent card moved to `toast.notify`.)\n */\n\nconst TOAST_CONTAINER_ID = 'toast-container';\nconst TOAST_BOTTOM_CONTAINER_ID = 'toast-container-bottom';\nconst NOTIF_CONTAINER_ID = 'notif-container';\nconst FADE_MS = 300;\n\nexport type ToastPlacement = 'top' | 'bottom';\n\nexport interface ToastOptions {\n /** Milliseconds before auto-dismiss. Ignored when `sticky`. */\n duration?: number;\n /**\n * Stay until dismissed. For a message the user must actually act on — the\n * kind that must not scroll past while they are looking at a customer.\n * Implies tap-to-dismiss is the only way out, which it already is.\n */\n sticky?: boolean;\n /**\n * `bottom` centres toasts along the bottom edge. On a till the top of the\n * screen is the cart total and the bottom is nearest the hand.\n */\n placement?: ToastPlacement;\n /**\n * Drop a message identical to one already showing, and RESTART its timer\n * rather than stacking a second copy. A failing poll otherwise writes the\n * same sentence a dozen times. The restart matters: the user asked twice, so\n * the message should persist, not expire on the first one's schedule.\n */\n dedupe?: boolean;\n}\n\n/**\n * Defaults for every subsequent toast, set once at app startup — the same\n * \"consumer wires this once\" shape as `setShellApiClient`/`setShellNavIcons`.\n * A till calls `toast.configure({ placement: 'bottom', duration: 6000,\n * dedupe: true })` instead of repeating those options at 40 call sites.\n * Per-call options still win.\n */\nlet defaults: ToastOptions = {};\n\n/** Live toasts by dedupe key, so a repeat can find and refresh its own. */\nconst live = new Map<string, { el: HTMLElement; restart: () => void }>();\n\nfunction getOrCreate(id: string, className: string): HTMLElement {\n let el = document.getElementById(id);\n if (!el) {\n el = document.createElement('div');\n el.id = id;\n el.className = className;\n document.body.appendChild(el);\n }\n return el;\n}\n\nfunction getMenuOpacity(): number {\n try {\n const val = getComputedStyle(document.documentElement).getPropertyValue('--menu-opacity')?.trim();\n if (val) return parseFloat(val);\n } catch {}\n return 0.95;\n}\n\nfunction glassBackground(o: number): string {\n return `linear-gradient(135deg, rgba(255,255,255,${o * 0.85}) 0%, rgba(255,255,255,${o * 0.65}) 50%, rgba(255,255,255,${o * 0.75}) 100%)`;\n}\n\nconst GLASS_COMMON = `\n backdrop-filter: blur(40px) saturate(1.8); -webkit-backdrop-filter: blur(40px) saturate(1.8);\n border: 1px solid rgba(255,255,255,0.35);\n box-shadow: inset 0 1px 0 rgba(255,255,255,0.4), 0 8px 32px rgba(0,0,0,0.15), 0 2px 8px rgba(0,0,0,0.08);\n`;\n\n// ── Toast (operation feedback) — top-center, brief ──\n\nfunction showToast(variant: 'success' | 'error' | 'info', message: string,\n opts?: ToastOptions) {\n const opts_ = { ...defaults, ...opts };\n const placement: ToastPlacement = opts_.placement ?? 'top';\n const sticky = opts_.sticky ?? false;\n\n // A repeat of a message already on screen refreshes it instead of stacking.\n const key = `${placement}|${variant}|${message}`;\n if (opts_.dedupe) {\n const existing = live.get(key);\n if (existing) { existing.restart(); return; }\n }\n\n import('../utils/sounds').then(s => {\n if (variant === 'success') s.playSuccess();\n else if (variant === 'error') s.playError();\n else s.playNotification();\n }).catch(() => {});\n\n const container = placement === 'bottom'\n ? getOrCreate(TOAST_BOTTOM_CONTAINER_ID, 'fixed bottom-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col-reverse gap-2 items-center pointer-events-none')\n : getOrCreate(TOAST_CONTAINER_ID, 'fixed top-4 left-1/2 -translate-x-1/2 z-[9999] flex flex-col gap-2 items-center pointer-events-none');\n const o = getMenuOpacity();\n const color = variant === 'success' ? '#22c55e' : variant === 'error' ? '#ef4444' : '#3b82f6';\n const icons = {\n success: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M5 13l4 4L19 7\"/></svg>',\n error: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\"/></svg>',\n info: '<svg class=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"' + color + '\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"/></svg>',\n };\n const icon = icons[variant];\n\n // Enter from the edge the toast is anchored to, so it reads as arriving from\n // off-screen rather than drifting in from the wrong direction.\n const offscreen = placement === 'bottom' ? 'translateY(10px)' : 'translateY(-10px)';\n\n const el = document.createElement('div');\n el.className = 'pointer-events-auto cursor-pointer';\n el.style.cssText = `\n padding: 8px 20px; border-radius: 12px;\n background: ${glassBackground(o)}; ${GLASS_COMMON}\n opacity: 0; transform: ${offscreen} scale(0.95);\n transition: opacity ${FADE_MS}ms ease, transform ${FADE_MS}ms ease;\n display: flex; align-items: center; gap: 8px;\n font-size: 13px; font-weight: 500; color: rgb(55,65,81);\n max-width: min(90vw, 460px);\n `;\n el.innerHTML = icon;\n const span = document.createElement('span');\n span.textContent = message;\n el.appendChild(span);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateY(0) scale(1)'; });\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let gone = false;\n const dismiss = () => {\n if (gone) return;\n gone = true;\n if (timer) clearTimeout(timer);\n live.delete(key);\n el.style.opacity = '0';\n el.style.transform = `${offscreen} scale(0.95)`;\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n // Info messages are usually a sentence (\"no matches — check X\"); give them a\n // beat longer to read than the terse success/error confirmations.\n const duration = opts_.duration ?? (variant === 'info' ? 4500 : 3000);\n const restart = () => {\n if (timer) clearTimeout(timer);\n if (!sticky) timer = setTimeout(dismiss, duration);\n };\n restart();\n\n // Tap anywhere on the toast to dismiss it. A toast has no other click\n // affordance, and a sticky one has no other exit at all — `notify` has\n // behaved this way since it existed.\n el.addEventListener('click', dismiss);\n\n live.set(key, { el, restart });\n}\n\n// ── Notification (system alert) — top-right, stays longer ──\n\nfunction showNotification(message: string, opts?: { duration?: number }) {\n import('../utils/sounds').then(s => s.playNotification()).catch(() => {});\n\n const container = getOrCreate(NOTIF_CONTAINER_ID, 'fixed top-4 right-4 z-[9999] flex flex-col gap-3 items-end pointer-events-none');\n const o = getMenuOpacity();\n\n const el = document.createElement('div');\n el.className = 'pointer-events-auto cursor-pointer';\n el.style.cssText = `\n min-width: 280px; max-width: 380px; padding: 12px 16px; border-radius: 16px;\n background: ${glassBackground(o)}; ${GLASS_COMMON}\n opacity: 0; transform: translateX(30px) scale(0.95);\n transition: opacity ${FADE_MS}ms cubic-bezier(0.4,0,0.2,1), transform ${FADE_MS}ms cubic-bezier(0.4,0,0.2,1);\n display: flex; align-items: flex-start; gap: 12px;\n `;\n\n el.innerHTML = `\n <div style=\"width: 36px; height: 36px; border-radius: 10px; background: rgba(59,130,246,0.15); display: flex; align-items: center; justify-content: center; flex-shrink: 0;\">\n <svg class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"#3b82f6\" stroke-width=\"2\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0\"/></svg>\n </div>\n <div style=\"flex: 1; min-width: 0;\">\n <div style=\"font-size: 11px; font-weight: 600; color: #3b82f6; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 2px;\">Notification</div>\n <div class=\"notif-msg\" style=\"font-size: 13px; font-weight: 500; color: rgb(55,65,81); line-height: 1.4;\"></div>\n </div>\n <button style=\"flex-shrink: 0; padding: 4px; color: rgb(156,163,175); font-size: 18px; line-height: 1; transition: color 0.2s;\" onmouseenter=\"this.style.color='rgb(75,85,99)'\" onmouseleave=\"this.style.color='rgb(156,163,175)'\">×</button>\n `;\n\n const msgEl = el.querySelector('.notif-msg');\n if (msgEl) msgEl.textContent = message;\n\n const dismiss = () => {\n el.style.opacity = '0';\n el.style.transform = 'translateX(30px) scale(0.95)';\n setTimeout(() => el.remove(), FADE_MS);\n };\n\n el.querySelector('button')?.addEventListener('click', (e) => { e.stopPropagation(); dismiss(); });\n el.addEventListener('click', dismiss);\n\n container.appendChild(el);\n requestAnimationFrame(() => { el.style.opacity = '1'; el.style.transform = 'translateX(0) scale(1)'; });\n\n setTimeout(dismiss, opts?.duration ?? 10000);\n}\n\nconst toast = {\n success: (message: string, opts?: ToastOptions) => showToast('success', message, opts),\n error: (message: string, opts?: ToastOptions) => showToast('error', message, opts),\n info: (message: string, opts?: ToastOptions) => showToast('info', message, opts),\n // Persistent top-right notification card (the old toast.info presentation).\n notify: (message: string, opts?: { duration?: number }) => showNotification(message, opts),\n /**\n * Set defaults for every subsequent toast. Call once at app startup; later\n * calls replace the whole object rather than merging, so a consumer sets its\n * policy in one place. Per-call options still take precedence.\n */\n configure: (next: ToastOptions) => { defaults = { ...next }; },\n};\n\nexport default toast;\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shell/useIsMobile.ts","../src/shell/PopupMenu.tsx","../src/shell/ShellPrefs.tsx"],"names":["jsx"],"mappings":"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","file":"chunk-C22EFSND.js","sourcesContent":["/**\n * Mobile breakpoint hook — single source of truth for \"is this a phone/tablet\n * portrait viewport?\" Drives whether <Layout> renders the desktop chrome\n * (windows / taskbar / start menu sidebar) or the <MobileAppLanding> screen\n * that points the user at the consumer-wired mobile app. A handful of\n * primitives (Modal, ResizableTable) also read it to adapt touch behaviour.\n *\n * Locked at 768 px so iPad portrait counts as mobile. Also OR's with\n * `pointer: coarse` so touch-only devices that happen to be wider (e.g. an\n * Android tablet in landscape) still get touch-appropriate UI.\n */\nimport { useSyncExternalStore } from 'react';\n\nconst QUERY = '(max-width: 767px), (pointer: coarse)';\n\nfunction getSnapshot(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia(QUERY).matches;\n}\n\nfunction getServerSnapshot(): boolean {\n return false;\n}\n\nfunction subscribe(cb: () => void): () => void {\n if (typeof window === 'undefined' || !window.matchMedia) return () => {};\n const mql = window.matchMedia(QUERY);\n // Older Safari uses addListener; modern browsers use addEventListener.\n if (mql.addEventListener) {\n mql.addEventListener('change', cb);\n return () => mql.removeEventListener('change', cb);\n }\n mql.addListener(cb);\n return () => mql.removeListener(cb);\n}\n\nexport function useIsMobile(): boolean {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n","import { useEffect, useRef, type ReactNode, type CSSProperties } from 'react';\nimport { createPortal } from 'react-dom';\nimport { glassStyle, GLASS_DIVIDER } from '../utils/glass';\n\n/**\n * Unified popup menu component — used for all context menus, dropdowns, and flyouts.\n * Reads --menu-density CSS variable: 'tight', 'normal' (default), or 'large'.\n */\n\nexport interface PopupMenuProps {\n children: ReactNode;\n style?: CSSProperties;\n className?: string;\n onClose?: () => void;\n minWidth?: number;\n /** Render into document.body instead of in place. Required when the menu\n * is opened from INSIDE a window: the window panel is a transformed /\n * backdrop-filtered `overflow-hidden` container, which both re-anchors\n * `position: fixed` descendants to itself and clips them — a menu\n * positioned at viewport coordinates ends up offset or invisible.\n * Portaling restores true viewport positioning. */\n portal?: boolean;\n}\n\nfunction getDensity(): 'tight' | 'normal' | 'large' {\n return (getComputedStyle(document.documentElement).getPropertyValue('--menu-density')?.trim() as any) || 'normal';\n}\n\n/** Container for a popup menu — auto-clamps to stay within viewport */\nexport function PopupMenu({ children, style, className = '', onClose, minWidth = 180, portal = false }: PopupMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n if (!onClose) return;\n const handleClick = (e: PointerEvent | MouseEvent) => {\n if ((e.target as HTMLElement).closest('[data-menu-toggle]')) return;\n if (ref.current && !ref.current.contains(e.target as Node)) onClose();\n };\n const handleKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('pointerdown', handleClick);\n window.addEventListener('keydown', handleKey);\n return () => { window.removeEventListener('pointerdown', handleClick); window.removeEventListener('keydown', handleKey); };\n }, [onClose]);\n\n // After render, clamp position to viewport boundaries\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n requestAnimationFrame(() => {\n const rect = el.getBoundingClientRect();\n const margin = 8;\n // Clamp right edge\n if (rect.right > window.innerWidth - margin) {\n const overflow = rect.right - window.innerWidth + margin;\n if (el.style.left) {\n el.style.left = `${parseFloat(el.style.left) - overflow}px`;\n } else if (!el.style.right) {\n el.style.left = `${rect.left - overflow}px`;\n }\n }\n // Clamp left edge\n if (rect.left < margin) {\n if (el.style.left) {\n el.style.left = `${margin}px`;\n } else if (el.style.right) {\n el.style.right = `${window.innerWidth - rect.width - margin}px`;\n }\n }\n // Clamp bottom edge\n if (rect.bottom > window.innerHeight - margin) {\n const overflow = rect.bottom - window.innerHeight + margin;\n if (el.style.top) {\n el.style.top = `${parseFloat(el.style.top) - overflow}px`;\n } else if (el.style.bottom) {\n // Already anchored to bottom, just clamp\n el.style.bottom = `${margin}px`;\n } else {\n el.style.top = `${rect.top - overflow}px`;\n }\n }\n // Clamp top edge\n if (rect.top < margin) {\n if (el.style.top) {\n el.style.top = `${margin}px`;\n } else if (el.style.bottom) {\n el.style.bottom = `${window.innerHeight - rect.height - margin}px`;\n }\n }\n });\n });\n\n const density = getDensity();\n\n const menu = (\n <div ref={ref}\n className={`fixed z-[400] rounded-2xl ${density === 'tight' ? 'py-1' : density === 'large' ? 'py-2' : 'py-1.5'} ${className}`}\n style={{ minWidth, animation: 'popup-in 0.12s ease-out', ...glassStyle(), ...style }}>\n {children}\n <style>{`@keyframes popup-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }`}</style>\n </div>\n );\n\n return portal ? createPortal(menu, document.body) : menu;\n}\n\n/** A clickable menu item */\nexport function PopupMenuItem({ onClick, children, className = '', danger, disabled }: {\n onClick?: () => void;\n children: ReactNode;\n className?: string;\n danger?: boolean;\n disabled?: boolean;\n}) {\n const density = getDensity();\n // Vertical gap between items. `normal` sits a little tighter than the raw\n // size padding; `large` adds a bit more room. Floored at the tight value so\n // the reduced `normal` never drops below `tight` at the smallest menu size.\n const itemPadY = density === 'tight' ? '0.25rem'\n : density === 'large' ? '0.6rem'\n : 'max(0.25rem, calc(var(--menu-padding-y, 0.5rem) - 0.1rem))';\n return (\n <button\n onClick={onClick}\n disabled={disabled}\n className={`w-full flex items-center gap-2 text-left transition-colors rounded-lg mx-auto\n ${danger ? 'text-red-600 hover:bg-red-50' : 'text-gray-700 hover:bg-blue-50 hover:text-blue-700'}\n ${disabled ? 'opacity-40 cursor-not-allowed' : ''}\n ${className}`}\n style={{\n width: 'calc(100% - 8px)',\n marginLeft: 4,\n marginRight: 4,\n fontSize: 'var(--menu-font-size, 14px)',\n paddingLeft: 'var(--menu-padding-x, 1rem)',\n paddingRight: 'var(--menu-padding-x, 1rem)',\n paddingTop: itemPadY,\n paddingBottom: itemPadY,\n }}\n >\n {children}\n </button>\n );\n}\n\n/** A divider line between menu items */\nexport function PopupMenuDivider() {\n const density = getDensity();\n return <div className={`border-t ${GLASS_DIVIDER} ${density === 'tight' ? 'my-0.5' : density === 'large' ? 'my-1.5' : 'my-1'} mx-3`} />;\n}\n\n/** A section header label */\nexport function PopupMenuLabel({ children }: { children: ReactNode }) {\n const density = getDensity();\n const labelPadY = density === 'tight' ? '0.125rem' : density === 'large' ? '0.375rem' : '0.25rem';\n return (\n <div\n className=\"text-[10px] font-medium text-gray-400 uppercase tracking-wider\"\n style={{\n paddingLeft: 'var(--menu-padding-x, 1rem)',\n paddingRight: 'var(--menu-padding-x, 1rem)',\n paddingTop: labelPadY,\n paddingBottom: labelPadY,\n }}\n >\n {children}\n </div>\n );\n}\n\n/** An icon helper */\nexport function MenuIcon({ d, className = 'h-4 w-4 text-gray-400' }: { d: string; className?: string }) {\n return (\n <svg className={className} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d={d} />\n </svg>\n );\n}\n","/**\n * Shell-side user-preferences surface — the package never owns where prefs\n * live. The consumer supplies a hook returning the current prefs object plus\n * a save callback; the shell reads keys it knows about (theme,\n * taskbar_position, taskbar_size, world_clocks, favorite_documents,\n * desktop_folders, desktop_snap, notepad_notes, …) and patches them.\n *\n * When no provider is mounted, the shell falls back to localStorage scoped\n * by `storageKey` — so the package works out of the box for a backend-less\n * consumer.\n */\nimport { createContext, useContext, useState, useCallback, type ReactNode } from 'react';\n\nexport interface ShellPrefsAdapter {\n /** The current prefs object. The shell reads its known keys directly off\n * this; consumer-private keys are ignored. */\n prefs: Record<string, any>;\n /** Patch a subset of prefs. The shell calls this with shallow diffs. */\n save: (patch: Record<string, any>) => Promise<void> | void;\n}\n\nconst ShellPrefsContext = createContext<ShellPrefsAdapter | null>(null);\n\nexport function ShellPrefsProvider({\n value,\n children,\n}: { value: ShellPrefsAdapter; children: ReactNode }) {\n return <ShellPrefsContext.Provider value={value}>{children}</ShellPrefsContext.Provider>;\n}\n\n/** Default localStorage-backed adapter — useful when the consumer doesn't\n * ship a backend. Pass the result into <ShellPrefsProvider value={…}>.\n *\n * `defaults` are merged behind whatever's already stored, so they only\n * apply for keys the user hasn't set yet. Useful for opting out of bundled\n * features (e.g. `{ show_desktop_version: false }`). */\nexport function useLocalStoragePrefs(\n storageKey = 'react-os-shell:prefs',\n defaults?: Record<string, any>,\n): ShellPrefsAdapter {\n const [prefs, setPrefs] = useState<Record<string, any>>(() => {\n try {\n const stored = JSON.parse(localStorage.getItem(storageKey) || '{}');\n return { ...(defaults ?? {}), ...stored };\n } catch { return { ...(defaults ?? {}) }; }\n });\n const save = useCallback((patch: Record<string, any>) => {\n setPrefs(prev => {\n const next = { ...prev, ...patch };\n try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch {}\n return next;\n });\n }, [storageKey]);\n return { prefs, save };\n}\n\n/** Returns the active prefs adapter. When no provider is mounted, returns a\n * no-op adapter that reads as empty and silently drops saves — components\n * still render, but persistence is a no-op. */\nexport function useShellPrefs(): ShellPrefsAdapter {\n const ctx = useContext(ShellPrefsContext);\n if (ctx) return ctx;\n return { prefs: {}, save: () => {} };\n}\n"]}
|