@marwes-ui/vue 1.0.1 → 1.0.2
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 -1
- package/dist/index.d.ts +21 -2
- package/dist/index.js +114 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img alt="Marwes Design System" src="https://raw.githubusercontent.com/niklas-westman/marwes/main/.github/assets/banner
|
|
3
|
+
<img alt="Marwes Design System" src="https://raw.githubusercontent.com/niklas-westman/marwes/main/.github/assets/banner.png" width="100%" style="border-radius: 40px;">
|
|
4
4
|
|
|
5
5
|
<br>
|
|
6
6
|
<br>
|
|
@@ -309,6 +309,49 @@ import ThemeToggle from "./theme-toggle.vue"
|
|
|
309
309
|
|
|
310
310
|
`default-mode` sets the initial uncontrolled mode. `toggleMode()` updates the provider, so Marwes components, preset CSS, and custom app styles that use `--mw-*` variables all move together without duplicating local theme state.
|
|
311
311
|
|
|
312
|
+
### System And Persisted Preference
|
|
313
|
+
|
|
314
|
+
`mode` is always the rendered visual mode: `ThemeMode.light` or `ThemeMode.dark`. `preference` is the app or user choice: `ThemeMode.light`, `ThemeMode.dark`, or `"system"`. Use `preference` and `default-preference` when your UI needs a system option; keep using `mode`, `default-mode`, and `on-mode-change` when you only need concrete light/dark compatibility.
|
|
315
|
+
|
|
316
|
+
```vue
|
|
317
|
+
<script setup lang="ts">
|
|
318
|
+
import { Button, ButtonVariant, ThemeMode, useThemeMode } from "@marwes-ui/vue"
|
|
319
|
+
|
|
320
|
+
const { mode, preference, systemMode, isSystem, setPreference, setMode } = useThemeMode()
|
|
321
|
+
</script>
|
|
322
|
+
|
|
323
|
+
<template>
|
|
324
|
+
<p>Rendering {{ mode }}; preference is {{ preference }}; system is {{ systemMode }}.</p>
|
|
325
|
+
<Button :variant="ButtonVariant.secondary" @click="setPreference('system')">
|
|
326
|
+
Use system
|
|
327
|
+
</Button>
|
|
328
|
+
<Button :variant="ButtonVariant.secondary" @click="setMode(ThemeMode.dark)">
|
|
329
|
+
Use dark
|
|
330
|
+
</Button>
|
|
331
|
+
<span v-if="isSystem">Following system</span>
|
|
332
|
+
</template>
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
```vue
|
|
336
|
+
<script setup lang="ts">
|
|
337
|
+
import { MarwesProvider } from "@marwes-ui/vue"
|
|
338
|
+
import ThemeMenu from "./theme-menu.vue"
|
|
339
|
+
</script>
|
|
340
|
+
|
|
341
|
+
<template>
|
|
342
|
+
<MarwesProvider default-preference="system" storage-key="marwes-theme" enable-system>
|
|
343
|
+
<ThemeMenu />
|
|
344
|
+
<AppShell />
|
|
345
|
+
</MarwesProvider>
|
|
346
|
+
</template>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`useThemeMode().mode` never returns `"system"`; it is always the concrete mode Marwes rendered. `useThemeMode().preference` returns the active app/user preference, and `setPreference("system")` is the system-capable setter. `setMode(mode)` remains a concrete light/dark convenience.
|
|
350
|
+
|
|
351
|
+
`storage-key` is opt-in and defaults to `false`. Storage reads and writes are failure-safe, so private browsing or unavailable storage will fall back to normal provider state. `enable-system` defaults to `true`; set it to `false` to avoid `matchMedia` detection and resolve `"system"` through the light fallback.
|
|
352
|
+
|
|
353
|
+
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.
|
|
354
|
+
|
|
312
355
|
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:
|
|
313
356
|
|
|
314
357
|
```vue
|
package/dist/index.d.ts
CHANGED
|
@@ -1,40 +1,59 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
2
|
import { ComputedRef, VNodeChild } from 'vue';
|
|
3
3
|
import * as _marwes_ui_core from '@marwes-ui/core';
|
|
4
|
-
import { ThemeInput, ThemeMode, FontLoadingConfig, ResolvedTheme, ButtonOptions, InputOptions, SelectOptions, SelectAppearance as SelectAppearance$1, SelectOption as SelectOption$1, TextareaOptions, RichTextOptions, InputOtpOptions, CurrencyCode, iconRegistry, IconSize, IconStrokeWidth, AvatarOptions, CheckboxProps as CheckboxProps$1, DividerOptions, DividerSize, DividerOrientation, SpinnerOptions, SpacingOptions, SpacingSize, HeadingOptions, HeadingSize, ParagraphOptions, ParagraphSize, ToastOptions, ToastVariant, ToastIntent, ToastPlacement, TooltipOptions, IconNameType, RadioOptions, BadgeOptions, SwitchOptions, SliderOptions, TabOptions, DialogOptions } from '@marwes-ui/core';
|
|
5
|
-
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, ToneName, createFontStack, mwAvailableFonts, mwFontFallbacks, mwGoogleFontFamilies, mwStyledTheme, mwThemeVarNames, mwThemeVars, mwVar } from '@marwes-ui/core';
|
|
4
|
+
import { ThemeInput, ThemePreference, ThemeMode, FontLoadingConfig, ResolvedTheme, ButtonOptions, InputOptions, SelectOptions, SelectAppearance as SelectAppearance$1, SelectOption as SelectOption$1, TextareaOptions, RichTextOptions, InputOtpOptions, CurrencyCode, iconRegistry, IconSize, IconStrokeWidth, AvatarOptions, CheckboxProps as CheckboxProps$1, DividerOptions, DividerSize, DividerOrientation, SpinnerOptions, SpacingOptions, SpacingSize, HeadingOptions, HeadingSize, ParagraphOptions, ParagraphSize, ToastOptions, ToastVariant, ToastIntent, ToastPlacement, TooltipOptions, IconNameType, RadioOptions, BadgeOptions, SwitchOptions, SliderOptions, TabOptions, DialogOptions } from '@marwes-ui/core';
|
|
5
|
+
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';
|
|
6
6
|
|
|
7
7
|
type MarwesProviderProps = {
|
|
8
8
|
theme?: ThemeInput;
|
|
9
|
+
defaultPreference?: ThemePreference;
|
|
10
|
+
preference?: ThemePreference;
|
|
9
11
|
defaultMode?: ThemeMode;
|
|
10
12
|
mode?: ThemeMode;
|
|
11
13
|
fontLoading?: FontLoadingConfig;
|
|
14
|
+
onPreferenceChange?: (preference: ThemePreference) => void;
|
|
12
15
|
onModeChange?: (mode: ThemeMode) => void;
|
|
16
|
+
storageKey?: string | false;
|
|
17
|
+
enableSystem?: boolean;
|
|
13
18
|
};
|
|
14
19
|
declare const MarwesProvider: vue.DefineComponent<{
|
|
15
20
|
mode?: any;
|
|
16
21
|
theme?: any;
|
|
22
|
+
defaultPreference?: any;
|
|
23
|
+
preference?: any;
|
|
17
24
|
defaultMode?: any;
|
|
18
25
|
fontLoading?: any;
|
|
26
|
+
onPreferenceChange?: any;
|
|
19
27
|
onModeChange?: any;
|
|
28
|
+
storageKey?: any;
|
|
29
|
+
enableSystem?: any;
|
|
20
30
|
}, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
21
31
|
[key: string]: any;
|
|
22
32
|
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{
|
|
23
33
|
mode?: any;
|
|
24
34
|
theme?: any;
|
|
35
|
+
defaultPreference?: any;
|
|
36
|
+
preference?: any;
|
|
25
37
|
defaultMode?: any;
|
|
26
38
|
fontLoading?: any;
|
|
39
|
+
onPreferenceChange?: any;
|
|
27
40
|
onModeChange?: any;
|
|
41
|
+
storageKey?: any;
|
|
42
|
+
enableSystem?: any;
|
|
28
43
|
}> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
29
44
|
|
|
30
45
|
declare function useTheme(): ResolvedTheme;
|
|
31
46
|
|
|
32
47
|
type ThemeModeContextValue = {
|
|
33
48
|
mode: ComputedRef<ThemeMode>;
|
|
49
|
+
preference: ComputedRef<ThemePreference>;
|
|
50
|
+
systemMode: ComputedRef<ThemeMode>;
|
|
34
51
|
setMode: (mode: ThemeMode) => void;
|
|
52
|
+
setPreference: (preference: ThemePreference) => void;
|
|
35
53
|
toggleMode: () => void;
|
|
36
54
|
isDark: ComputedRef<boolean>;
|
|
37
55
|
isLight: ComputedRef<boolean>;
|
|
56
|
+
isSystem: ComputedRef<boolean>;
|
|
38
57
|
};
|
|
39
58
|
declare function useThemeMode(): ThemeModeContextValue;
|
|
40
59
|
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import '@marwes-ui/presets/firstEdition/styles.css';
|
|
2
|
-
import { ThemeMode, resolveThemeInput, resolveIconSize, resolveIconStrokeWidth, iconRegistry, resolveIconA11y, createSpinnerRecipe, createButtonRecipe, ButtonAction, ButtonVariant, IconName, createInputRecipe, createSelectRecipe, resolveSelectMode, createTextareaRecipe, createRichTextRecipe, resolveRichTextAllowedFormats, normalizeRichTextHtml, isRichTextHtmlEmpty, paragraphRecipe, buildInputFieldA11yIds, sanitizeInputOtpValue, createInputOtpRecipe, buildRichTextFieldA11yIds, buildCurrencyHelperText, getCurrencySymbol, sanitizeCurrencyValue, createAvatarRecipe, createPurposeSemanticAttributes, checkboxRecipe, buildCheckboxFieldA11yIds, buildCheckboxGroupFieldA11yIds, createDividerRecipe, createSpacingRecipe, createCardRecipe, createToastRecipe, createTooltipRecipe, createAccordionRecipe, buildAccordionFieldA11yIds, radioRecipe, buildRadioGroupFieldA11yIds, createBadgeRecipe, createSwitchRecipe, buildSwitchFieldA11yIds, createSliderRecipe, buildSliderFieldA11yIds, createTabRecipe, resolveTabValue, buildTabGroupA11yIds, createDialogRecipe, themeToCSSVars, richTextCommandByFormat, headingRecipe, moveTabSelection, extractUsedWeights, extractFontFamilyName, shouldLoadGoogleFont, buildGoogleFontsUrl } from '@marwes-ui/core';
|
|
2
|
+
import { ThemeMode, resolveThemePreference, resolveThemeInput, resolveIconSize, resolveIconStrokeWidth, iconRegistry, resolveIconA11y, createSpinnerRecipe, createButtonRecipe, ButtonAction, ButtonVariant, IconName, createInputRecipe, createSelectRecipe, resolveSelectMode, createTextareaRecipe, createRichTextRecipe, resolveRichTextAllowedFormats, normalizeRichTextHtml, isRichTextHtmlEmpty, paragraphRecipe, buildInputFieldA11yIds, sanitizeInputOtpValue, createInputOtpRecipe, buildRichTextFieldA11yIds, buildCurrencyHelperText, getCurrencySymbol, sanitizeCurrencyValue, createAvatarRecipe, createPurposeSemanticAttributes, checkboxRecipe, buildCheckboxFieldA11yIds, buildCheckboxGroupFieldA11yIds, createDividerRecipe, createSpacingRecipe, createCardRecipe, createToastRecipe, createTooltipRecipe, createAccordionRecipe, buildAccordionFieldA11yIds, radioRecipe, buildRadioGroupFieldA11yIds, createBadgeRecipe, createSwitchRecipe, buildSwitchFieldA11yIds, createSliderRecipe, buildSliderFieldA11yIds, createTabRecipe, resolveTabValue, buildTabGroupA11yIds, createDialogRecipe, isThemePreference, nextThemeMode, themeToCSSVars, richTextCommandByFormat, headingRecipe, moveTabSelection, extractUsedWeights, extractFontFamilyName, shouldLoadGoogleFont, buildGoogleFontsUrl } from '@marwes-ui/core';
|
|
3
3
|
export { AvatarSize, AvatarType, BadgeVariant, ButtonAction, ButtonSize, ButtonVariant, IconName, Spacings, SwitchSize, ThemeMode, createFontStack, mwAvailableFonts, mwFontFallbacks, mwGoogleFontFamilies, mwStyledTheme, mwThemeVarNames, mwThemeVars, mwVar } from '@marwes-ui/core';
|
|
4
|
-
import { defineComponent, ref, computed, onMounted, watch, provide, h, useAttrs, onBeforeUnmount, shallowRef, Fragment, nextTick, Teleport, inject } from 'vue';
|
|
4
|
+
import { defineComponent, ref, computed, onMounted, onUnmounted, watch, provide, h, useAttrs, onBeforeUnmount, shallowRef, Fragment, nextTick, Teleport, inject } from 'vue';
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
7
|
|
|
@@ -61,32 +61,99 @@ function loadThemeFonts(theme, config = "auto") {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
var mediaQuery = "(prefers-color-scheme: dark)";
|
|
65
|
+
function getSystemThemeMode() {
|
|
66
|
+
if (typeof window === "undefined") return ThemeMode.light;
|
|
67
|
+
return window.matchMedia?.(mediaQuery).matches ? ThemeMode.dark : ThemeMode.light;
|
|
68
|
+
}
|
|
69
|
+
function subscribeToSystemThemeMode(onChange) {
|
|
70
|
+
if (typeof window === "undefined" || !window.matchMedia) return () => {
|
|
71
|
+
};
|
|
72
|
+
const media = window.matchMedia(mediaQuery);
|
|
73
|
+
const listener = (event) => {
|
|
74
|
+
onChange(event.matches ? ThemeMode.dark : ThemeMode.light);
|
|
75
|
+
};
|
|
76
|
+
if (media.addEventListener) {
|
|
77
|
+
media.addEventListener("change", listener);
|
|
78
|
+
} else {
|
|
79
|
+
media.addListener?.(listener);
|
|
80
|
+
}
|
|
81
|
+
return () => {
|
|
82
|
+
if (media.removeEventListener) {
|
|
83
|
+
media.removeEventListener("change", listener);
|
|
84
|
+
} else {
|
|
85
|
+
media.removeListener?.(listener);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function readStoredThemePreference(storageKey) {
|
|
90
|
+
if (!storageKey || typeof window === "undefined") return void 0;
|
|
91
|
+
try {
|
|
92
|
+
const value = window.localStorage.getItem(storageKey);
|
|
93
|
+
return isThemePreference(value) ? value : void 0;
|
|
94
|
+
} catch {
|
|
95
|
+
return void 0;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function writeStoredThemePreference(storageKey, preference) {
|
|
99
|
+
if (!storageKey || typeof window === "undefined") return;
|
|
100
|
+
try {
|
|
101
|
+
window.localStorage.setItem(storageKey, preference);
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
64
105
|
|
|
65
106
|
// src/provider/marwes-provider.ts
|
|
66
107
|
var MarwesProvider = defineComponent({
|
|
67
108
|
name: "MarwesProvider",
|
|
68
|
-
props: [
|
|
109
|
+
props: [
|
|
110
|
+
"theme",
|
|
111
|
+
"defaultPreference",
|
|
112
|
+
"preference",
|
|
113
|
+
"defaultMode",
|
|
114
|
+
"mode",
|
|
115
|
+
"fontLoading",
|
|
116
|
+
"onPreferenceChange",
|
|
117
|
+
"onModeChange",
|
|
118
|
+
"storageKey",
|
|
119
|
+
"enableSystem"
|
|
120
|
+
],
|
|
69
121
|
setup(rawProps, { slots }) {
|
|
70
122
|
const props = rawProps;
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
123
|
+
const internalPreference = ref(
|
|
124
|
+
props.defaultPreference ?? props.defaultMode ?? ThemeMode.light
|
|
125
|
+
);
|
|
126
|
+
const systemMode = ref(
|
|
127
|
+
props.enableSystem === false ? ThemeMode.light : getSystemThemeMode()
|
|
74
128
|
);
|
|
75
|
-
const
|
|
76
|
-
|
|
129
|
+
const enableSystem = computed(() => props.enableSystem !== false);
|
|
130
|
+
const storageKey = computed(() => props.storageKey ?? false);
|
|
131
|
+
const isPreferenceControlled = computed(
|
|
132
|
+
() => props.preference !== void 0 || props.mode !== void 0 || props.theme?.mode !== void 0
|
|
133
|
+
);
|
|
134
|
+
const activePreference = computed(
|
|
135
|
+
() => props.preference ?? props.mode ?? props.theme?.mode ?? internalPreference.value
|
|
136
|
+
);
|
|
137
|
+
const activeMode = computed(
|
|
138
|
+
() => resolveThemePreference(activePreference.value, systemMode.value)
|
|
77
139
|
);
|
|
78
140
|
const resolved = computed(
|
|
79
141
|
() => resolveThemeInput({ ...props.theme ?? {}, mode: activeMode.value })
|
|
80
142
|
);
|
|
81
143
|
const rootRef = ref(null);
|
|
82
|
-
function
|
|
83
|
-
if (!
|
|
84
|
-
|
|
144
|
+
function setPreference(nextPreference) {
|
|
145
|
+
if (!isPreferenceControlled.value) {
|
|
146
|
+
internalPreference.value = nextPreference;
|
|
85
147
|
}
|
|
148
|
+
writeStoredThemePreference(storageKey.value, nextPreference);
|
|
149
|
+
props.onPreferenceChange?.(nextPreference);
|
|
150
|
+
}
|
|
151
|
+
function setMode(nextMode) {
|
|
152
|
+
setPreference(nextMode);
|
|
86
153
|
props.onModeChange?.(nextMode);
|
|
87
154
|
}
|
|
88
155
|
function toggleMode() {
|
|
89
|
-
setMode(activeMode.value
|
|
156
|
+
setMode(nextThemeMode(activeMode.value));
|
|
90
157
|
}
|
|
91
158
|
function syncThemeToRuntime() {
|
|
92
159
|
if (rootRef.value) {
|
|
@@ -94,12 +161,41 @@ var MarwesProvider = defineComponent({
|
|
|
94
161
|
}
|
|
95
162
|
loadThemeFonts(resolved.value, props.fontLoading ?? "auto");
|
|
96
163
|
}
|
|
97
|
-
|
|
164
|
+
let unsubscribeSystemThemeMode;
|
|
165
|
+
function syncSystemThemeSubscription() {
|
|
166
|
+
unsubscribeSystemThemeMode?.();
|
|
167
|
+
unsubscribeSystemThemeMode = void 0;
|
|
168
|
+
if (!enableSystem.value) {
|
|
169
|
+
systemMode.value = ThemeMode.light;
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
systemMode.value = getSystemThemeMode();
|
|
173
|
+
if (activePreference.value === "system") {
|
|
174
|
+
unsubscribeSystemThemeMode = subscribeToSystemThemeMode((nextMode) => {
|
|
175
|
+
systemMode.value = nextMode;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
onMounted(() => {
|
|
180
|
+
const storedPreference = readStoredThemePreference(storageKey.value);
|
|
181
|
+
if (storedPreference !== void 0 && !isPreferenceControlled.value) {
|
|
182
|
+
internalPreference.value = storedPreference;
|
|
183
|
+
}
|
|
184
|
+
syncSystemThemeSubscription();
|
|
185
|
+
syncThemeToRuntime();
|
|
186
|
+
});
|
|
187
|
+
onUnmounted(() => {
|
|
188
|
+
unsubscribeSystemThemeMode?.();
|
|
189
|
+
});
|
|
98
190
|
watch(resolved, syncThemeToRuntime);
|
|
191
|
+
watch([activePreference, enableSystem], syncSystemThemeSubscription);
|
|
99
192
|
provide(marwesContextKey, {
|
|
100
193
|
theme: resolved,
|
|
101
194
|
mode: activeMode,
|
|
195
|
+
preference: activePreference,
|
|
196
|
+
systemMode: computed(() => systemMode.value),
|
|
102
197
|
setMode,
|
|
198
|
+
setPreference,
|
|
103
199
|
toggleMode
|
|
104
200
|
});
|
|
105
201
|
return () => h(
|
|
@@ -128,10 +224,14 @@ function useThemeMode() {
|
|
|
128
224
|
}
|
|
129
225
|
return {
|
|
130
226
|
mode: context.mode,
|
|
227
|
+
preference: context.preference,
|
|
228
|
+
systemMode: context.systemMode,
|
|
131
229
|
setMode: context.setMode,
|
|
230
|
+
setPreference: context.setPreference,
|
|
132
231
|
toggleMode: context.toggleMode,
|
|
133
232
|
isDark: computed(() => context.mode.value === ThemeMode.dark),
|
|
134
|
-
isLight: computed(() => context.mode.value === ThemeMode.light)
|
|
233
|
+
isLight: computed(() => context.mode.value === ThemeMode.light),
|
|
234
|
+
isSystem: computed(() => context.preference.value === "system")
|
|
135
235
|
};
|
|
136
236
|
}
|
|
137
237
|
|