@marwes-ui/react 1.0.2 → 1.0.4
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 +37 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -358,6 +358,43 @@ export function App() {
|
|
|
358
358
|
|
|
359
359
|
This layer does not provide SSR no-flash behavior. Server output can still render the fallback mode before the client applies a stored or system preference. Use it for client-side preference and persistence, not for a no-flash SSR guarantee.
|
|
360
360
|
|
|
361
|
+
### Root Target Sync
|
|
362
|
+
|
|
363
|
+
By default Marwes keeps theme state scoped to the provider element. If your app shell also needs the resolved mode on `html` or `body`, set `target` and `attribute`:
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<MarwesProvider
|
|
367
|
+
defaultPreference="system"
|
|
368
|
+
storageKey="marwes-theme"
|
|
369
|
+
target="html"
|
|
370
|
+
attribute="class"
|
|
371
|
+
disableTransitionOnChange
|
|
372
|
+
>
|
|
373
|
+
<AppShell />
|
|
374
|
+
</MarwesProvider>
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`target` can be `"provider"`, `"html"`, or `"body"`. `attribute` can be `"class"`, `"data-theme"`, or `"data-mode"`. Class mode only adds the active `light` / `dark` class and removes the opposite one; unrelated classes are preserved. The provider root still keeps `mw-theme--light` / `mw-theme--dark` and provider-scoped `--mw-*` variables in every target mode.
|
|
378
|
+
|
|
379
|
+
Use one global-target provider per app shell. If multiple providers target `html` or `body`, deciding which provider owns that global element is app-owned behavior.
|
|
380
|
+
|
|
381
|
+
### Tailwind And shadcn Compatibility
|
|
382
|
+
|
|
383
|
+
Marwes is compatible with Tailwind and shadcn-style dark variants through `target="html" attribute="class"`. App-owned Tailwind tokens can also reference Marwes `--mw-*` variables when rendered inside the provider subtree.
|
|
384
|
+
|
|
385
|
+
```tsx
|
|
386
|
+
<MarwesProvider
|
|
387
|
+
defaultPreference="system"
|
|
388
|
+
storageKey="marwes-theme"
|
|
389
|
+
target="html"
|
|
390
|
+
attribute="class"
|
|
391
|
+
>
|
|
392
|
+
<AppShell />
|
|
393
|
+
</MarwesProvider>
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Read the [Tailwind and shadcn integration guide](https://github.com/niklas-westman/marwes/blob/main/docs/guides/tailwind-shadcn-integration.md) for Tailwind v3/v4 examples, token mapping, provider-scoped variable limits, and the SSR no-flash boundary.
|
|
397
|
+
|
|
361
398
|
For a simple brand pass, override shared values once and let Marwes fill the rest. If your product needs different brand colors in light and dark mode, control `mode` and switch between two small `ThemeInput` override objects:
|
|
362
399
|
|
|
363
400
|
```tsx
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ import { ThemeInput, ThemePreference, ThemeMode, FontLoadingConfig, ResolvedThem
|
|
|
3
3
|
export { AvatarSize, AvatarType, BadgeVariant, ButtonAction, ButtonSize, ButtonVariant, Density, FontLoadingConfig, FontLoadingMode, FontLoadingOptions, IconName, MwAvailableFont, MwFontFallback, MwGoogleFontFamily, MwStyledTheme, MwThemeVarName, MwThemeVarNames, MwThemeVarReference, MwThemeVars, ResolvedTheme, SpacingSize, Spacings, SwitchSize, Theme, ThemeInput, ThemeMode, ThemePreference, ToneName, createFontStack, mwAvailableFonts, mwFontFallbacks, mwGoogleFontFamilies, mwStyledTheme, mwThemeVarNames, mwThemeVars, mwVar } from '@marwes-ui/core';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
|
|
6
|
+
type ThemeTarget = "provider" | "html" | "body";
|
|
7
|
+
type ThemeAttribute = "class" | "data-theme" | "data-mode";
|
|
8
|
+
|
|
6
9
|
type MarwesProviderProps = {
|
|
7
10
|
theme?: ThemeInput;
|
|
8
11
|
defaultPreference?: ThemePreference;
|
|
@@ -14,9 +17,12 @@ type MarwesProviderProps = {
|
|
|
14
17
|
onModeChange?: (mode: ThemeMode) => void;
|
|
15
18
|
storageKey?: string | false;
|
|
16
19
|
enableSystem?: boolean;
|
|
20
|
+
target?: ThemeTarget;
|
|
21
|
+
attribute?: ThemeAttribute;
|
|
22
|
+
disableTransitionOnChange?: boolean;
|
|
17
23
|
children: React.ReactNode;
|
|
18
24
|
};
|
|
19
|
-
declare function MarwesProvider({ theme, defaultPreference, preference: controlledPreference, defaultMode, mode: controlledMode, fontLoading, onPreferenceChange, onModeChange, storageKey, enableSystem, children, }: MarwesProviderProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
declare function MarwesProvider({ theme, defaultPreference, preference: controlledPreference, defaultMode, mode: controlledMode, fontLoading, onPreferenceChange, onModeChange, storageKey, enableSystem, target, attribute, disableTransitionOnChange, children, }: MarwesProviderProps): react_jsx_runtime.JSX.Element;
|
|
20
26
|
|
|
21
27
|
declare function useTheme(): ResolvedTheme;
|
|
22
28
|
|
package/dist/index.js
CHANGED
|
@@ -103,6 +103,42 @@ function writeStoredThemePreference(storageKey, preference) {
|
|
|
103
103
|
} catch {
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
|
+
function applyModeAttribute({
|
|
107
|
+
target,
|
|
108
|
+
providerElement,
|
|
109
|
+
mode,
|
|
110
|
+
attribute
|
|
111
|
+
}) {
|
|
112
|
+
const element = getTargetElement(target, providerElement);
|
|
113
|
+
if (!element) return;
|
|
114
|
+
if (attribute === "class") {
|
|
115
|
+
const oppositeMode = mode === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark;
|
|
116
|
+
element.classList.remove(oppositeMode);
|
|
117
|
+
element.classList.add(mode);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
element.setAttribute(attribute, mode);
|
|
121
|
+
}
|
|
122
|
+
function withoutModeTransitions(callback) {
|
|
123
|
+
if (typeof document === "undefined" || typeof window === "undefined") {
|
|
124
|
+
callback();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const style = document.createElement("style");
|
|
128
|
+
style.dataset.marwesDisableTransitions = "true";
|
|
129
|
+
style.textContent = "* { transition: none !important; }";
|
|
130
|
+
document.head.appendChild(style);
|
|
131
|
+
callback();
|
|
132
|
+
window.getComputedStyle(document.body);
|
|
133
|
+
window.requestAnimationFrame(() => {
|
|
134
|
+
style.remove();
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function getTargetElement(target, providerElement) {
|
|
138
|
+
if (target === "provider") return providerElement;
|
|
139
|
+
if (typeof document === "undefined") return null;
|
|
140
|
+
return target === "html" ? document.documentElement : document.body;
|
|
141
|
+
}
|
|
106
142
|
function MarwesProvider({
|
|
107
143
|
theme,
|
|
108
144
|
defaultPreference,
|
|
@@ -114,6 +150,9 @@ function MarwesProvider({
|
|
|
114
150
|
onModeChange,
|
|
115
151
|
storageKey = false,
|
|
116
152
|
enableSystem = true,
|
|
153
|
+
target = "provider",
|
|
154
|
+
attribute = "class",
|
|
155
|
+
disableTransitionOnChange = false,
|
|
117
156
|
children
|
|
118
157
|
}) {
|
|
119
158
|
const rootRef = React2.useRef(null);
|
|
@@ -176,6 +215,22 @@ function MarwesProvider({
|
|
|
176
215
|
applyThemeToElement(rootRef.current, resolved);
|
|
177
216
|
}
|
|
178
217
|
}, [resolved]);
|
|
218
|
+
React2.useEffect(() => {
|
|
219
|
+
if (target === "provider") return;
|
|
220
|
+
const apply = () => {
|
|
221
|
+
applyModeAttribute({
|
|
222
|
+
target,
|
|
223
|
+
providerElement: rootRef.current,
|
|
224
|
+
mode: activeMode,
|
|
225
|
+
attribute
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
if (disableTransitionOnChange) {
|
|
229
|
+
withoutModeTransitions(apply);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
apply();
|
|
233
|
+
}, [activeMode, attribute, disableTransitionOnChange, target]);
|
|
179
234
|
React2.useEffect(() => {
|
|
180
235
|
loadThemeFonts(resolved, fontLoading);
|
|
181
236
|
}, [resolved, fontLoading]);
|