@inferencesh/ui 0.0.0-stage → 0.1.1

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 (165) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -2
  3. package/components.json +22 -0
  4. package/package.json +231 -4
  5. package/src/components/audio-player.tsx +549 -0
  6. package/src/components/button.tsx +13 -0
  7. package/src/components/chart.tsx +403 -0
  8. package/src/components/circuit.tsx +276 -0
  9. package/src/components/code-block/code-block.tsx +260 -0
  10. package/src/components/code-block/index.ts +18 -0
  11. package/src/components/code-block/languages.ts +311 -0
  12. package/src/components/code-block/styles.ts +39 -0
  13. package/src/components/code-block/tokenizer.ts +290 -0
  14. package/src/components/code-block/types.ts +38 -0
  15. package/src/components/code-block/utils.ts +39 -0
  16. package/src/components/code.tsx +48 -0
  17. package/src/components/cookie-consent.tsx +137 -0
  18. package/src/components/copy-button.tsx +44 -0
  19. package/src/components/country-dropdown.tsx +188 -0
  20. package/src/components/glow-loader-fast.tsx +32 -0
  21. package/src/components/glow-loader-mono.tsx +33 -0
  22. package/src/components/glow-loader-sm-fast.tsx +32 -0
  23. package/src/components/glow-loader-sm-mono.tsx +34 -0
  24. package/src/components/glow-loader-sm.tsx +32 -0
  25. package/src/components/glow-loader.tsx +32 -0
  26. package/src/components/glow-logo.tsx +32 -0
  27. package/src/components/gradient.tsx +80 -0
  28. package/src/components/index.ts +45 -0
  29. package/src/components/inline-sidebar.tsx +351 -0
  30. package/src/components/input-color.tsx +349 -0
  31. package/src/components/logo-base.tsx +213 -0
  32. package/src/components/logo-color.tsx +23 -0
  33. package/src/components/logo-full.tsx +71 -0
  34. package/src/components/logo-svg.tsx +23 -0
  35. package/src/components/menu-navigation.tsx +359 -0
  36. package/src/components/multi-select.tsx +721 -0
  37. package/src/components/pastel-gradient.tsx +25 -0
  38. package/src/components/pixel-symmetry.tsx +151 -0
  39. package/src/components/progress.tsx +28 -0
  40. package/src/components/scroll-more.tsx +131 -0
  41. package/src/components/select.tsx +85 -0
  42. package/src/components/shimmering-text.tsx +108 -0
  43. package/src/components/sidebar-light.tsx +102 -0
  44. package/src/components/sonner.tsx +38 -0
  45. package/src/components/theme-switcher-sidebar.tsx +56 -0
  46. package/src/components/theme-switcher-simple.tsx +110 -0
  47. package/src/components/theme-switcher.tsx +47 -0
  48. package/src/components/toggle-group.tsx +55 -0
  49. package/src/components/tooltip-button.tsx +91 -0
  50. package/src/components/tooltip.tsx +71 -0
  51. package/src/components/tree-view.tsx +499 -0
  52. package/src/components/unglow-loader.tsx +33 -0
  53. package/src/components/waveform.tsx +1632 -0
  54. package/src/components/zoomable-image.tsx +76 -0
  55. package/src/docs/api-docs.tsx +763 -0
  56. package/src/docs/callout.tsx +160 -0
  57. package/src/docs/code-group.tsx +215 -0
  58. package/src/docs/copy-menu.tsx +143 -0
  59. package/src/docs/doc-tabs.tsx +84 -0
  60. package/src/docs/index.ts +16 -0
  61. package/src/docs/steps.tsx +105 -0
  62. package/src/docs/table-of-contents.tsx +174 -0
  63. package/src/examples/guide-markdown.ts +129 -0
  64. package/src/examples/index.ts +1153 -0
  65. package/src/global.d.ts +1 -0
  66. package/src/hooks/use-copy-to-clipboard.ts +36 -0
  67. package/src/hooks/use-mobile.ts +19 -0
  68. package/src/lib/display-name.ts +15 -0
  69. package/src/lib/example-input.ts +130 -0
  70. package/src/lib/plans.ts +44 -0
  71. package/src/lib/safe-content.ts +137 -0
  72. package/src/lib/schema.ts +320 -0
  73. package/src/lib/storage.ts +108 -0
  74. package/src/markdown/docs-markdown-renderer.tsx +518 -0
  75. package/src/markdown/helpers.ts +95 -0
  76. package/src/markdown/index.ts +24 -0
  77. package/src/markdown/markdown-renderer.tsx +232 -0
  78. package/src/markdown/youtube-embed.tsx +43 -0
  79. package/src/og/image.ts +312 -0
  80. package/src/og/index.tsx +430 -0
  81. package/src/og/query.ts +26 -0
  82. package/src/shadcn/accordion.tsx +71 -0
  83. package/src/shadcn/alert-dialog.tsx +187 -0
  84. package/src/shadcn/alert.tsx +75 -0
  85. package/src/shadcn/aspect-ratio.tsx +22 -0
  86. package/src/shadcn/attachment.tsx +207 -0
  87. package/src/shadcn/avatar.tsx +106 -0
  88. package/src/shadcn/badge.tsx +51 -0
  89. package/src/shadcn/breadcrumb.tsx +124 -0
  90. package/src/shadcn/bubble.tsx +127 -0
  91. package/src/shadcn/button-group.tsx +87 -0
  92. package/src/shadcn/button.tsx +57 -0
  93. package/src/shadcn/calendar.tsx +219 -0
  94. package/src/shadcn/card.tsx +102 -0
  95. package/src/shadcn/carousel.tsx +242 -0
  96. package/src/shadcn/chart.tsx +372 -0
  97. package/src/shadcn/checkbox.tsx +28 -0
  98. package/src/shadcn/collapsible.tsx +19 -0
  99. package/src/shadcn/combobox.tsx +297 -0
  100. package/src/shadcn/command.tsx +194 -0
  101. package/src/shadcn/context-menu.tsx +270 -0
  102. package/src/shadcn/dialog.tsx +158 -0
  103. package/src/shadcn/direction.tsx +4 -0
  104. package/src/shadcn/drawer.tsx +227 -0
  105. package/src/shadcn/dropdown-menu.tsx +265 -0
  106. package/src/shadcn/empty.tsx +103 -0
  107. package/src/shadcn/field.tsx +238 -0
  108. package/src/shadcn/form.tsx +173 -0
  109. package/src/shadcn/hover-card.tsx +50 -0
  110. package/src/shadcn/index.ts +53 -0
  111. package/src/shadcn/input-group.tsx +156 -0
  112. package/src/shadcn/input-otp.tsx +84 -0
  113. package/src/shadcn/input.tsx +19 -0
  114. package/src/shadcn/item.tsx +201 -0
  115. package/src/shadcn/kbd.tsx +26 -0
  116. package/src/shadcn/label.tsx +19 -0
  117. package/src/shadcn/marker.tsx +70 -0
  118. package/src/shadcn/menubar.tsx +278 -0
  119. package/src/shadcn/message-scroller.tsx +131 -0
  120. package/src/shadcn/message.tsx +91 -0
  121. package/src/shadcn/native-select.tsx +60 -0
  122. package/src/shadcn/navigation-menu.tsx +167 -0
  123. package/src/shadcn/pagination.tsx +130 -0
  124. package/src/shadcn/popover.tsx +87 -0
  125. package/src/shadcn/progress.tsx +82 -0
  126. package/src/shadcn/radio-group.tsx +35 -0
  127. package/src/shadcn/resizable.tsx +49 -0
  128. package/src/shadcn/scroll-area.tsx +52 -0
  129. package/src/shadcn/select.tsx +200 -0
  130. package/src/shadcn/separator.tsx +22 -0
  131. package/src/shadcn/sheet.tsx +136 -0
  132. package/src/shadcn/sidebar.tsx +721 -0
  133. package/src/shadcn/skeleton.tsx +13 -0
  134. package/src/shadcn/slider.tsx +51 -0
  135. package/src/shadcn/sonner.tsx +49 -0
  136. package/src/shadcn/spinner.tsx +10 -0
  137. package/src/shadcn/switch.tsx +29 -0
  138. package/src/shadcn/table.tsx +115 -0
  139. package/src/shadcn/tabs.tsx +79 -0
  140. package/src/shadcn/textarea.tsx +17 -0
  141. package/src/shadcn/toggle-group.tsx +89 -0
  142. package/src/shadcn/toggle.tsx +44 -0
  143. package/src/shadcn/tooltip.tsx +63 -0
  144. package/src/theme/config.ts +48 -0
  145. package/src/theme/css/base.css +92 -0
  146. package/src/theme/css/bitboy.css +75 -0
  147. package/src/theme/css/dark.css +47 -0
  148. package/src/theme/css/designer.css +122 -0
  149. package/src/theme/css/fonts.css +33 -0
  150. package/src/theme/css/globals.css +14 -0
  151. package/src/theme/css/hackintosh.css +57 -0
  152. package/src/theme/css/llama.css +103 -0
  153. package/src/theme/css/safelist.css +39 -0
  154. package/src/theme/css/shadcn.css +77 -0
  155. package/src/theme/css/theme.css +216 -0
  156. package/src/theme/embed-theme.ts +146 -0
  157. package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
  158. package/src/theme/fonts/Hack-Regular.woff2 +0 -0
  159. package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
  160. package/src/theme/fonts/LICENSE-Hack.txt +45 -0
  161. package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
  162. package/src/theme/fonts/Mona-Sans.woff2 +0 -0
  163. package/src/theme/index.ts +15 -0
  164. package/src/utils/cn.ts +3 -0
  165. package/src/utils/index.ts +2 -0
