@refraction-ui/react 0.3.9 → 0.4.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 +44 -0
- package/dist/index.cjs +1765 -935
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.js +1698 -926
- package/dist/index.js.map +1 -1
- package/dist/styles.css +296 -0
- package/dist/theme.cjs +323 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +1 -0
- package/dist/theme.d.ts +1 -0
- package/dist/theme.js +298 -0
- package/dist/theme.js.map +1 -0
- package/package.json +17 -4
package/dist/styles.css
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @refraction-ui/react — default theme stylesheet
|
|
3
|
+
*
|
|
4
|
+
* Refraction UI components are headless: they read CSS custom properties
|
|
5
|
+
* for color, typography, layout, and motion. This stylesheet defines a
|
|
6
|
+
* complete, working set of defaults so consumers can render components
|
|
7
|
+
* out of the box without authoring tokens themselves.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
*
|
|
11
|
+
* import '@refraction-ui/react/styles.css'
|
|
12
|
+
*
|
|
13
|
+
* The stylesheet is opt-in. Consumers who already define their own tokens
|
|
14
|
+
* (or who use `@refraction-ui/tailwind-config`'s preset) should NOT import
|
|
15
|
+
* this file — every component will continue to work with consumer-defined
|
|
16
|
+
* tokens.
|
|
17
|
+
*
|
|
18
|
+
* Override a single token by re-declaring it after the import, for example:
|
|
19
|
+
*
|
|
20
|
+
* :root {
|
|
21
|
+
* --primary: 280 70% 50%;
|
|
22
|
+
* --radius: 0.5rem;
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Dark mode follows the `react-theme` ThemeProvider convention: the dark
|
|
26
|
+
* palette activates when `.dark` is on the html/root element (the default
|
|
27
|
+
* `attribute: 'class'` mode), or when `[data-theme="dark"]` is set (the
|
|
28
|
+
* alternative `attribute: 'data-theme'` mode). Both selectors are wired
|
|
29
|
+
* up below so either ThemeProvider configuration "just works".
|
|
30
|
+
*
|
|
31
|
+
* The default palette is the canonical "Refraction" theme defined in
|
|
32
|
+
* @refraction-ui/tailwind-config/src/themes/glassa.ts (Linear + Vercel-
|
|
33
|
+
* inspired calm violet on a near-white surface, subtle 6% borders, 0.375rem
|
|
34
|
+
* radius, system/Inter font stack).
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
:root {
|
|
38
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
39
|
+
Color tokens (HSL triplets — components wrap them as hsl(var(--token)))
|
|
40
|
+
───────────────────────────────────────────────────────────────────── */
|
|
41
|
+
--background: 0 0% 99%;
|
|
42
|
+
--foreground: 240 10% 10%;
|
|
43
|
+
--card: 0 0% 99%;
|
|
44
|
+
--card-foreground: 240 10% 10%;
|
|
45
|
+
--popover: 0 0% 100%;
|
|
46
|
+
--popover-foreground: 240 10% 10%;
|
|
47
|
+
|
|
48
|
+
--primary: 250 50% 50%;
|
|
49
|
+
--primary-foreground: 0 0% 100%;
|
|
50
|
+
--secondary: 240 5% 96%;
|
|
51
|
+
--secondary-foreground: 240 4% 44%;
|
|
52
|
+
--accent: 250 30% 95%;
|
|
53
|
+
--accent-foreground: 250 50% 40%;
|
|
54
|
+
|
|
55
|
+
--muted: 240 5% 96%;
|
|
56
|
+
--muted-foreground: 240 4% 44%;
|
|
57
|
+
|
|
58
|
+
--destructive: 0 84% 50%;
|
|
59
|
+
--destructive-foreground: 0 0% 100%;
|
|
60
|
+
--success: 142 71% 35%;
|
|
61
|
+
--success-foreground: 0 0% 100%;
|
|
62
|
+
--warning: 38 92% 50%;
|
|
63
|
+
--warning-foreground: 240 10% 10%;
|
|
64
|
+
|
|
65
|
+
--border: 240 6% 92%;
|
|
66
|
+
--input: 240 6% 92%;
|
|
67
|
+
--ring: 250 50% 50%;
|
|
68
|
+
|
|
69
|
+
/* Charts (colorblind-safe spread) */
|
|
70
|
+
--chart-1: 250 50% 50%;
|
|
71
|
+
--chart-2: 173 80% 36%;
|
|
72
|
+
--chart-3: 38 92% 50%;
|
|
73
|
+
--chart-4: 330 65% 50%;
|
|
74
|
+
--chart-5: 201 96% 42%;
|
|
75
|
+
|
|
76
|
+
/* Sidebar */
|
|
77
|
+
--sidebar-background: 0 0% 98%;
|
|
78
|
+
--sidebar-foreground: 240 10% 10%;
|
|
79
|
+
--sidebar-primary: 250 50% 50%;
|
|
80
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
81
|
+
--sidebar-accent: 250 30% 95%;
|
|
82
|
+
--sidebar-accent-foreground: 250 50% 40%;
|
|
83
|
+
--sidebar-border: 240 6% 92%;
|
|
84
|
+
--sidebar-ring: 250 50% 50%;
|
|
85
|
+
|
|
86
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
87
|
+
Shape language
|
|
88
|
+
───────────────────────────────────────────────────────────────────── */
|
|
89
|
+
--radius: 0.375rem;
|
|
90
|
+
--radius-none: 0;
|
|
91
|
+
--radius-sm: 0.25rem;
|
|
92
|
+
--radius-md: 0.375rem;
|
|
93
|
+
--radius-lg: 0.5rem;
|
|
94
|
+
--radius-xl: 0.75rem;
|
|
95
|
+
--radius-2xl: 1rem;
|
|
96
|
+
--radius-full: 9999px;
|
|
97
|
+
|
|
98
|
+
--avatar-radius: 9999px;
|
|
99
|
+
--badge-radius: 9999px;
|
|
100
|
+
--button-radius: 0.375rem;
|
|
101
|
+
--input-radius: 0.375rem;
|
|
102
|
+
--card-radius: 0.5rem;
|
|
103
|
+
--tooltip-radius: 0.375rem;
|
|
104
|
+
|
|
105
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
106
|
+
Typography
|
|
107
|
+
───────────────────────────────────────────────────────────────────── */
|
|
108
|
+
--font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
109
|
+
--font-heading: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
|
|
110
|
+
--font-serif: ui-serif, Georgia, 'Times New Roman', serif;
|
|
111
|
+
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
112
|
+
|
|
113
|
+
--font-size-xs: 0.75rem;
|
|
114
|
+
--font-size-sm: 0.875rem;
|
|
115
|
+
--font-size-base: 1rem;
|
|
116
|
+
--font-size-lg: 1.125rem;
|
|
117
|
+
--font-size-xl: 1.25rem;
|
|
118
|
+
--font-size-2xl: 1.5rem;
|
|
119
|
+
--font-size-3xl: 1.875rem;
|
|
120
|
+
--font-size-4xl: 2.25rem;
|
|
121
|
+
--font-size-5xl: 3rem;
|
|
122
|
+
|
|
123
|
+
--font-weight-normal: 400;
|
|
124
|
+
--font-weight-medium: 500;
|
|
125
|
+
--font-weight-semibold: 600;
|
|
126
|
+
--font-weight-bold: 700;
|
|
127
|
+
|
|
128
|
+
--letter-spacing-tighter: -0.04em;
|
|
129
|
+
--letter-spacing-tight: -0.02em;
|
|
130
|
+
--letter-spacing-normal: 0;
|
|
131
|
+
--letter-spacing-wide: 0.02em;
|
|
132
|
+
--letter-spacing-wider: 0.05em;
|
|
133
|
+
|
|
134
|
+
--line-height-tight: 1.3;
|
|
135
|
+
--line-height-normal: 1.5;
|
|
136
|
+
--line-height-relaxed: 1.7;
|
|
137
|
+
|
|
138
|
+
--heading-weight: 600;
|
|
139
|
+
--heading-letter-spacing: -0.02em;
|
|
140
|
+
--heading-line-height: 1.2;
|
|
141
|
+
|
|
142
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
143
|
+
Shadows / elevation
|
|
144
|
+
───────────────────────────────────────────────────────────────────── */
|
|
145
|
+
--shadow-none: none;
|
|
146
|
+
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.03);
|
|
147
|
+
--shadow-md: 0 2px 4px -1px rgb(0 0 0 / 0.04), 0 1px 2px -1px rgb(0 0 0 / 0.03);
|
|
148
|
+
--shadow-lg: 0 6px 10px -2px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.03);
|
|
149
|
+
--shadow-xl: 0 12px 20px -4px rgb(0 0 0 / 0.06), 0 4px 6px -4px rgb(0 0 0 / 0.03);
|
|
150
|
+
|
|
151
|
+
--card-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.03);
|
|
152
|
+
--dropdown-shadow: 0 6px 10px -2px rgb(0 0 0 / 0.05), 0 2px 4px -2px rgb(0 0 0 / 0.03);
|
|
153
|
+
--dialog-shadow: 0 12px 20px -4px rgb(0 0 0 / 0.06), 0 4px 6px -4px rgb(0 0 0 / 0.03);
|
|
154
|
+
--button-shadow: none;
|
|
155
|
+
|
|
156
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
157
|
+
Glass / overlay
|
|
158
|
+
───────────────────────────────────────────────────────────────────── */
|
|
159
|
+
--overlay-opacity: 0.5;
|
|
160
|
+
--backdrop-blur: 8px;
|
|
161
|
+
--glass-bg: rgba(252, 252, 253, 0.8);
|
|
162
|
+
--glass-border: rgba(255, 255, 255, 0.15);
|
|
163
|
+
|
|
164
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
165
|
+
Spacing & density
|
|
166
|
+
───────────────────────────────────────────────────────────────────── */
|
|
167
|
+
--spacing-scale: 1;
|
|
168
|
+
--container-max-width: 1280px;
|
|
169
|
+
--container-padding: 2rem;
|
|
170
|
+
--card-padding: 1.5rem;
|
|
171
|
+
--input-height: 2.5rem;
|
|
172
|
+
--button-height: 2.25rem;
|
|
173
|
+
--section-gap: 4rem;
|
|
174
|
+
|
|
175
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
176
|
+
Layout / app-shell
|
|
177
|
+
───────────────────────────────────────────────────────────────────── */
|
|
178
|
+
--page-max-width: 1280px;
|
|
179
|
+
--page-nav-height: 3.5rem;
|
|
180
|
+
--shell-header-height: 3.5rem;
|
|
181
|
+
--shell-sidebar-width: 16rem;
|
|
182
|
+
--shell-sidebar-full-width: 16rem;
|
|
183
|
+
|
|
184
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
185
|
+
Borders & dividers
|
|
186
|
+
───────────────────────────────────────────────────────────────────── */
|
|
187
|
+
--border-width: 1px;
|
|
188
|
+
--border-style: solid;
|
|
189
|
+
--divider-style: solid;
|
|
190
|
+
--divider-opacity: 0.15;
|
|
191
|
+
|
|
192
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
193
|
+
Component behavior tokens
|
|
194
|
+
───────────────────────────────────────────────────────────────────── */
|
|
195
|
+
--input-style: bordered;
|
|
196
|
+
--input-border-on-focus: 0;
|
|
197
|
+
--placeholder-opacity: 0.5;
|
|
198
|
+
|
|
199
|
+
--button-style: filled;
|
|
200
|
+
--button-weight: 500;
|
|
201
|
+
--hover-effect: darken;
|
|
202
|
+
--active-effect: scale-down;
|
|
203
|
+
--disabled-opacity: 0.5;
|
|
204
|
+
|
|
205
|
+
--link-style: color-only;
|
|
206
|
+
--link-weight: inherit;
|
|
207
|
+
|
|
208
|
+
--focus-ring-width: 2px;
|
|
209
|
+
--focus-ring-offset: 2px;
|
|
210
|
+
--focus-ring-style: ring;
|
|
211
|
+
|
|
212
|
+
--icon-style: outlined;
|
|
213
|
+
--icon-stroke-width: 1.5;
|
|
214
|
+
--icon-size: 1.25rem;
|
|
215
|
+
|
|
216
|
+
--scrollbar-style: thin;
|
|
217
|
+
--scrollbar-track: transparent;
|
|
218
|
+
--scrollbar-thumb: rgba(0, 0, 0, 0.15);
|
|
219
|
+
|
|
220
|
+
--tooltip-style: dark;
|
|
221
|
+
--table-style: clean;
|
|
222
|
+
--table-header-weight: 600;
|
|
223
|
+
--spinner-style: circle;
|
|
224
|
+
|
|
225
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
226
|
+
Selection
|
|
227
|
+
───────────────────────────────────────────────────────────────────── */
|
|
228
|
+
--selection-background: 250 50% 50%;
|
|
229
|
+
--selection-foreground: 0 0% 100%;
|
|
230
|
+
|
|
231
|
+
/* ─────────────────────────────────────────────────────────────────────
|
|
232
|
+
Motion
|
|
233
|
+
───────────────────────────────────────────────────────────────────── */
|
|
234
|
+
--transition-duration: 150ms;
|
|
235
|
+
--transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
|
|
236
|
+
--animation-speed: fast;
|
|
237
|
+
--hover-transition: 150ms ease;
|
|
238
|
+
--enter-transition: 200ms ease-out;
|
|
239
|
+
--exit-transition: 150ms ease-in;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/*
|
|
243
|
+
* Dark mode — activated by `.dark` class (default ThemeProvider mode)
|
|
244
|
+
* or by `[data-theme="dark"]` (attribute mode). Matches the convention
|
|
245
|
+
* implemented in @refraction-ui/theme/src/dom-adapters.ts.
|
|
246
|
+
*/
|
|
247
|
+
.dark,
|
|
248
|
+
[data-theme='dark'] {
|
|
249
|
+
--background: 240 10% 4%;
|
|
250
|
+
--foreground: 0 0% 98%;
|
|
251
|
+
--card: 240 10% 8%;
|
|
252
|
+
--card-foreground: 0 0% 98%;
|
|
253
|
+
--popover: 240 10% 8%;
|
|
254
|
+
--popover-foreground: 0 0% 98%;
|
|
255
|
+
|
|
256
|
+
--primary: 250 50% 65%;
|
|
257
|
+
--primary-foreground: 240 10% 4%;
|
|
258
|
+
--secondary: 240 5% 16%;
|
|
259
|
+
--secondary-foreground: 240 5% 65%;
|
|
260
|
+
--accent: 250 30% 20%;
|
|
261
|
+
--accent-foreground: 250 60% 80%;
|
|
262
|
+
|
|
263
|
+
--muted: 240 5% 16%;
|
|
264
|
+
--muted-foreground: 240 5% 65%;
|
|
265
|
+
|
|
266
|
+
--destructive: 0 63% 31%;
|
|
267
|
+
--destructive-foreground: 0 0% 98%;
|
|
268
|
+
--success: 142 71% 45%;
|
|
269
|
+
--success-foreground: 0 0% 98%;
|
|
270
|
+
--warning: 38 92% 50%;
|
|
271
|
+
--warning-foreground: 240 10% 4%;
|
|
272
|
+
|
|
273
|
+
--border: 240 5% 16%;
|
|
274
|
+
--input: 240 5% 16%;
|
|
275
|
+
--ring: 250 50% 65%;
|
|
276
|
+
|
|
277
|
+
--chart-1: 250 50% 65%;
|
|
278
|
+
--chart-2: 173 70% 50%;
|
|
279
|
+
--chart-3: 38 92% 60%;
|
|
280
|
+
--chart-4: 330 60% 65%;
|
|
281
|
+
--chart-5: 201 90% 55%;
|
|
282
|
+
|
|
283
|
+
--sidebar-background: 240 10% 4%;
|
|
284
|
+
--sidebar-foreground: 0 0% 98%;
|
|
285
|
+
--sidebar-primary: 250 50% 65%;
|
|
286
|
+
--sidebar-primary-foreground: 240 10% 4%;
|
|
287
|
+
--sidebar-accent: 250 30% 20%;
|
|
288
|
+
--sidebar-accent-foreground: 250 60% 80%;
|
|
289
|
+
--sidebar-border: 240 5% 16%;
|
|
290
|
+
--sidebar-ring: 250 50% 65%;
|
|
291
|
+
|
|
292
|
+
/* Glass / scrollbar tweaks for dark surface */
|
|
293
|
+
--glass-bg: rgba(20, 20, 24, 0.7);
|
|
294
|
+
--glass-border: rgba(255, 255, 255, 0.06);
|
|
295
|
+
--scrollbar-thumb: rgba(255, 255, 255, 0.18);
|
|
296
|
+
}
|
package/dist/theme.cjs
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React2 = require('react');
|
|
4
|
+
|
|
5
|
+
function _interopNamespace(e) {
|
|
6
|
+
if (e && e.__esModule) return e;
|
|
7
|
+
var n = Object.create(null);
|
|
8
|
+
if (e) {
|
|
9
|
+
Object.keys(e).forEach(function (k) {
|
|
10
|
+
if (k !== 'default') {
|
|
11
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
12
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () { return e[k]; }
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
n.default = e;
|
|
20
|
+
return Object.freeze(n);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
|
|
24
|
+
|
|
25
|
+
// ../react-theme/dist/index.js
|
|
26
|
+
|
|
27
|
+
// ../theme/dist/index.js
|
|
28
|
+
function resolveTheme(mode, systemPrefersDark) {
|
|
29
|
+
if (mode === "system") {
|
|
30
|
+
return systemPrefersDark ? "dark" : "light";
|
|
31
|
+
}
|
|
32
|
+
return mode;
|
|
33
|
+
}
|
|
34
|
+
function createTheme(config = {}, storage, mediaQuery) {
|
|
35
|
+
const {
|
|
36
|
+
defaultMode = "system",
|
|
37
|
+
storageKey = "rfr-theme"
|
|
38
|
+
} = config;
|
|
39
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
40
|
+
let cleanupMediaQuery = null;
|
|
41
|
+
const persisted = storage?.get(storageKey);
|
|
42
|
+
let mode = persisted && ["light", "dark", "system"].includes(persisted) ? persisted : defaultMode;
|
|
43
|
+
let systemPrefersDark = mediaQuery?.matches("(prefers-color-scheme: dark)") ?? false;
|
|
44
|
+
let state = {
|
|
45
|
+
mode,
|
|
46
|
+
resolved: resolveTheme(mode, systemPrefersDark)
|
|
47
|
+
};
|
|
48
|
+
function notify() {
|
|
49
|
+
for (const fn of listeners) {
|
|
50
|
+
fn(state);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function updateState(newMode) {
|
|
54
|
+
mode = newMode;
|
|
55
|
+
state = { mode, resolved: resolveTheme(mode, systemPrefersDark) };
|
|
56
|
+
storage?.set(storageKey, mode);
|
|
57
|
+
notify();
|
|
58
|
+
}
|
|
59
|
+
if (mediaQuery) {
|
|
60
|
+
cleanupMediaQuery = mediaQuery.subscribe(
|
|
61
|
+
"(prefers-color-scheme: dark)",
|
|
62
|
+
(matches) => {
|
|
63
|
+
systemPrefersDark = matches;
|
|
64
|
+
if (mode === "system") {
|
|
65
|
+
state = { mode, resolved: resolveTheme(mode, systemPrefersDark) };
|
|
66
|
+
notify();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
getState() {
|
|
73
|
+
return state;
|
|
74
|
+
},
|
|
75
|
+
setMode(newMode) {
|
|
76
|
+
if (newMode !== mode) {
|
|
77
|
+
updateState(newMode);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
subscribe(fn) {
|
|
81
|
+
listeners.add(fn);
|
|
82
|
+
return () => {
|
|
83
|
+
listeners.delete(fn);
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
destroy() {
|
|
87
|
+
listeners.clear();
|
|
88
|
+
cleanupMediaQuery?.();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function getThemeScript(storageKey = "rfr-theme", attribute = "class") {
|
|
93
|
+
return `(function(){try{var m=localStorage.getItem('${storageKey}');var s=window.matchMedia('(prefers-color-scheme:dark)').matches;var t=m==='dark'||(m!=='light'&&s)?'dark':'light';var d=document.documentElement;${attribute === "class" ? "d.classList.remove('light','dark');d.classList.add(t);" : `d.setAttribute('${attribute}',t);`}d.style.colorScheme=t;}catch(e){}})()`;
|
|
94
|
+
}
|
|
95
|
+
function createLocalStorageAdapter() {
|
|
96
|
+
return {
|
|
97
|
+
get(key) {
|
|
98
|
+
try {
|
|
99
|
+
return localStorage.getItem(key);
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
set(key, value) {
|
|
105
|
+
try {
|
|
106
|
+
localStorage.setItem(key, value);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function createMediaQueryAdapter() {
|
|
113
|
+
return {
|
|
114
|
+
matches(query) {
|
|
115
|
+
if (typeof window === "undefined") return false;
|
|
116
|
+
return window.matchMedia(query).matches;
|
|
117
|
+
},
|
|
118
|
+
subscribe(query, callback) {
|
|
119
|
+
if (typeof window === "undefined") return () => {
|
|
120
|
+
};
|
|
121
|
+
const mql = window.matchMedia(query);
|
|
122
|
+
const handler = (e) => callback(e.matches);
|
|
123
|
+
mql.addEventListener("change", handler);
|
|
124
|
+
return () => mql.removeEventListener("change", handler);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function applyThemeToDOM(resolved, attribute = "class") {
|
|
129
|
+
if (typeof document === "undefined") return;
|
|
130
|
+
const root = document.documentElement;
|
|
131
|
+
if (attribute === "class") {
|
|
132
|
+
root.classList.remove("light", "dark");
|
|
133
|
+
root.classList.add(resolved);
|
|
134
|
+
} else {
|
|
135
|
+
root.setAttribute(attribute, resolved);
|
|
136
|
+
}
|
|
137
|
+
root.style.colorScheme = resolved;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ../react-theme/dist/index.js
|
|
141
|
+
var ThemeContext = React2__namespace.createContext(null);
|
|
142
|
+
function ThemeProvider({
|
|
143
|
+
children,
|
|
144
|
+
defaultMode = "system",
|
|
145
|
+
storageKey = "rfr-theme",
|
|
146
|
+
attribute = "class"
|
|
147
|
+
}) {
|
|
148
|
+
const themeRef = React2__namespace.useRef(null);
|
|
149
|
+
if (!themeRef.current) {
|
|
150
|
+
const isBrowser = typeof window !== "undefined";
|
|
151
|
+
themeRef.current = createTheme(
|
|
152
|
+
{ defaultMode, storageKey},
|
|
153
|
+
isBrowser ? createLocalStorageAdapter() : void 0,
|
|
154
|
+
isBrowser ? createMediaQueryAdapter() : void 0
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
const [state, setState] = React2__namespace.useState(() => themeRef.current.getState());
|
|
158
|
+
React2__namespace.useEffect(() => {
|
|
159
|
+
const theme = themeRef.current;
|
|
160
|
+
applyThemeToDOM(theme.getState().resolved, attribute);
|
|
161
|
+
const unsub = theme.subscribe((newState) => {
|
|
162
|
+
setState(newState);
|
|
163
|
+
applyThemeToDOM(newState.resolved, attribute);
|
|
164
|
+
});
|
|
165
|
+
return () => {
|
|
166
|
+
unsub();
|
|
167
|
+
theme.destroy();
|
|
168
|
+
};
|
|
169
|
+
}, [attribute]);
|
|
170
|
+
const contextValue = React2__namespace.useMemo(
|
|
171
|
+
() => ({
|
|
172
|
+
mode: state.mode,
|
|
173
|
+
resolved: state.resolved,
|
|
174
|
+
setMode: (mode) => themeRef.current?.setMode(mode)
|
|
175
|
+
}),
|
|
176
|
+
[state.mode, state.resolved]
|
|
177
|
+
);
|
|
178
|
+
return React2__namespace.createElement(ThemeContext.Provider, { value: contextValue }, children);
|
|
179
|
+
}
|
|
180
|
+
function useTheme() {
|
|
181
|
+
const context = React2__namespace.useContext(ThemeContext);
|
|
182
|
+
if (!context) {
|
|
183
|
+
throw new Error("useTheme must be used within a <ThemeProvider>");
|
|
184
|
+
}
|
|
185
|
+
return context;
|
|
186
|
+
}
|
|
187
|
+
var modes = [
|
|
188
|
+
{ value: "light", label: "Light", icon: "sun" },
|
|
189
|
+
{ value: "dark", label: "Dark", icon: "moon" },
|
|
190
|
+
{ value: "system", label: "System", icon: "monitor" }
|
|
191
|
+
];
|
|
192
|
+
var icons = {
|
|
193
|
+
sun: React2__namespace.createElement(
|
|
194
|
+
"svg",
|
|
195
|
+
{
|
|
196
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
197
|
+
width: 16,
|
|
198
|
+
height: 16,
|
|
199
|
+
viewBox: "0 0 24 24",
|
|
200
|
+
fill: "none",
|
|
201
|
+
stroke: "currentColor",
|
|
202
|
+
strokeWidth: 2,
|
|
203
|
+
strokeLinecap: "round",
|
|
204
|
+
strokeLinejoin: "round"
|
|
205
|
+
},
|
|
206
|
+
React2__namespace.createElement("circle", { cx: 12, cy: 12, r: 5 }),
|
|
207
|
+
React2__namespace.createElement("path", { d: "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42" })
|
|
208
|
+
),
|
|
209
|
+
moon: React2__namespace.createElement(
|
|
210
|
+
"svg",
|
|
211
|
+
{
|
|
212
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
213
|
+
width: 16,
|
|
214
|
+
height: 16,
|
|
215
|
+
viewBox: "0 0 24 24",
|
|
216
|
+
fill: "none",
|
|
217
|
+
stroke: "currentColor",
|
|
218
|
+
strokeWidth: 2,
|
|
219
|
+
strokeLinecap: "round",
|
|
220
|
+
strokeLinejoin: "round"
|
|
221
|
+
},
|
|
222
|
+
React2__namespace.createElement("path", { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" })
|
|
223
|
+
),
|
|
224
|
+
monitor: React2__namespace.createElement(
|
|
225
|
+
"svg",
|
|
226
|
+
{
|
|
227
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
228
|
+
width: 16,
|
|
229
|
+
height: 16,
|
|
230
|
+
viewBox: "0 0 24 24",
|
|
231
|
+
fill: "none",
|
|
232
|
+
stroke: "currentColor",
|
|
233
|
+
strokeWidth: 2,
|
|
234
|
+
strokeLinecap: "round",
|
|
235
|
+
strokeLinejoin: "round"
|
|
236
|
+
},
|
|
237
|
+
React2__namespace.createElement("rect", { x: 2, y: 3, width: 20, height: 14, rx: 2, ry: 2 }),
|
|
238
|
+
React2__namespace.createElement("line", { x1: 8, y1: 21, x2: 16, y2: 21 }),
|
|
239
|
+
React2__namespace.createElement("line", { x1: 12, y1: 17, x2: 12, y2: 21 })
|
|
240
|
+
)
|
|
241
|
+
};
|
|
242
|
+
function ThemeToggle({ className, variant = "segmented" }) {
|
|
243
|
+
const { mode, setMode } = useTheme();
|
|
244
|
+
if (variant === "segmented") {
|
|
245
|
+
return React2__namespace.createElement(
|
|
246
|
+
"div",
|
|
247
|
+
{
|
|
248
|
+
className: `inline-flex items-center gap-1 rounded-lg border p-1 ${className ?? ""}`,
|
|
249
|
+
role: "radiogroup",
|
|
250
|
+
"aria-label": "Theme"
|
|
251
|
+
},
|
|
252
|
+
modes.map(
|
|
253
|
+
({ value, label, icon }) => React2__namespace.createElement("button", {
|
|
254
|
+
key: value,
|
|
255
|
+
type: "button",
|
|
256
|
+
role: "radio",
|
|
257
|
+
"aria-checked": mode === value,
|
|
258
|
+
"aria-label": label,
|
|
259
|
+
className: `inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors ${mode === value ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-muted"}`,
|
|
260
|
+
onClick: () => setMode(value)
|
|
261
|
+
}, icons[icon])
|
|
262
|
+
)
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
const [open, setOpen] = React2__namespace.useState(false);
|
|
266
|
+
const ref = React2__namespace.useRef(null);
|
|
267
|
+
React2__namespace.useEffect(() => {
|
|
268
|
+
if (!open) return;
|
|
269
|
+
const handler = (e) => {
|
|
270
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
271
|
+
};
|
|
272
|
+
document.addEventListener("mousedown", handler);
|
|
273
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
274
|
+
}, [open]);
|
|
275
|
+
const currentIcon = modes.find((m) => m.value === mode)?.icon ?? "monitor";
|
|
276
|
+
return React2__namespace.createElement(
|
|
277
|
+
"div",
|
|
278
|
+
{ ref, className: `relative ${className ?? ""}` },
|
|
279
|
+
React2__namespace.createElement("button", {
|
|
280
|
+
type: "button",
|
|
281
|
+
"aria-label": "Toggle theme",
|
|
282
|
+
"aria-expanded": open,
|
|
283
|
+
className: "inline-flex items-center justify-center rounded-md p-2 text-sm transition-colors hover:bg-muted",
|
|
284
|
+
onClick: () => setOpen(!open)
|
|
285
|
+
}, icons[currentIcon]),
|
|
286
|
+
open && React2__namespace.createElement(
|
|
287
|
+
"div",
|
|
288
|
+
{
|
|
289
|
+
className: "absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-md border bg-popover p-1 shadow-md",
|
|
290
|
+
role: "menu"
|
|
291
|
+
},
|
|
292
|
+
modes.map(
|
|
293
|
+
({ value, label, icon }) => React2__namespace.createElement("button", {
|
|
294
|
+
key: value,
|
|
295
|
+
type: "button",
|
|
296
|
+
role: "menuitem",
|
|
297
|
+
className: `flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent ${mode === value ? "bg-accent" : ""}`,
|
|
298
|
+
onClick: () => {
|
|
299
|
+
setMode(value);
|
|
300
|
+
setOpen(false);
|
|
301
|
+
}
|
|
302
|
+
}, icons[icon], label)
|
|
303
|
+
)
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function ThemeScript({
|
|
308
|
+
storageKey = "rfr-theme",
|
|
309
|
+
attribute = "class"
|
|
310
|
+
}) {
|
|
311
|
+
return React2__namespace.createElement("script", {
|
|
312
|
+
dangerouslySetInnerHTML: {
|
|
313
|
+
__html: getThemeScript(storageKey, attribute)
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
exports.ThemeProvider = ThemeProvider;
|
|
319
|
+
exports.ThemeScript = ThemeScript;
|
|
320
|
+
exports.ThemeToggle = ThemeToggle;
|
|
321
|
+
exports.useTheme = useTheme;
|
|
322
|
+
//# sourceMappingURL=theme.cjs.map
|
|
323
|
+
//# sourceMappingURL=theme.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../theme/src/theme-machine.ts","../../theme/src/theme-script.ts","../../theme/src/dom-adapters.ts","../../react-theme/src/theme-provider.tsx","../../react-theme/src/theme-toggle.tsx","../../react-theme/src/theme-script-component.tsx"],"names":["React","React2","React3"],"mappings":"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","file":"theme.cjs","sourcesContent":["/**\n * Headless theme state machine — pure TypeScript, zero DOM dependencies.\n * Manages light/dark/system mode with system preference tracking.\n */\n\nexport type ThemeMode = 'light' | 'dark' | 'system'\nexport type ResolvedTheme = 'light' | 'dark'\n\nexport interface ThemeState {\n /** User's chosen mode */\n mode: ThemeMode\n /** Resolved theme after system preference detection */\n resolved: ResolvedTheme\n}\n\nexport interface ThemeConfig {\n /** Initial mode. Default: 'system' */\n defaultMode?: ThemeMode\n /** localStorage key. Default: 'rfr-theme' */\n storageKey?: string\n /** HTML attribute to set. Default: 'class' */\n attribute?: 'class' | 'data-theme'\n}\n\nexport interface StorageAdapter {\n get(key: string): string | null\n set(key: string, value: string): void\n}\n\nexport interface MediaQueryAdapter {\n matches(query: string): boolean\n subscribe(query: string, callback: (matches: boolean) => void): () => void\n}\n\nexport interface ThemeAPI {\n /** Get current state */\n getState(): ThemeState\n /** Set mode (light/dark/system) */\n setMode(mode: ThemeMode): void\n /** Subscribe to state changes */\n subscribe(fn: (state: ThemeState) => void): () => void\n /** Clean up subscriptions */\n destroy(): void\n}\n\nfunction resolveTheme(mode: ThemeMode, systemPrefersDark: boolean): ResolvedTheme {\n if (mode === 'system') {\n return systemPrefersDark ? 'dark' : 'light'\n }\n return mode\n}\n\nexport function createTheme(\n config: ThemeConfig = {},\n storage?: StorageAdapter,\n mediaQuery?: MediaQueryAdapter,\n): ThemeAPI {\n const {\n defaultMode = 'system',\n storageKey = 'rfr-theme',\n } = config\n\n const listeners = new Set<(state: ThemeState) => void>()\n let cleanupMediaQuery: (() => void) | null = null\n\n // Read persisted mode or use default\n const persisted = storage?.get(storageKey) as ThemeMode | null\n let mode: ThemeMode = persisted && ['light', 'dark', 'system'].includes(persisted)\n ? persisted\n : defaultMode\n\n // Detect system preference\n let systemPrefersDark = mediaQuery?.matches('(prefers-color-scheme: dark)') ?? false\n\n let state: ThemeState = {\n mode,\n resolved: resolveTheme(mode, systemPrefersDark),\n }\n\n function notify() {\n for (const fn of listeners) {\n fn(state)\n }\n }\n\n function updateState(newMode: ThemeMode) {\n mode = newMode\n state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }\n storage?.set(storageKey, mode)\n notify()\n }\n\n // Listen for system preference changes\n if (mediaQuery) {\n cleanupMediaQuery = mediaQuery.subscribe(\n '(prefers-color-scheme: dark)',\n (matches) => {\n systemPrefersDark = matches\n if (mode === 'system') {\n state = { mode, resolved: resolveTheme(mode, systemPrefersDark) }\n notify()\n }\n },\n )\n }\n\n return {\n getState() {\n return state\n },\n\n setMode(newMode: ThemeMode) {\n if (newMode !== mode) {\n updateState(newMode)\n }\n },\n\n subscribe(fn: (state: ThemeState) => void) {\n listeners.add(fn)\n return () => {\n listeners.delete(fn)\n }\n },\n\n destroy() {\n listeners.clear()\n cleanupMediaQuery?.()\n },\n }\n}\n","/**\n * Inline script for preventing theme flash on page load.\n * Inject this as a <script> tag in the <head> before any CSS.\n * Works with any framework (React, Angular, Astro, plain HTML).\n */\n\nexport function getThemeScript(\n storageKey = 'rfr-theme',\n attribute: 'class' | 'data-theme' = 'class',\n): string {\n // This string is injected as innerHTML of a <script> tag.\n // It runs before any CSS/JS loads, preventing flash of wrong theme.\n return `(function(){try{var m=localStorage.getItem('${storageKey}');var s=window.matchMedia('(prefers-color-scheme:dark)').matches;var t=m==='dark'||(m!=='light'&&s)?'dark':'light';var d=document.documentElement;${\n attribute === 'class'\n ? \"d.classList.remove('light','dark');d.classList.add(t);\"\n : `d.setAttribute('${attribute}',t);`\n }d.style.colorScheme=t;}catch(e){}})()`\n}\n","/**\n * Browser DOM adapters for the theme machine.\n * These bridge the headless core to browser APIs.\n */\n\nimport type { StorageAdapter, MediaQueryAdapter, ResolvedTheme } from './theme-machine.js'\n\n/** localStorage adapter */\nexport function createLocalStorageAdapter(): StorageAdapter {\n return {\n get(key) {\n try {\n return localStorage.getItem(key)\n } catch {\n return null\n }\n },\n set(key, value) {\n try {\n localStorage.setItem(key, value)\n } catch {\n // localStorage unavailable (SSR, incognito quota exceeded, etc.)\n }\n },\n }\n}\n\n/** matchMedia adapter */\nexport function createMediaQueryAdapter(): MediaQueryAdapter {\n return {\n matches(query) {\n if (typeof window === 'undefined') return false\n return window.matchMedia(query).matches\n },\n subscribe(query, callback) {\n if (typeof window === 'undefined') return () => {}\n const mql = window.matchMedia(query)\n const handler = (e: MediaQueryListEvent) => callback(e.matches)\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n },\n }\n}\n\n/** Apply resolved theme to the document */\nexport function applyThemeToDOM(\n resolved: ResolvedTheme,\n attribute: 'class' | 'data-theme' = 'class',\n): void {\n if (typeof document === 'undefined') return\n\n const root = document.documentElement\n if (attribute === 'class') {\n root.classList.remove('light', 'dark')\n root.classList.add(resolved)\n } else {\n root.setAttribute(attribute, resolved)\n }\n root.style.colorScheme = resolved\n}\n","import * as React from 'react'\nimport {\n createTheme,\n createLocalStorageAdapter,\n createMediaQueryAdapter,\n applyThemeToDOM,\n type ThemeMode,\n type ResolvedTheme,\n type ThemeConfig,\n type ThemeAPI,\n} from '@refraction-ui/theme'\n\nexport interface ThemeContextValue {\n mode: ThemeMode\n resolved: ResolvedTheme\n setMode: (mode: ThemeMode) => void\n}\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null)\n\nexport interface ThemeProviderProps extends ThemeConfig {\n children: React.ReactNode\n}\n\nexport function ThemeProvider({\n children,\n defaultMode = 'system',\n storageKey = 'rfr-theme',\n attribute = 'class',\n}: ThemeProviderProps) {\n const themeRef = React.useRef<ThemeAPI | null>(null)\n\n // Initialize theme machine once (client-side only for adapters)\n if (!themeRef.current) {\n const isBrowser = typeof window !== 'undefined'\n themeRef.current = createTheme(\n { defaultMode, storageKey, attribute },\n isBrowser ? createLocalStorageAdapter() : undefined,\n isBrowser ? createMediaQueryAdapter() : undefined,\n )\n }\n\n const [state, setState] = React.useState(() => themeRef.current!.getState())\n\n React.useEffect(() => {\n const theme = themeRef.current!\n // Apply initial theme to DOM\n applyThemeToDOM(theme.getState().resolved, attribute)\n\n // Subscribe to changes\n const unsub = theme.subscribe((newState) => {\n setState(newState)\n applyThemeToDOM(newState.resolved, attribute)\n })\n\n return () => {\n unsub()\n theme.destroy()\n }\n }, [attribute])\n\n const contextValue = React.useMemo<ThemeContextValue>(\n () => ({\n mode: state.mode,\n resolved: state.resolved,\n setMode: (mode: ThemeMode) => themeRef.current?.setMode(mode),\n }),\n [state.mode, state.resolved],\n )\n\n return React.createElement(ThemeContext.Provider, { value: contextValue }, children)\n}\n\nexport function useTheme(): ThemeContextValue {\n const context = React.useContext(ThemeContext)\n if (!context) {\n throw new Error('useTheme must be used within a <ThemeProvider>')\n }\n return context\n}\n","import * as React from 'react'\nimport { useTheme } from './theme-provider.js'\nimport type { ThemeMode } from '@refraction-ui/theme'\n\nconst modes: { value: ThemeMode; label: string; icon: string }[] = [\n { value: 'light', label: 'Light', icon: 'sun' },\n { value: 'dark', label: 'Dark', icon: 'moon' },\n { value: 'system', label: 'System', icon: 'monitor' },\n]\n\n// Inline SVG icons — no external icon library dependency\nconst icons: Record<string, React.ReactNode> = {\n sun: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('circle', { cx: 12, cy: 12, r: 5 }),\n React.createElement('path', { d: 'M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42' }),\n ),\n moon: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('path', { d: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' }),\n ),\n monitor: React.createElement('svg', {\n xmlns: 'http://www.w3.org/2000/svg', width: 16, height: 16, viewBox: '0 0 24 24',\n fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round',\n },\n React.createElement('rect', { x: 2, y: 3, width: 20, height: 14, rx: 2, ry: 2 }),\n React.createElement('line', { x1: 8, y1: 21, x2: 16, y2: 21 }),\n React.createElement('line', { x1: 12, y1: 17, x2: 12, y2: 21 }),\n ),\n}\n\nexport interface ThemeToggleProps {\n className?: string\n /** 'dropdown' shows a menu, 'segmented' shows inline buttons */\n variant?: 'dropdown' | 'segmented'\n}\n\nexport function ThemeToggle({ className, variant = 'segmented' }: ThemeToggleProps) {\n const { mode, setMode } = useTheme()\n\n if (variant === 'segmented') {\n return React.createElement('div', {\n className: `inline-flex items-center gap-1 rounded-lg border p-1 ${className ?? ''}`,\n role: 'radiogroup',\n 'aria-label': 'Theme',\n },\n modes.map(({ value, label, icon }) =>\n React.createElement('button', {\n key: value,\n type: 'button',\n role: 'radio',\n 'aria-checked': mode === value,\n 'aria-label': label,\n className: `inline-flex items-center justify-center rounded-md p-1.5 text-sm transition-colors ${\n mode === value\n ? 'bg-accent text-accent-foreground'\n : 'text-muted-foreground hover:bg-muted'\n }`,\n onClick: () => setMode(value),\n }, icons[icon]),\n ),\n )\n }\n\n // Dropdown variant — simplified, no external dropdown dependency\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (!open) return\n const handler = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)\n }\n document.addEventListener('mousedown', handler)\n return () => document.removeEventListener('mousedown', handler)\n }, [open])\n\n const currentIcon = modes.find((m) => m.value === mode)?.icon ?? 'monitor'\n\n return React.createElement('div', { ref, className: `relative ${className ?? ''}` },\n React.createElement('button', {\n type: 'button',\n 'aria-label': 'Toggle theme',\n 'aria-expanded': open,\n className: 'inline-flex items-center justify-center rounded-md p-2 text-sm transition-colors hover:bg-muted',\n onClick: () => setOpen(!open),\n }, icons[currentIcon]),\n open && React.createElement('div', {\n className: 'absolute right-0 top-full mt-1 z-50 min-w-[8rem] rounded-md border bg-popover p-1 shadow-md',\n role: 'menu',\n },\n modes.map(({ value, label, icon }) =>\n React.createElement('button', {\n key: value,\n type: 'button',\n role: 'menuitem',\n className: `flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors hover:bg-accent ${\n mode === value ? 'bg-accent' : ''\n }`,\n onClick: () => { setMode(value); setOpen(false) },\n }, icons[icon], label),\n ),\n ),\n )\n}\n","import * as React from 'react'\nimport { getThemeScript } from '@refraction-ui/theme'\n\nexport interface ThemeScriptProps {\n storageKey?: string\n attribute?: 'class' | 'data-theme'\n}\n\n/**\n * Renders an inline <script> that prevents theme flash on SSR pages.\n * Place this in the <head> of your document (in Next.js layout.tsx, Remix root, etc.)\n */\nexport function ThemeScript({\n storageKey = 'rfr-theme',\n attribute = 'class',\n}: ThemeScriptProps) {\n return React.createElement('script', {\n dangerouslySetInnerHTML: {\n __html: getThemeScript(storageKey, attribute),\n },\n })\n}\n"]}
|
package/dist/theme.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@refraction-ui/react-theme';
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from '@refraction-ui/react-theme';
|