@marwes-ui/react 1.0.2 → 1.0.3

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 CHANGED
@@ -358,6 +358,26 @@ 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
+
361
381
  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
382
 
363
383
  ```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]);