@@ -0,0 +1,320 @@
1
+ /**
2
+ * JSON Schema types and utilities for parsing and resolving references
3
+ */
4
+
5
+ export type SchemaTypeName =
6
+ | "string"
7
+ | "number"
8
+ | "integer"
9
+ | "boolean"
10
+ | "object"
11
+ | "array"
12
+ | "any"
13
+ | "null";
14
+
15
+ export type SchemaType =
16
+ | string
17
+ | number
18
+ | boolean
19
+ | Record<string, unknown>
20
+ | unknown[]
21
+ | null
22
+ | undefined;
23
+
24
+ export interface SchemaAlternative extends Omit<Schema, 'type'> {
25
+ type: SchemaTypeName;
26
+ }
27
+
28
+ export interface Schema {
29
+ $id?: string;
30
+ $ref?: string;
31
+ $schema?: string;
32
+ $comment?: string;
33
+ $defs?: Record<string, Schema>;
34
+
35
+ type?: SchemaTypeName | SchemaTypeName[];
36
+ enum?: SchemaType[];
37
+ const?: SchemaType;
38
+
39
+ // String validations
40
+ minLength?: number;
41
+ maxLength?: number;
42
+ pattern?: string;
43
+
44
+ // Number validations
45
+ multipleOf?: number;
46
+ maximum?: number;
47
+ exclusiveMaximum?: number;
48
+ minimum?: number;
49
+ exclusiveMinimum?: number;
50
+
51
+ // Object validations
52
+ maxProperties?: number;
53
+ minProperties?: number;
54
+ required?: string[];
55
+ properties?: Record<string, Schema>;
56
+ patternProperties?: Record<string, Schema>;
57
+ additionalProperties?: Schema | boolean;
58
+
59
+ // Array validations
60
+ items?: Schema | Schema[];
61
+ additionalItems?: Schema;
62
+ maxItems?: number;
63
+ minItems?: number;
64
+ uniqueItems?: boolean;
65
+ contains?: Schema;
66
+
67
+ // Conditionals
68
+ if?: Schema;
69
+ then?: Schema;
70
+ else?: Schema;
71
+
72
+ // Combiners
73
+ allOf?: SchemaAlternative[];
74
+ anyOf?: SchemaAlternative[];
75
+ oneOf?: SchemaAlternative[];
76
+ not?: SchemaAlternative;
77
+
78
+ format?: string;
79
+ contentMediaType?: string;
80
+ contentEncoding?: string;
81
+
82
+ // Metadata
83
+ title?: string;
84
+ description?: string;
85
+ default?: SchemaType;
86
+ readOnly?: boolean;
87
+ writeOnly?: boolean;
88
+ examples?: SchemaType | SchemaType[];
89
+ definitions?: Record<string, Schema>;
90
+
91
+ // Set by the form layer when an `anyOf [T, null]` is collapsed to T, so a
92
+ // renderer can offer "unset" and default builders can leave it absent.
93
+ nullable?: boolean;
94
+
95
+ // Vendor extensions
96
+ 'x-promoted'?: boolean;
97
+ }
98
+
99
+ /**
100
+ * A field parsed from a schema with resolved references.
101
+ * `displayType` holds the human-readable type string from `resolveDisplayType`
102
+ * (e.g. "object | string", "file | object") while `type` stays compatible
103
+ * with Schema for structural operations.
104
+ */
105
+ export interface SchemaField extends Schema {
106
+ displayType: string;
107
+ key: string;
108
+ resolved?: Schema;
109
+ isRequired?: boolean;
110
+ promoted?: boolean;
111
+ }
112
+
113
+ // Type guards
114
+ function hasRef(value: unknown): value is Schema & { $ref: string } {
115
+ return typeof value === 'object' && value !== null && '$ref' in value && typeof (value as Schema).$ref === 'string';
116
+ }
117
+
118
+ /**
119
+ * Deeply resolves all $ref references in a schema
120
+ */
121
+ export function resolveSchemaReferences(schema: Schema, rootSchema: Schema): Schema {
122
+ if (!schema) return schema;
123
+
124
+ // Handle direct reference
125
+ if (hasRef(schema) && rootSchema.$defs) {
126
+ const refName = schema.$ref.replace('#/$defs/', '');
127
+ const resolvedSchema = rootSchema.$defs[refName];
128
+ if (resolvedSchema) {
129
+ const fullyResolved = resolveSchemaReferences(resolvedSchema, rootSchema);
130
+ return {
131
+ ...fullyResolved,
132
+ ...schema,
133
+ $ref: schema.$ref
134
+ };
135
+ }
136
+ }
137
+
138
+ // Handle nested references in properties
139
+ if (schema.properties) {
140
+ const resolvedProperties: Record<string, Schema> = {};
141
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
142
+ resolvedProperties[key] = resolveSchemaReferences(propSchema, rootSchema);
143
+ }
144
+ schema = { ...schema, properties: resolvedProperties };
145
+ }
146
+
147
+ // Handle references in array items
148
+ if (schema.items) {
149
+ if (Array.isArray(schema.items)) {
150
+ schema = {
151
+ ...schema,
152
+ items: schema.items.map(itemSchema => resolveSchemaReferences(itemSchema, rootSchema))
153
+ };
154
+ } else {
155
+ schema = {
156
+ ...schema,
157
+ items: resolveSchemaReferences(schema.items, rootSchema)
158
+ };
159
+ }
160
+ }
161
+
162
+ // Handle references in anyOf, oneOf, allOf
163
+ const combiners = ['anyOf', 'oneOf', 'allOf'] as const;
164
+ for (const key of combiners) {
165
+ const schemas = schema[key];
166
+ if (schemas) {
167
+ schema = {
168
+ ...schema,
169
+ [key]: schemas.map((subSchema: Schema) => resolveSchemaReferences(subSchema, rootSchema))
170
+ };
171
+ }
172
+ }
173
+
174
+ return schema;
175
+ }
176
+
177
+ /**
178
+ * Process anyOf to handle nullable fields
179
+ */
180
+ function processAnyOf(schema: Schema): { processedSchema: Schema; isNullable: boolean } {
181
+ if (!schema.anyOf || schema.anyOf.length > 2) {
182
+ return { processedSchema: schema, isNullable: false };
183
+ }
184
+
185
+ const nullSchema = schema.anyOf.find(type => type.type === 'null');
186
+ const nonNullSchema = schema.anyOf.find(type => type.type !== 'null');
187
+
188
+ if (!nullSchema) {
189
+ return { processedSchema: schema, isNullable: false };
190
+ }
191
+
192
+ if (!nonNullSchema) {
193
+ return {
194
+ processedSchema: { ...schema, type: 'null', anyOf: undefined },
195
+ isNullable: true
196
+ };
197
+ }
198
+
199
+ return {
200
+ processedSchema: {
201
+ ...schema,
202
+ ...nonNullSchema,
203
+ description: schema.description || nonNullSchema.description,
204
+ default: schema.default || nonNullSchema.default,
205
+ anyOf: undefined
206
+ },
207
+ isNullable: true
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Synthesize a display type from a oneOf schema.
213
+ * Special-cases the File schema ($id: /schemas/File) to show "file" instead of "object".
214
+ */
215
+ function resolveOneOfType(schema: Schema): string | undefined {
216
+ if (!schema.oneOf || schema.oneOf.length === 0) return undefined;
217
+
218
+ const isFileSchema = schema.$id === '/schemas/File';
219
+ const types: string[] = [];
220
+ for (const alt of schema.oneOf) {
221
+ if (isFileSchema && alt.type === 'object') {
222
+ types.push('file');
223
+ } else if (typeof alt.type === 'string') {
224
+ types.push(alt.type);
225
+ } else {
226
+ types.push('any');
227
+ }
228
+ }
229
+
230
+ if (types.length === 0) return undefined;
231
+ if (types.length === 1) return types[0];
232
+ return types.join(' | ');
233
+ }
234
+
235
+ /**
236
+ * Normalize a schema type to a single SchemaTypeName.
237
+ * JSON Schema allows type arrays (e.g. ["string", "null"]) — pick the
238
+ * first non-null entry so field logic can use simple === comparisons.
239
+ */
240
+ export function normalizeType(schema: Schema): SchemaTypeName | undefined {
241
+ if (!schema.type) return undefined;
242
+ if (!Array.isArray(schema.type)) return schema.type;
243
+ return schema.type.find(t => t !== 'null') ?? schema.type[0];
244
+ }
245
+
246
+ /**
247
+ * Determine the display type for a resolved schema
248
+ */
249
+ export function resolveDisplayType(schema: Schema): string {
250
+ if (schema.format) {
251
+ return schema.format;
252
+ }
253
+ if (schema.type) {
254
+ return Array.isArray(schema.type) ? schema.type.join(' | ') : schema.type;
255
+ }
256
+ const oneOfType = resolveOneOfType(schema);
257
+ if (oneOfType) return oneOfType;
258
+ if (schema.anyOf?.length) {
259
+ return schema.anyOf.map(alt => alt.type ?? 'any').join(' | ');
260
+ }
261
+ return 'any';
262
+ }
263
+
264
+ /**
265
+ * Create a field from a schema property
266
+ */
267
+ function createFieldFromSchema(
268
+ schema: Schema,
269
+ key: string,
270
+ rootSchema: Schema,
271
+ required?: string[]
272
+ ): SchemaField {
273
+ const resolvedSchema = resolveSchemaReferences(schema, rootSchema);
274
+
275
+ const promoted = schema['x-promoted'] === true || resolvedSchema['x-promoted'] === true;
276
+
277
+ const type = normalizeType(resolvedSchema);
278
+
279
+ if (hasRef(schema)) {
280
+ return {
281
+ ...schema,
282
+ ...resolvedSchema,
283
+ key,
284
+ type,
285
+ displayType: resolveDisplayType(resolvedSchema),
286
+ title: resolvedSchema.title || key,
287
+ isRequired: required?.includes(key),
288
+ ...(promoted && { promoted }),
289
+ resolved: resolvedSchema,
290
+ $ref: schema.$ref
291
+ };
292
+ }
293
+
294
+ return {
295
+ ...resolvedSchema,
296
+ key,
297
+ type,
298
+ displayType: resolveDisplayType(resolvedSchema),
299
+ title: schema.title || key,
300
+ isRequired: required?.includes(key),
301
+ ...(promoted && { promoted }),
302
+ };
303
+ }
304
+
305
+ /**
306
+ * Parse a JSON Schema into a list of fields with resolved references
307
+ */
308
+ export function getFieldsFromSchema(schema: Schema): { fields: SchemaField[] } {
309
+ if (!schema || !schema.properties) {
310
+ return { fields: [] };
311
+ }
312
+
313
+ const fields = Object.entries(schema.properties).map(([key, fieldData]): SchemaField => {
314
+ const resolvedFieldData = resolveSchemaReferences(fieldData, schema);
315
+ const { processedSchema } = processAnyOf(resolvedFieldData);
316
+ return createFieldFromSchema(processedSchema, key, schema, schema.required);
317
+ });
318
+
319
+ return { fields };
320
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The browser's localStorage and sessionStorage, for a page that may not be
3
+ * allowed either. Framed by another site (the app inside ChatGPT, Claude or
4
+ * Codex) with third-party storage blocked, reading `window.localStorage`
5
+ * itself throws a SecurityError; elsewhere a write can be refused (a full
6
+ * quota, some private modes). Neither reaches the caller: what the browser
7
+ * will not keep is kept in memory until the page goes away, so a value reads
8
+ * back the same and simply does not outlive the page.
9
+ *
10
+ * Every use of web storage goes through `localStore` and `sessionStore`; lint
11
+ * rejects the browser globals outside this module. Both are also what
12
+ * zustand's `createJSONStorage` expects (a `StateStorage`).
13
+ */
14
+
15
+ /** The part of the DOM `Storage` interface the stores provide. */
16
+ export interface WebStorage {
17
+ /** How many keys are stored. */
18
+ readonly length: number
19
+ /** The key at `index`, or null past the end. */
20
+ key(index: number): string | null
21
+ getItem(key: string): string | null
22
+ setItem(key: string, value: string): void
23
+ removeItem(key: string): void
24
+ }
25
+
26
+ /**
27
+ * A store over the Storage `resolve` returns. `resolve` runs once, on first
28
+ * use rather than when the module loads, and may throw or return nothing:
29
+ * the store is then memory only.
30
+ */
31
+ export function createSafeStorage(resolve: () => Storage | null | undefined): WebStorage {
32
+ let browser: Storage | null | undefined
33
+ // What the browser does not hold: everything when there is no Storage, and
34
+ // otherwise each value it refused to write. A key here shadows the browser's.
35
+ const kept = new Map<string, string>()
36
+
37
+ const storage = (): Storage | null => {
38
+ if (browser === undefined) {
39
+ try {
40
+ browser = usable(resolve())
41
+ } catch {
42
+ browser = null
43
+ }
44
+ }
45
+ return browser
46
+ }
47
+
48
+ const keys = (): string[] => {
49
+ const browser = storage()
50
+ const all = new Set<string>()
51
+ for (let i = 0; browser && i < browser.length; i++) {
52
+ const key = browser.key(i)
53
+ if (key !== null) all.add(key)
54
+ }
55
+ for (const key of kept.keys()) all.add(key)
56
+ return [...all]
57
+ }
58
+
59
+ return {
60
+ get length() {
61
+ return kept.size === 0 ? (storage()?.length ?? 0) : keys().length
62
+ },
63
+ key(index) {
64
+ return (kept.size === 0 ? storage()?.key(index) : keys()[index]) ?? null
65
+ },
66
+ getItem(key) {
67
+ return kept.get(key) ?? storage()?.getItem(key) ?? null
68
+ },
69
+ setItem(key, value) {
70
+ const browser = storage()
71
+ if (browser) {
72
+ try {
73
+ browser.setItem(key, value)
74
+ kept.delete(key)
75
+ return
76
+ } catch {
77
+ // refused this write: the value is kept below instead
78
+ }
79
+ }
80
+ kept.set(key, String(value))
81
+ },
82
+ removeItem(key) {
83
+ kept.delete(key)
84
+ storage()?.removeItem(key)
85
+ },
86
+ }
87
+ }
88
+
89
+ // A Storage that answers the calls the store makes, else null. Node 25 gives
90
+ // every process a `localStorage` global without its methods unless it is
91
+ // started with --localstorage-file, and a DOM shim can hand that one back.
92
+ function usable(candidate: Storage | null | undefined): Storage | null {
93
+ if (!candidate) return null
94
+ const s = candidate as Partial<Storage>
95
+ return typeof s.getItem === 'function' && typeof s.setItem === 'function' && typeof s.removeItem === 'function' && typeof s.key === 'function'
96
+ ? candidate
97
+ : null
98
+ }
99
+
100
+ // Without a window (a server render, a test) there is no web storage to ask
101
+ // for. Node's own `localStorage` global is not it, and is left alone.
102
+ const browserWindow = (): Window | undefined => (typeof window === 'undefined' ? undefined : window)
103
+
104
+ /** localStorage: kept across visits when the browser allows it. */
105
+ export const localStore = createSafeStorage(() => browserWindow()?.localStorage)
106
+
107
+ /** sessionStorage: kept for the tab's lifetime when the browser allows it. */
108
+ export const sessionStore = createSafeStorage(() => browserWindow()?.sessionStorage)