@passui/react 0.1.45 → 0.1.46
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 +57 -46
- package/dist/types/src/components/accordion/accordion.d.ts +1 -1
- package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
- package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
- package/dist/types/src/components/popover/index.d.ts +41 -0
- package/dist/types/src/components/popover/popover-content.d.ts +32 -0
- package/dist/types/src/components/popover/popover-root.d.ts +22 -0
- package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
- package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
- package/dist/types/src/components/table/table.d.ts +1 -1
- package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/machines/popover.machine.d.ts +30 -0
- package/package.json +307 -307
- package/src/appearance/index.ts +2 -2
- package/src/appearance/platform.tsx +76 -60
- package/src/appearance/theme.tsx +409 -377
- package/src/components/accordion/accordion.tsx +58 -49
- package/src/components/ambient/ambient.tsx +25 -25
- package/src/components/ambient/index.ts +1 -1
- package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
- package/src/components/breadcrumb/breadcrumb.tsx +63 -57
- package/src/components/button/button-styles.ts +39 -32
- package/src/components/button/button.tsx +91 -84
- package/src/components/button/icon-button.tsx +27 -25
- package/src/components/button/pending-spinner.tsx +8 -8
- package/src/components/button/use-pending-indicator.ts +11 -11
- package/src/components/chip/chip.tsx +35 -30
- package/src/components/code-block/code-block.tsx +40 -39
- package/src/components/combo-box/combo-box.tsx +5 -5
- package/src/components/command/command.tsx +124 -94
- package/src/components/compact-date-picker/calendar-month.ts +75 -53
- package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
- package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
- package/src/components/compact-time-picker/time-value.ts +32 -29
- package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
- package/src/components/content-view/content-view.tsx +168 -159
- package/src/components/content-view/index.ts +1 -1
- package/src/components/context-menu/conceal-menu-source.ts +25 -25
- package/src/components/context-menu/context-menu-host.tsx +439 -402
- package/src/components/context-menu/context-menu-store.ts +125 -123
- package/src/components/context-menu/menu-navigation.ts +25 -18
- package/src/components/context-menu/menu-position.ts +33 -29
- package/src/components/context-menu/menu-reveal.ts +32 -32
- package/src/components/context-menu/use-context-menu.ts +63 -56
- package/src/components/conversation/conversation.tsx +265 -263
- package/src/components/conversation/index.ts +1 -1
- package/src/components/date-picker/date-picker.tsx +95 -94
- package/src/components/date-picker/date-value.ts +20 -10
- package/src/components/dialog/dialog-host.tsx +105 -103
- package/src/components/dialog/dialog-store.ts +37 -36
- package/src/components/dialog/dialog.tsx +192 -179
- package/src/components/dialog/index.ts +1 -1
- package/src/components/disclosure-group/disclosure-group.tsx +93 -86
- package/src/components/divider/divider.tsx +24 -22
- package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
- package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
- package/src/components/labeled-content/labeled-content.tsx +68 -68
- package/src/components/locale-switcher/index.ts +1 -1
- package/src/components/locale-switcher/locale-switcher.tsx +279 -251
- package/src/components/menu/menu.tsx +75 -58
- package/src/components/morph/index.ts +2 -2
- package/src/components/morph/morph.tsx +227 -201
- package/src/components/morph/use-morph-disclosure.ts +19 -19
- package/src/components/one-time-code/one-time-code.tsx +216 -200
- package/src/components/picker/picker-trigger.ts +16 -13
- package/src/components/picker/picker.tsx +371 -345
- package/src/components/popover/anchored-surface.tsx +253 -229
- package/src/components/popover/index.ts +50 -0
- package/src/components/popover/popover-content.tsx +334 -0
- package/src/components/popover/popover-fit.ts +58 -53
- package/src/components/popover/popover-root.tsx +97 -0
- package/src/components/popover/popover-trigger.tsx +71 -0
- package/src/components/profile-image/profile-image.tsx +80 -64
- package/src/components/progress-view/progress-view.tsx +61 -59
- package/src/components/progress-view/use-liquid-progress.ts +38 -38
- package/src/components/radio-group/radio-group.tsx +71 -68
- package/src/components/search-field/expanding-search-field.tsx +191 -177
- package/src/components/search-field/search-field.tsx +59 -50
- package/src/components/search-list/search-list.tsx +246 -236
- package/src/components/section/section.tsx +58 -58
- package/src/components/secure-field/secure-field.tsx +3 -3
- package/src/components/segmented-control/segmented-control.tsx +136 -136
- package/src/components/sheet/modal-environment.ts +71 -64
- package/src/components/sheet/sheet-portal.tsx +58 -53
- package/src/components/sheet/sheet.tsx +146 -141
- package/src/components/sheet/use-sheet-focus.ts +97 -82
- package/src/components/sidebar/sidebar.tsx +78 -78
- package/src/components/skeleton/skeleton.tsx +30 -21
- package/src/components/slider/slider.tsx +113 -104
- package/src/components/slosh-slider/liquid-step.ts +32 -31
- package/src/components/slosh-slider/slosh-slider.tsx +265 -228
- package/src/components/stat-tile/index.ts +1 -1
- package/src/components/stat-tile/stat-tile.tsx +79 -68
- package/src/components/status-indicator/status-indicator.tsx +40 -37
- package/src/components/stepper/rolling-digit.tsx +82 -68
- package/src/components/stepper/stepper.tsx +122 -113
- package/src/components/swatch-picker/swatch-picker.tsx +120 -120
- package/src/components/tab-bar/tab-bar.tsx +149 -148
- package/src/components/tab-view/tab-view.tsx +140 -137
- package/src/components/table/table.tsx +117 -98
- package/src/components/text-editor/text-editor.tsx +34 -29
- package/src/components/text-field/text-field.tsx +182 -180
- package/src/components/toast/toast-store.ts +127 -115
- package/src/components/toast/toaster.tsx +361 -347
- package/src/components/toggle/toggle.tsx +297 -282
- package/src/components/tooltip/tooltip-host.tsx +157 -150
- package/src/components/view-controller/index.ts +1 -1
- package/src/components/view-controller/view-controller.tsx +64 -53
- package/src/components/wake-slider/wake-slider.tsx +220 -205
- package/src/hooks/scroll/minimize-decision.ts +11 -7
- package/src/hooks/use-anchored-disclosure.ts +19 -19
- package/src/hooks/use-anchored-placement.ts +29 -25
- package/src/hooks/use-dismiss.ts +33 -29
- package/src/hooks/use-floating-dismiss.ts +24 -22
- package/src/hooks/use-media-query.ts +67 -60
- package/src/hooks/use-minimize-on-scroll.ts +69 -60
- package/src/hooks/use-visual-viewport.ts +63 -63
- package/src/hooks/viewport/types.ts +16 -16
- package/src/hooks/viewport/viewport-metrics.ts +16 -10
- package/src/icons/icons.tsx +440 -328
- package/src/index.ts +91 -91
- package/src/lib/anchored-placement.ts +70 -65
- package/src/lib/class-names.ts +14 -13
- package/src/lib/clipboard.ts +8 -7
- package/src/lib/date.ts +61 -60
- package/src/lib/floating-layers.ts +39 -24
- package/src/lib/haptics.ts +10 -10
- package/src/lib/language.ts +9 -9
- package/src/lib/range-value.ts +27 -21
- package/src/lib/screen-edges.ts +33 -30
- package/src/lib/shapes.ts +38 -34
- package/src/machines/popover.machine.ts +51 -0
- package/src/motion/transitions.ts +46 -43
- package/src/styles/code-block.css +69 -43
- package/src/styles/motion-controls.css +393 -191
- package/src/tokens.ts +1 -1
- package/dist/types/src/components/popover/popover.d.ts +0 -21
- package/src/components/popover/popover.tsx +0 -334
package/src/appearance/theme.tsx
CHANGED
|
@@ -1,65 +1,78 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
memo,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
11
|
+
import type {
|
|
12
|
+
Dispatch,
|
|
13
|
+
PropsWithChildren,
|
|
14
|
+
ReactNode,
|
|
15
|
+
SetStateAction,
|
|
16
|
+
} from "react";
|
|
4
17
|
|
|
5
18
|
type ValueObject = {
|
|
6
|
-
|
|
7
|
-
}
|
|
19
|
+
[themeName: string]: string;
|
|
20
|
+
};
|
|
8
21
|
|
|
9
22
|
/**
|
|
10
23
|
* Props for the useTheme hook
|
|
11
24
|
* @author la niina
|
|
12
25
|
*/
|
|
13
26
|
export type UseThemeProps = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type SystemTheme = "dark" | "light"
|
|
27
|
-
|
|
28
|
-
export type Attribute = `data-${string}` | "class"
|
|
27
|
+
/** List of all available theme names */
|
|
28
|
+
themes: Array<string>;
|
|
29
|
+
/** Forced theme name for the current page */
|
|
30
|
+
forcedTheme?: string | undefined;
|
|
31
|
+
/** Update the theme */
|
|
32
|
+
setTheme: Dispatch<SetStateAction<string>>;
|
|
33
|
+
/** Active theme name */
|
|
34
|
+
theme?: string | undefined;
|
|
35
|
+
/** If enableSystem is true, returns the System theme preference ("dark" or "light"), regardless what the active theme is */
|
|
36
|
+
systemTheme?: SystemTheme | undefined;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type SystemTheme = "dark" | "light";
|
|
40
|
+
|
|
41
|
+
export type Attribute = `data-${string}` | "class";
|
|
29
42
|
|
|
30
43
|
export interface ThemeProviderProps extends PropsWithChildren {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
/** List of all available theme names */
|
|
45
|
+
themes?: Array<string> | undefined;
|
|
46
|
+
/** Forced theme name for the current page */
|
|
47
|
+
forcedTheme?: string | undefined;
|
|
48
|
+
/** Whether to switch between dark and light themes based on prefers-color-scheme */
|
|
49
|
+
enableSystem?: boolean | undefined;
|
|
50
|
+
/** Disable all CSS transitions when switching themes */
|
|
51
|
+
disableTransitionOnChange?: boolean | undefined;
|
|
52
|
+
/** Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons */
|
|
53
|
+
enableColorScheme?: boolean | undefined;
|
|
54
|
+
/** Key used to store theme setting in localStorage */
|
|
55
|
+
storageKey?: string | undefined;
|
|
56
|
+
/** Default theme name (for v0.0.12 and lower the default was light). If `enableSystem` is false, the default theme is light */
|
|
57
|
+
defaultTheme?: string | undefined;
|
|
58
|
+
/** HTML attribute modified based on the active theme. Accepts `class`, `data-*` (meaning any data attribute, `data-mode`, `data-color`, etc.), or an array which could include both */
|
|
59
|
+
attribute?: Attribute | Array<Attribute> | undefined;
|
|
60
|
+
/** Mapping of theme name to HTML attribute value. Object where key is the theme name and value is the attribute value */
|
|
61
|
+
value?: ValueObject | undefined;
|
|
62
|
+
/** Nonce string to pass to the inline script for CSP headers */
|
|
63
|
+
nonce?: string | undefined;
|
|
51
64
|
}
|
|
52
65
|
|
|
53
|
-
const colorSchemes = ["light", "dark"]
|
|
54
|
-
const MEDIA = "(prefers-color-scheme: dark)"
|
|
55
|
-
const isServer = typeof window === "undefined"
|
|
56
|
-
const ThemeContext = createContext<UseThemeProps | undefined>(undefined)
|
|
66
|
+
const colorSchemes = ["light", "dark"];
|
|
67
|
+
const MEDIA = "(prefers-color-scheme: dark)";
|
|
68
|
+
const isServer = typeof window === "undefined";
|
|
69
|
+
const ThemeContext = createContext<UseThemeProps | undefined>(undefined);
|
|
57
70
|
const defaultContext: UseThemeProps = {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
71
|
+
setTheme: () => {
|
|
72
|
+
/* no-op */
|
|
73
|
+
},
|
|
74
|
+
themes: [],
|
|
75
|
+
};
|
|
63
76
|
|
|
64
77
|
/**
|
|
65
78
|
* Hook to access the theme context
|
|
@@ -67,7 +80,8 @@ const defaultContext: UseThemeProps = {
|
|
|
67
80
|
* @example
|
|
68
81
|
* const { theme, setTheme } = useTheme()
|
|
69
82
|
*/
|
|
70
|
-
export const useTheme = (): UseThemeProps =>
|
|
83
|
+
export const useTheme = (): UseThemeProps =>
|
|
84
|
+
useContext(ThemeContext) ?? defaultContext;
|
|
71
85
|
|
|
72
86
|
/**
|
|
73
87
|
* Theme provider component is used to wrap your app &
|
|
@@ -87,272 +101,286 @@ export const useTheme = (): UseThemeProps => useContext(ThemeContext) ?? default
|
|
|
87
101
|
* </ThemeProvider>
|
|
88
102
|
*/
|
|
89
103
|
export const ThemeProvider = (props: ThemeProviderProps): ReactNode => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const defaultThemes = ["light", "dark"]
|
|
104
|
+
const context = useContext(ThemeContext);
|
|
105
|
+
// Ignore nested context providers, just passthrough children
|
|
106
|
+
if (context) {
|
|
107
|
+
return props.children;
|
|
108
|
+
}
|
|
109
|
+
return <Theme {...props} />;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const defaultThemes = ["light", "dark"];
|
|
99
113
|
const Theme = ({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
forcedTheme,
|
|
115
|
+
disableTransitionOnChange = false,
|
|
116
|
+
enableSystem = true,
|
|
117
|
+
enableColorScheme = true,
|
|
118
|
+
storageKey = "theme",
|
|
119
|
+
themes = defaultThemes,
|
|
120
|
+
defaultTheme = enableSystem ? "system" : "light",
|
|
121
|
+
attribute = "data-theme",
|
|
122
|
+
value,
|
|
123
|
+
children,
|
|
124
|
+
nonce,
|
|
111
125
|
}: ThemeProviderProps) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
126
|
+
const [theme, setThemeState] = useState(() =>
|
|
127
|
+
getTheme(storageKey, defaultTheme),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const applyClassAttribute = useCallback(
|
|
131
|
+
(name: string | undefined, attrValues: Array<string>) => {
|
|
132
|
+
const d = document.documentElement;
|
|
133
|
+
d.classList.remove(...attrValues);
|
|
134
|
+
if (name) {
|
|
135
|
+
d.classList.add(name);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
[],
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const applyDataAttribute = useCallback(
|
|
142
|
+
(attr: string, name: string | undefined) => {
|
|
143
|
+
const d = document.documentElement;
|
|
144
|
+
if (name) {
|
|
145
|
+
d.setAttribute(attr, name);
|
|
146
|
+
} else {
|
|
147
|
+
d.removeAttribute(attr);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
[],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const applyAttributesToDOM = useCallback(
|
|
154
|
+
(resolved: string) => {
|
|
155
|
+
const attributeList = Array.isArray(attribute) ? attribute : [attribute];
|
|
156
|
+
const attrValues = value ? Object.values(value) : themes;
|
|
157
|
+
const name = value ? value[resolved] : resolved;
|
|
158
|
+
|
|
159
|
+
for (const attr of attributeList) {
|
|
160
|
+
if (attr === "class") {
|
|
161
|
+
applyClassAttribute(name, attrValues);
|
|
162
|
+
} else if (attr.startsWith("data-")) {
|
|
163
|
+
applyDataAttribute(attr, name);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
[attribute, themes, value, applyClassAttribute, applyDataAttribute],
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const applyColorScheme = useCallback(
|
|
171
|
+
(resolved: string) => {
|
|
172
|
+
if (!enableColorScheme) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const fallback = colorSchemes.includes(defaultTheme)
|
|
176
|
+
? defaultTheme
|
|
177
|
+
: null;
|
|
178
|
+
const colorScheme = colorSchemes.includes(resolved) ? resolved : fallback;
|
|
179
|
+
document.documentElement.style.colorScheme = colorScheme || "";
|
|
180
|
+
},
|
|
181
|
+
[enableColorScheme, defaultTheme],
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// apply selected theme function (light, dark, system)
|
|
185
|
+
const applyTheme = useCallback(
|
|
186
|
+
(nextTheme: string | undefined) => {
|
|
187
|
+
if (!nextTheme) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const resolved =
|
|
191
|
+
nextTheme === "system" && enableSystem ? getSystemTheme() : nextTheme;
|
|
192
|
+
const enable = disableTransitionOnChange ? disableAnimation() : null;
|
|
193
|
+
applyAttributesToDOM(resolved);
|
|
194
|
+
applyColorScheme(resolved);
|
|
195
|
+
enable?.();
|
|
196
|
+
},
|
|
197
|
+
[
|
|
198
|
+
enableSystem,
|
|
199
|
+
disableTransitionOnChange,
|
|
200
|
+
applyAttributesToDOM,
|
|
201
|
+
applyColorScheme,
|
|
202
|
+
],
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Set theme state and save to local storage
|
|
206
|
+
const setTheme = useCallback(
|
|
207
|
+
(newValue: SetStateAction<string>) => {
|
|
208
|
+
const newTheme =
|
|
209
|
+
typeof newValue === "function" ? newValue(theme ?? "") : newValue;
|
|
210
|
+
setThemeState(newTheme);
|
|
211
|
+
|
|
212
|
+
// Save to storage
|
|
213
|
+
try {
|
|
214
|
+
localStorage.setItem(storageKey, newTheme);
|
|
215
|
+
} catch {
|
|
216
|
+
// localStorage might not be available
|
|
217
|
+
console.error("Failed to save theme to localStorage");
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
[theme, storageKey],
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const handleMediaQuery = useCallback(
|
|
224
|
+
(_event: MediaQueryListEvent | MediaQueryList) => {
|
|
225
|
+
if (theme === "system" && enableSystem && !forcedTheme) {
|
|
226
|
+
applyTheme("system");
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
[applyTheme, enableSystem, forcedTheme, theme],
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Always listen to System preference
|
|
233
|
+
React.useEffect(() => {
|
|
234
|
+
if (isServer) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const media = window.matchMedia(MEDIA);
|
|
239
|
+
// Intentionally use deprecated listener methods to support iOS 13 and older browsers
|
|
240
|
+
media.addEventListener("change", handleMediaQuery);
|
|
241
|
+
handleMediaQuery(media);
|
|
242
|
+
return () => media.removeEventListener("change", handleMediaQuery);
|
|
243
|
+
}, [handleMediaQuery]);
|
|
244
|
+
|
|
245
|
+
// localStorage event handling, allow to sync theme changes between tabs
|
|
246
|
+
React.useEffect(() => {
|
|
247
|
+
if (isServer) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const handleStorage = (e: StorageEvent) => {
|
|
252
|
+
if (e.key !== storageKey) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// If default theme set, use it if localstorage === null (happens on local storage manual deletion)
|
|
256
|
+
const newTheme = e.newValue || defaultTheme;
|
|
257
|
+
setTheme(newTheme);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
window.addEventListener("storage", handleStorage);
|
|
261
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
262
|
+
}, [defaultTheme, setTheme, storageKey]);
|
|
263
|
+
|
|
264
|
+
// Whenever theme or forcedTheme changes, apply it
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
applyTheme(forcedTheme ?? theme);
|
|
267
|
+
}, [applyTheme, forcedTheme, theme]);
|
|
268
|
+
|
|
269
|
+
const providerValue = useMemo(
|
|
270
|
+
() => ({
|
|
271
|
+
theme,
|
|
272
|
+
setTheme,
|
|
273
|
+
forcedTheme,
|
|
274
|
+
themes: enableSystem ? [...themes, "system"] : themes,
|
|
275
|
+
systemTheme: enableSystem
|
|
276
|
+
? (getSystemTheme() as "dark" | "light" | undefined)
|
|
277
|
+
: undefined,
|
|
278
|
+
}),
|
|
279
|
+
[theme, forcedTheme, enableSystem, themes, setTheme],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<ThemeContext.Provider value={providerValue}>
|
|
284
|
+
<ThemeScript
|
|
285
|
+
{...{
|
|
286
|
+
forcedTheme,
|
|
287
|
+
storageKey,
|
|
288
|
+
attribute,
|
|
289
|
+
enableSystem,
|
|
290
|
+
enableColorScheme,
|
|
291
|
+
defaultTheme,
|
|
292
|
+
value,
|
|
293
|
+
themes,
|
|
294
|
+
nonce,
|
|
295
|
+
}}
|
|
296
|
+
/>
|
|
297
|
+
{children}
|
|
298
|
+
</ThemeContext.Provider>
|
|
299
|
+
);
|
|
300
|
+
};
|
|
273
301
|
|
|
274
302
|
const ThemeScript = memo(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
ThemeScript.displayName = "ThemeScript"
|
|
303
|
+
({
|
|
304
|
+
forcedTheme,
|
|
305
|
+
storageKey,
|
|
306
|
+
attribute,
|
|
307
|
+
enableSystem,
|
|
308
|
+
enableColorScheme,
|
|
309
|
+
defaultTheme,
|
|
310
|
+
value,
|
|
311
|
+
themes,
|
|
312
|
+
nonce,
|
|
313
|
+
}: Omit<ThemeProviderProps, "children"> & {
|
|
314
|
+
defaultTheme: string;
|
|
315
|
+
}) => {
|
|
316
|
+
const scriptArgs = JSON.stringify([
|
|
317
|
+
attribute,
|
|
318
|
+
storageKey,
|
|
319
|
+
defaultTheme,
|
|
320
|
+
forcedTheme,
|
|
321
|
+
themes,
|
|
322
|
+
value,
|
|
323
|
+
enableSystem,
|
|
324
|
+
enableColorScheme,
|
|
325
|
+
]).slice(1, -1);
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<script
|
|
329
|
+
// Run the theme bootstrap with serialized provider configuration before first paint.
|
|
330
|
+
dangerouslySetInnerHTML={{
|
|
331
|
+
__html: `(${script.toString()})(${scriptArgs})`,
|
|
332
|
+
}}
|
|
333
|
+
nonce={nonce}
|
|
334
|
+
suppressHydrationWarning
|
|
335
|
+
/>
|
|
336
|
+
);
|
|
337
|
+
},
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
ThemeScript.displayName = "ThemeScript";
|
|
313
341
|
|
|
314
342
|
// Helpers
|
|
315
343
|
const getTheme = (key: string, fallback?: string) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
344
|
+
if (isServer) {
|
|
345
|
+
return fallback;
|
|
346
|
+
}
|
|
347
|
+
let theme: string | undefined;
|
|
348
|
+
try {
|
|
349
|
+
theme = localStorage.getItem(key) || undefined;
|
|
350
|
+
} catch {
|
|
351
|
+
// localStorage might not be available
|
|
352
|
+
}
|
|
353
|
+
return theme || fallback;
|
|
354
|
+
};
|
|
327
355
|
|
|
328
356
|
const disableAnimation = () => {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
357
|
+
const css = document.createElement("style");
|
|
358
|
+
css.appendChild(
|
|
359
|
+
document.createTextNode(
|
|
360
|
+
"*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}",
|
|
361
|
+
),
|
|
362
|
+
);
|
|
363
|
+
document.head.appendChild(css);
|
|
364
|
+
|
|
365
|
+
return () => {
|
|
366
|
+
// Force restyle
|
|
367
|
+
window.getComputedStyle(document.body);
|
|
368
|
+
// Wait for next tick before removing
|
|
369
|
+
setTimeout(() => {
|
|
370
|
+
document.head.removeChild(css);
|
|
371
|
+
}, 1);
|
|
372
|
+
};
|
|
373
|
+
};
|
|
346
374
|
|
|
347
375
|
const getSystemTheme = (e?: MediaQueryList | MediaQueryListEvent) => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
376
|
+
if (isServer) {
|
|
377
|
+
return "light";
|
|
378
|
+
}
|
|
379
|
+
const event = e ?? window.matchMedia(MEDIA);
|
|
380
|
+
const isDark = event.matches;
|
|
381
|
+
const systemTheme = isDark ? "dark" : "light";
|
|
382
|
+
return systemTheme;
|
|
383
|
+
};
|
|
356
384
|
|
|
357
385
|
/*
|
|
358
386
|
This file is adapted from next-themes to work with tanstack start.
|
|
@@ -360,76 +388,80 @@ const getSystemTheme = (e?: MediaQueryList | MediaQueryListEvent) => {
|
|
|
360
388
|
*/
|
|
361
389
|
|
|
362
390
|
export const script = (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
391
|
+
attribute: Attribute | Array<Attribute>,
|
|
392
|
+
storageKey: string,
|
|
393
|
+
defaultTheme: string,
|
|
394
|
+
forcedTheme: string | undefined,
|
|
395
|
+
themes: Array<string>,
|
|
396
|
+
value: ValueObject | undefined,
|
|
397
|
+
enableSystem: boolean,
|
|
398
|
+
enableColorScheme: boolean,
|
|
371
399
|
) => {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
400
|
+
const el = document.documentElement;
|
|
401
|
+
const systemThemes = ["light", "dark"];
|
|
402
|
+
const attributes = Array.isArray(attribute) ? attribute : [attribute];
|
|
403
|
+
const attrValues = value ? Object.values(value) : themes;
|
|
404
|
+
|
|
405
|
+
function applyClassAttr(name: string | undefined) {
|
|
406
|
+
el.classList.remove(...attrValues);
|
|
407
|
+
if (name) {
|
|
408
|
+
el.classList.add(name);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function applyDataAttr(attr: string, name: string | undefined) {
|
|
413
|
+
if (name) {
|
|
414
|
+
el.setAttribute(attr, name);
|
|
415
|
+
} else {
|
|
416
|
+
el.removeAttribute(attr);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function updateDOM(theme: string) {
|
|
421
|
+
const name = value ? value[theme] : theme;
|
|
422
|
+
|
|
423
|
+
for (const attr of attributes) {
|
|
424
|
+
if (attr === "class") {
|
|
425
|
+
applyClassAttr(name);
|
|
426
|
+
} else if (attr.startsWith("data-")) {
|
|
427
|
+
applyDataAttr(attr, name);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
setColorScheme(theme);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function setColorScheme(theme: string) {
|
|
435
|
+
if (!enableColorScheme) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const fallback = systemThemes.includes(defaultTheme) ? defaultTheme : null;
|
|
440
|
+
const colorScheme = systemThemes.includes(theme) ? theme : fallback;
|
|
441
|
+
el.style.colorScheme = colorScheme || "";
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function resolveSystemTheme() {
|
|
445
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
446
|
+
? "dark"
|
|
447
|
+
: "light";
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (forcedTheme) {
|
|
451
|
+
const resolvedForcedTheme =
|
|
452
|
+
forcedTheme === "system" && enableSystem
|
|
453
|
+
? resolveSystemTheme()
|
|
454
|
+
: forcedTheme;
|
|
455
|
+
updateDOM(resolvedForcedTheme);
|
|
456
|
+
} else {
|
|
457
|
+
try {
|
|
458
|
+
const themeName = localStorage.getItem(storageKey) || defaultTheme;
|
|
459
|
+
const isSystem = enableSystem && themeName === "system";
|
|
460
|
+
const theme = isSystem ? resolveSystemTheme() : themeName;
|
|
461
|
+
updateDOM(theme);
|
|
462
|
+
} catch {
|
|
463
|
+
// localStorage might not be available
|
|
464
|
+
updateDOM(defaultTheme);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
};
|