@marwes-ui/vue 1.0.0 → 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 CHANGED
@@ -1,13 +1,13 @@
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-light.png" width="100%">
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>
7
7
 
8
8
  # Marwes Design System - Vue
9
9
 
10
- **Vue 3 components with first edition styling, typed theme tokens, accessibility contracts, and AI-readable semantics built in.**
10
+ **Vue 3 components with default Marwes styling, typed theme tokens, accessibility contracts, and AI-readable semantics built in.**
11
11
 
12
12
  Vue 3.4+ • TypeScript-first • Default CSS included • ThemeInput • Google Fonts DX • Purpose components
13
13
 
@@ -21,7 +21,7 @@ Vue 3.4+ • TypeScript-first • Default CSS included • ThemeInput • Google
21
21
 
22
22
  Marwes gives Vue apps a ready design-system base without requiring custom CSS setup or local component forks.
23
23
 
24
- - **One package for Vue apps**: components, provider, default first edition CSS, theme helpers, and typed props.
24
+ - **One package for Vue apps**: components, provider, default preset CSS, theme helpers, and typed props.
25
25
  - **Vue-native binding**: common controls support `v-model` while preserving Marwes semantic contracts.
26
26
  - **Consequential theming**: a `ThemeInput` object changes colors, fonts, radius, density, typography, and component visuals through shared CSS variables.
27
27
  - **Purpose components**: `SubmitButton`, `CancelButton`, and `DestructiveButton` make intent machine-readable so tests, audits, and AI agents can handle actions safely.
@@ -36,7 +36,7 @@ For a Vue app, install this package first. It includes the Vue adapter, loads th
36
36
  | `@marwes-ui/vue` | You are building a Vue app. |
37
37
  | `@marwes-ui/react` | You are building a React app instead. |
38
38
  | `@marwes-ui/core` | You are building adapters, tests, tooling, or framework-agnostic integrations. |
39
- | `@marwes-ui/presets` | You need standalone first edition CSS or preset theme exports. |
39
+ | `@marwes-ui/presets` | You need standalone preset CSS or preset theme exports. |
40
40
 
41
41
  This split keeps installation simple for app teams while giving humans and AI agents clear package boundaries: adapters render, core defines contracts, presets style.
42
42
 
@@ -46,14 +46,14 @@ This split keeps installation simple for app teams while giving humans and AI ag
46
46
  pnpm add @marwes-ui/vue vue
47
47
  ```
48
48
 
49
- No preset CSS import is needed. `@marwes-ui/vue` depends on `@marwes-ui/presets` and loads the first edition CSS automatically.
49
+ No preset CSS import is needed. `@marwes-ui/vue` depends on `@marwes-ui/presets` and loads the default Marwes CSS automatically.
50
50
 
51
51
  ## Quick Start
52
52
 
53
53
  ```vue
54
54
  <script setup lang="ts">
55
55
  import { ref } from "vue"
56
- import { Button, Checkbox, Input, MarwesProvider, SubmitButton } from "@marwes-ui/vue"
56
+ import { Button, ButtonVariant, Checkbox, Input, MarwesProvider, SubmitButton } from "@marwes-ui/vue"
57
57
 
58
58
  const email = ref("")
59
59
  const subscribed = ref(false)
@@ -63,7 +63,7 @@ const subscribed = ref(false)
63
63
  <MarwesProvider>
64
64
  <Input v-model="email" placeholder="Email" ariaLabel="Email" />
65
65
  <Checkbox v-model="subscribed" ariaLabel="Subscribe" />
66
- <Button variant="secondary">Preview</Button>
66
+ <Button :variant="ButtonVariant.secondary">Preview</Button>
67
67
  <SubmitButton>Save</SubmitButton>
68
68
  </MarwesProvider>
69
69
  </template>
@@ -75,7 +75,7 @@ Marwes components pick up provider tokens automatically. Your own Vue styling ca
75
75
 
76
76
  ```vue
77
77
  <script setup lang="ts">
78
- import { Button, MarwesProvider, mwThemeVars } from "@marwes-ui/vue"
78
+ import { Button, ButtonVariant, MarwesProvider, mwThemeVars } from "@marwes-ui/vue"
79
79
 
80
80
  const panelStyle = {
81
81
  padding: mwThemeVars.spacing.sp24,
@@ -90,7 +90,7 @@ const panelStyle = {
90
90
  <main class="app-shell">
91
91
  <aside class="primary-callout">Launch workspace</aside>
92
92
  <section class="feature-panel" :style="panelStyle">
93
- <Button variant="primary">Save</Button>
93
+ <Button :variant="ButtonVariant.primary">Save</Button>
94
94
  </section>
95
95
  </main>
96
96
  </MarwesProvider>
@@ -136,7 +136,9 @@ import {
136
136
  StatusBadge,
137
137
  SubmitButton,
138
138
  type ButtonProps,
139
+ type HeadingProps,
139
140
  type InputFieldProps,
141
+ type ParagraphProps,
140
142
  } from "@marwes-ui/vue"
141
143
 
142
144
  const primaryAction: ButtonProps = {
@@ -152,6 +154,14 @@ const emailField: InputFieldProps = {
152
154
  placeholder: "you@example.com",
153
155
  },
154
156
  }
157
+
158
+ const titleProps: HeadingProps = {
159
+ size: "h2",
160
+ }
161
+
162
+ const descriptionProps: ParagraphProps = {
163
+ size: "md",
164
+ }
155
165
  </script>
156
166
 
157
167
  <template>
@@ -159,8 +169,8 @@ const emailField: InputFieldProps = {
159
169
  <template #title>Project setup</template>
160
170
  <StatusBadge :variant="BadgeVariant.success">Ready</StatusBadge>
161
171
  <Spacer :spacing="Spacings.sp16" />
162
- <H1 size="h2">Launch workspace</H1>
163
- <Paragraph size="md">
172
+ <H1 v-bind="titleProps">Launch workspace</H1>
173
+ <Paragraph v-bind="descriptionProps">
164
174
  Components share theme tokens, typed variants, spacing, and semantic metadata.
165
175
  </Paragraph>
166
176
  <InputField v-bind="emailField" />
@@ -223,11 +233,11 @@ Typed tokens and helpers:
223
233
 
224
234
  ## Theme In Seconds
225
235
 
226
- First edition is the default. Pass `theme` only when a brand or design file needs to change the baseline.
236
+ The default Marwes theme is already active. If you already have a good-looking design library or brand system, pass a small typed `ThemeInput` override instead of rebuilding component CSS.
227
237
 
228
238
  ```vue
229
239
  <script setup lang="ts">
230
- import { MarwesProvider, mwAvailableFonts } from "@marwes-ui/vue"
240
+ import { MarwesProvider, mwAvailableFonts, type ThemeInput } from "@marwes-ui/vue"
231
241
 
232
242
  const brandTheme = {
233
243
  color: {
@@ -251,7 +261,7 @@ const brandTheme = {
251
261
  radius: 10,
252
262
  density: "comfortable",
253
263
  },
254
- }
264
+ } satisfies ThemeInput
255
265
  </script>
256
266
 
257
267
  <template>
@@ -263,6 +273,133 @@ const brandTheme = {
263
273
 
264
274
  The provider resolves `ThemeInput` into `--mw-*` CSS variables. Preset CSS consumes those variables across the full component system.
265
275
 
276
+ Marwes is designed to look great from the beginning. Start with the default preset, then override only the colors, fonts, radius, or density that belong to your product. Unspecified values keep the polished Marwes defaults.
277
+
278
+ ## Light And Dark Mode
279
+
280
+ MarwesProvider can own the active mode for the app. Use `ThemeMode.light` and `ThemeMode.dark` instead of string literals, then read or change the active mode with `useThemeMode()` anywhere under the provider. Every component under the provider receives the matching `--mw-*` variables and `mw-theme--light` / `mw-theme--dark` class.
281
+
282
+ ```vue
283
+ <script setup lang="ts">
284
+ import { Button, ButtonVariant, ThemeMode, useThemeMode } from "@marwes-ui/vue"
285
+
286
+ const { mode, toggleMode } = useThemeMode()
287
+ </script>
288
+
289
+ <template>
290
+ <Button :variant="ButtonVariant.secondary" @click="toggleMode">
291
+ Use {{ mode === ThemeMode.dark ? ThemeMode.light : ThemeMode.dark }} mode
292
+ </Button>
293
+ </template>
294
+ ```
295
+
296
+ ```vue
297
+ <script setup lang="ts">
298
+ import { MarwesProvider, ThemeMode } from "@marwes-ui/vue"
299
+ import ThemeToggle from "./theme-toggle.vue"
300
+ </script>
301
+
302
+ <template>
303
+ <MarwesProvider :default-mode="ThemeMode.light">
304
+ <ThemeToggle />
305
+ <AppShell />
306
+ </MarwesProvider>
307
+ </template>
308
+ ```
309
+
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
+
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
+
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:
356
+
357
+ ```vue
358
+ <script setup lang="ts">
359
+ import { ref } from "vue"
360
+ import { MarwesProvider, ThemeMode, type ThemeInput } from "@marwes-ui/vue"
361
+ import ThemeToggle from "./theme-toggle.vue"
362
+
363
+ const mode = ref<ThemeMode>(ThemeMode.light)
364
+
365
+ const themeByMode = {
366
+ [ThemeMode.light]: {
367
+ color: {
368
+ primary: "#2457FF",
369
+ background: "#F8FAFC",
370
+ surface: "#FFFFFF",
371
+ text: "#111827",
372
+ border: "#D1D5DB",
373
+ focus: "#2457FF",
374
+ },
375
+ },
376
+ [ThemeMode.dark]: {
377
+ color: {
378
+ primary: "#8BA2FF",
379
+ background: "#0B1020",
380
+ surface: "#111827",
381
+ text: "#F8FAFC",
382
+ border: "#334155",
383
+ focus: "#93C5FD",
384
+ },
385
+ },
386
+ } satisfies Record<ThemeMode, ThemeInput>
387
+
388
+ function setMode(nextMode: ThemeMode) {
389
+ mode.value = nextMode
390
+ }
391
+ </script>
392
+
393
+ <template>
394
+ <MarwesProvider :mode="mode" :theme="themeByMode[mode]" :on-mode-change="setMode">
395
+ <ThemeToggle />
396
+ <AppShell />
397
+ </MarwesProvider>
398
+ </template>
399
+ ```
400
+
401
+ Mode-specific defaults fill every omitted token, so each override can stay small. `:theme="{ mode: ThemeMode.dark }"` is still enough for the default dark baseline.
402
+
266
403
  ## Custom Styling Tokens
267
404
 
268
405
  `MarwesProvider` resolves `ThemeInput` into `--mw-*` CSS variables. Marwes components and preset CSS consume those variables automatically. The custom styling token helpers let app-owned Vue styles, script-driven style objects, and build-time config use the same provider-scoped values instead of hard-coding colors, spacing, radius, typography, or duplicated `var(...)` strings.
@@ -331,14 +468,14 @@ Most Google Font use cases only need `mwAvailableFonts`; no `fontLoading` prop i
331
468
 
332
469
  ```vue
333
470
  <script setup lang="ts">
334
- import { mwAvailableFonts } from "@marwes-ui/vue"
471
+ import { MarwesProvider, mwAvailableFonts, type ThemeInput } from "@marwes-ui/vue"
335
472
 
336
473
  const theme = {
337
474
  font: {
338
475
  primary: mwAvailableFonts.Poppins,
339
476
  secondary: mwAvailableFonts.Lora,
340
477
  },
341
- }
478
+ } satisfies ThemeInput
342
479
  </script>
343
480
 
344
481
  <template>
@@ -402,16 +539,18 @@ Example:
402
539
 
403
540
  ```vue
404
541
  <script setup lang="ts">
405
- import { InputField } from "@marwes-ui/vue"
542
+ import { InputField, type InputFieldProps } from "@marwes-ui/vue"
543
+
544
+ const receiptEmailField = {
545
+ label: "Email",
546
+ helperText: "Used for receipts.",
547
+ error: "Enter a valid email.",
548
+ input: { type: "email", placeholder: "you@example.com" },
549
+ } satisfies InputFieldProps
406
550
  </script>
407
551
 
408
552
  <template>
409
- <InputField
410
- label="Email"
411
- helperText="Used for receipts."
412
- error="Enter a valid email."
413
- :input="{ type: 'email', placeholder: 'you@example.com' }"
414
- />
553
+ <InputField v-bind="receiptEmailField" />
415
554
  </template>
416
555
  ```
417
556
 
@@ -434,7 +573,7 @@ The important part is that the same label, helper, error, and invalid contract i
434
573
  ## Package Boundaries
435
574
 
436
575
  - `@marwes-ui/core` owns recipes, theme resolution, a11y mapping, and semantic metadata.
437
- - `@marwes-ui/presets` owns first edition CSS.
576
+ - `@marwes-ui/presets` owns default preset CSS.
438
577
  - `@marwes-ui/vue` owns Vue rendering and provider behavior.
439
578
 
440
579
  ## Scripts
package/dist/index.d.ts CHANGED
@@ -1,28 +1,62 @@
1
1
  import * as vue from 'vue';
2
- import { VNodeChild } from 'vue';
2
+ import { ComputedRef, VNodeChild } from 'vue';
3
3
  import * as _marwes_ui_core from '@marwes-ui/core';
4
- import { ThemeInput, FontLoadingConfig, ThemeMode, 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;
11
+ defaultMode?: ThemeMode;
12
+ mode?: ThemeMode;
9
13
  fontLoading?: FontLoadingConfig;
14
+ onPreferenceChange?: (preference: ThemePreference) => void;
10
15
  onModeChange?: (mode: ThemeMode) => void;
16
+ storageKey?: string | false;
17
+ enableSystem?: boolean;
11
18
  };
12
19
  declare const MarwesProvider: vue.DefineComponent<{
20
+ mode?: any;
13
21
  theme?: any;
22
+ defaultPreference?: any;
23
+ preference?: any;
24
+ defaultMode?: any;
14
25
  fontLoading?: any;
26
+ onPreferenceChange?: any;
15
27
  onModeChange?: any;
28
+ storageKey?: any;
29
+ enableSystem?: any;
16
30
  }, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
17
31
  [key: string]: any;
18
32
  }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<{
33
+ mode?: any;
19
34
  theme?: any;
35
+ defaultPreference?: any;
36
+ preference?: any;
37
+ defaultMode?: any;
20
38
  fontLoading?: any;
39
+ onPreferenceChange?: any;
21
40
  onModeChange?: any;
41
+ storageKey?: any;
42
+ enableSystem?: any;
22
43
  }> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
23
44
 
24
45
  declare function useTheme(): ResolvedTheme;
25
46
 
47
+ type ThemeModeContextValue = {
48
+ mode: ComputedRef<ThemeMode>;
49
+ preference: ComputedRef<ThemePreference>;
50
+ systemMode: ComputedRef<ThemeMode>;
51
+ setMode: (mode: ThemeMode) => void;
52
+ setPreference: (preference: ThemePreference) => void;
53
+ toggleMode: () => void;
54
+ isDark: ComputedRef<boolean>;
55
+ isLight: ComputedRef<boolean>;
56
+ isSystem: ComputedRef<boolean>;
57
+ };
58
+ declare function useThemeMode(): ThemeModeContextValue;
59
+
26
60
  type ButtonProps = ButtonOptions & {
27
61
  onClick?: (event: MouseEvent) => void;
28
62
  className?: string;
@@ -2853,4 +2887,4 @@ declare const InfoDialog: vue.DefineComponent<{
2853
2887
  onAcknowledge?: any;
2854
2888
  }> & Readonly<{}>, {}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
2855
2889
 
2856
- export { Accordion, AccordionField, type AccordionFieldItem, type AccordionFieldProps, type AccordionProps, Avatar, AvatarBadge, type AvatarBadgeProps, AvatarGroup, type AvatarGroupItem, type AvatarGroupProps, type AvatarProps, Badge, BadgeGroup, type BadgeGroupProps, type BadgeProps, BrightnessSlider, type BrightnessSliderProps, Button, type ButtonProps, ButtonSpinner, type ButtonSpinnerProps, CancelButton, type CancelButtonProps, Card, type CardProps, Checkbox, CheckboxField, type CheckboxFieldProps, CheckboxGroupField, type CheckboxGroupFieldOption, type CheckboxGroupFieldProps, type CheckboxProps, CloseButton, type CloseButtonProps, ConfirmButton, type ConfirmButtonProps, ConfirmDialog, type ConfirmDialogProps, ContentTabs, type ContentTabsProps, CopyButton, type CopyButtonProps, CreateButton, type CreateButtonProps, CurrencyField, type CurrencyFieldProps, DateOfBirthField, type DateOfBirthFieldProps, DestructiveButton, type DestructiveButtonProps, DestructiveDialog, type DestructiveDialogProps, Dialog, type DialogFooterControls, DialogModal, type DialogModalProps, type DialogProps, Divider, type DividerProps, DownloadButton, type DownloadButtonProps, DropdownButton, type DropdownButtonProps, DropdownField, type DropdownFieldProps, EditButton, type EditButtonProps, EmailField, type EmailFieldProps, EmptyStateSpinner, type EmptyStateSpinnerProps, ErrorToast, type ErrorToastProps, FAQAccordion, type FAQAccordionProps, FeatureToggle, type FeatureToggleProps, FilterButton, type FilterButtonProps, H1, H2, H3, type HeadingLevel, type HeadingProps, Icon, type IconProps, InfoDialog, type InfoDialogProps, InfoToast, type InfoToastProps, Input, InputField, type InputFieldProps, InputOtp, type InputOtpProps, type InputProps, LinkButton, type LinkButtonProps, type ManagedToast, MarwesProvider, type MarwesProviderProps, NavigationTabs, type NavigationTabsProps, NotificationBadge, type NotificationBadgeProps, OptionRadioGroup, type OptionRadioGroupProps, Paragraph, type ParagraphProps, PasswordField, type PasswordFieldProps, PermissionSwitch, type PermissionSwitchProps, PhoneField, type PhoneFieldProps, PreferenceSwitch, type PreferenceSwitchProps, PresenceAvatar, type PresenceAvatarProps, PrimaryButton, type PrimaryButtonProps, PriorityBadge, type PriorityBadgeProps, ProductCard, type ProductCardProps, ProfileAvatar, type ProfileAvatarProps, ProfileCard, type ProfileCardProps, Radio, RadioGroupField, type RadioGroupFieldOption, type RadioGroupFieldProps, type RadioProps, RadiusSlider, type RadiusSliderProps, RatingRadioGroup, type RatingRadioGroupProps, RefreshButton, type RefreshButtonProps, RichText, RichTextField, type RichTextFieldProps, type RichTextProps, SaveButton, type SaveButtonProps, SearchButton, type SearchButtonProps, SearchField, type SearchFieldProps, SecondaryButton, type SecondaryButtonProps, SectionsAccordion, type SectionsAccordionProps, Select, type SelectAppearance, SelectField, type SelectFieldProps, type SelectOption, type SelectProps, SettingsAccordion, type SettingsAccordionProps, SettingsTabs, type SettingsTabsProps, type ShowToastOptions, Slider, SliderField, type SliderFieldProps, type SliderProps, SortButton, type SortButtonProps, Spacer, type SpacerProps, Spacing, type SpacingProps, Spinner, type SpinnerProps, StatCard, type StatCardProps, StatusBadge, type StatusBadgeProps, SubmitButton, type SubmitButtonProps, SuccessButton, type SuccessButtonProps, SuccessToast, type SuccessToastProps, Switch, SwitchField, type SwitchFieldProps, type SwitchProps, Tab, TabGroup, type TabGroupItem, type TabGroupProps, TabPanel, type TabPanelProps, type TabProps, TeamAvatarGroup, type TeamAvatarGroupProps, TextButton, type TextButtonProps, Textarea, TextareaField, type TextareaFieldProps, type TextareaProps, Toast, ToastContainer, type ToastContainerProps, type ToastController, type ToastProps, ToastProvider, type ToastProviderProps, Tooltip, TooltipGroup, type TooltipGroupProps, type TooltipProps, URLField, type URLFieldProps, UploadButton, type UploadButtonProps, VerifyButton, type VerifyButtonProps, VolumeSlider, type VolumeSliderProps, WarningToast, type WarningToastProps, YesNoRadioGroup, type YesNoRadioGroupProps, ZipCodeField, type ZipCodeFieldProps, createHeadingComponent, useTheme, useToast };
2890
+ export { Accordion, AccordionField, type AccordionFieldItem, type AccordionFieldProps, type AccordionProps, Avatar, AvatarBadge, type AvatarBadgeProps, AvatarGroup, type AvatarGroupItem, type AvatarGroupProps, type AvatarProps, Badge, BadgeGroup, type BadgeGroupProps, type BadgeProps, BrightnessSlider, type BrightnessSliderProps, Button, type ButtonProps, ButtonSpinner, type ButtonSpinnerProps, CancelButton, type CancelButtonProps, Card, type CardProps, Checkbox, CheckboxField, type CheckboxFieldProps, CheckboxGroupField, type CheckboxGroupFieldOption, type CheckboxGroupFieldProps, type CheckboxProps, CloseButton, type CloseButtonProps, ConfirmButton, type ConfirmButtonProps, ConfirmDialog, type ConfirmDialogProps, ContentTabs, type ContentTabsProps, CopyButton, type CopyButtonProps, CreateButton, type CreateButtonProps, CurrencyField, type CurrencyFieldProps, DateOfBirthField, type DateOfBirthFieldProps, DestructiveButton, type DestructiveButtonProps, DestructiveDialog, type DestructiveDialogProps, Dialog, type DialogFooterControls, DialogModal, type DialogModalProps, type DialogProps, Divider, type DividerProps, DownloadButton, type DownloadButtonProps, DropdownButton, type DropdownButtonProps, DropdownField, type DropdownFieldProps, EditButton, type EditButtonProps, EmailField, type EmailFieldProps, EmptyStateSpinner, type EmptyStateSpinnerProps, ErrorToast, type ErrorToastProps, FAQAccordion, type FAQAccordionProps, FeatureToggle, type FeatureToggleProps, FilterButton, type FilterButtonProps, H1, H2, H3, type HeadingLevel, type HeadingProps, Icon, type IconProps, InfoDialog, type InfoDialogProps, InfoToast, type InfoToastProps, Input, InputField, type InputFieldProps, InputOtp, type InputOtpProps, type InputProps, LinkButton, type LinkButtonProps, type ManagedToast, MarwesProvider, type MarwesProviderProps, NavigationTabs, type NavigationTabsProps, NotificationBadge, type NotificationBadgeProps, OptionRadioGroup, type OptionRadioGroupProps, Paragraph, type ParagraphProps, PasswordField, type PasswordFieldProps, PermissionSwitch, type PermissionSwitchProps, PhoneField, type PhoneFieldProps, PreferenceSwitch, type PreferenceSwitchProps, PresenceAvatar, type PresenceAvatarProps, PrimaryButton, type PrimaryButtonProps, PriorityBadge, type PriorityBadgeProps, ProductCard, type ProductCardProps, ProfileAvatar, type ProfileAvatarProps, ProfileCard, type ProfileCardProps, Radio, RadioGroupField, type RadioGroupFieldOption, type RadioGroupFieldProps, type RadioProps, RadiusSlider, type RadiusSliderProps, RatingRadioGroup, type RatingRadioGroupProps, RefreshButton, type RefreshButtonProps, RichText, RichTextField, type RichTextFieldProps, type RichTextProps, SaveButton, type SaveButtonProps, SearchButton, type SearchButtonProps, SearchField, type SearchFieldProps, SecondaryButton, type SecondaryButtonProps, SectionsAccordion, type SectionsAccordionProps, Select, type SelectAppearance, SelectField, type SelectFieldProps, type SelectOption, type SelectProps, SettingsAccordion, type SettingsAccordionProps, SettingsTabs, type SettingsTabsProps, type ShowToastOptions, Slider, SliderField, type SliderFieldProps, type SliderProps, SortButton, type SortButtonProps, Spacer, type SpacerProps, Spacing, type SpacingProps, Spinner, type SpinnerProps, StatCard, type StatCardProps, StatusBadge, type StatusBadgeProps, SubmitButton, type SubmitButtonProps, SuccessButton, type SuccessButtonProps, SuccessToast, type SuccessToastProps, Switch, SwitchField, type SwitchFieldProps, type SwitchProps, Tab, TabGroup, type TabGroupItem, type TabGroupProps, TabPanel, type TabPanelProps, type TabProps, TeamAvatarGroup, type TeamAvatarGroupProps, TextButton, type TextButtonProps, Textarea, TextareaField, type TextareaFieldProps, type TextareaProps, type ThemeModeContextValue, Toast, ToastContainer, type ToastContainerProps, type ToastController, type ToastProps, ToastProvider, type ToastProviderProps, Tooltip, TooltipGroup, type TooltipGroupProps, type TooltipProps, URLField, type URLFieldProps, UploadButton, type UploadButtonProps, VerifyButton, type VerifyButtonProps, VolumeSlider, type VolumeSliderProps, WarningToast, type WarningToastProps, YesNoRadioGroup, type YesNoRadioGroupProps, ZipCodeField, type ZipCodeFieldProps, createHeadingComponent, useTheme, useThemeMode, useToast };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import '@marwes-ui/presets/firstEdition/styles.css';
2
- import { 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';
3
- export { AvatarSize, AvatarType, BadgeVariant, ButtonAction, ButtonSize, ButtonVariant, IconName, Spacings, SwitchSize, createFontStack, mwAvailableFonts, mwFontFallbacks, mwGoogleFontFamilies, mwStyledTheme, mwThemeVarNames, mwThemeVars, mwVar } from '@marwes-ui/core';
4
- import { defineComponent, computed, ref, onMounted, watch, provide, h, useAttrs, onBeforeUnmount, shallowRef, Fragment, nextTick, Teleport, inject } from 'vue';
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
+ 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, onUnmounted, watch, provide, h, useAttrs, onBeforeUnmount, shallowRef, Fragment, nextTick, Teleport, inject } from 'vue';
5
5
 
6
6
  // src/index.ts
7
7
 
@@ -61,26 +61,142 @@ 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: ["theme", "fontLoading", "onModeChange"],
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 resolved = computed(() => resolveThemeInput(props.theme ?? {}));
123
+ const internalPreference = ref(
124
+ props.defaultPreference ?? props.defaultMode ?? ThemeMode.light
125
+ );
126
+ const systemMode = ref(
127
+ props.enableSystem === false ? ThemeMode.light : getSystemThemeMode()
128
+ );
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)
139
+ );
140
+ const resolved = computed(
141
+ () => resolveThemeInput({ ...props.theme ?? {}, mode: activeMode.value })
142
+ );
72
143
  const rootRef = ref(null);
144
+ function setPreference(nextPreference) {
145
+ if (!isPreferenceControlled.value) {
146
+ internalPreference.value = nextPreference;
147
+ }
148
+ writeStoredThemePreference(storageKey.value, nextPreference);
149
+ props.onPreferenceChange?.(nextPreference);
150
+ }
151
+ function setMode(nextMode) {
152
+ setPreference(nextMode);
153
+ props.onModeChange?.(nextMode);
154
+ }
155
+ function toggleMode() {
156
+ setMode(nextThemeMode(activeMode.value));
157
+ }
73
158
  function syncThemeToRuntime() {
74
159
  if (rootRef.value) {
75
160
  applyThemeToElement(rootRef.value, resolved.value);
76
161
  }
77
162
  loadThemeFonts(resolved.value, props.fontLoading ?? "auto");
78
163
  }
79
- onMounted(syncThemeToRuntime);
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
+ });
80
190
  watch(resolved, syncThemeToRuntime);
191
+ watch([activePreference, enableSystem], syncSystemThemeSubscription);
81
192
  provide(marwesContextKey, {
82
193
  theme: resolved,
83
- onModeChange: props.onModeChange
194
+ mode: activeMode,
195
+ preference: activePreference,
196
+ systemMode: computed(() => systemMode.value),
197
+ setMode,
198
+ setPreference,
199
+ toggleMode
84
200
  });
85
201
  return () => h(
86
202
  "div",
@@ -101,6 +217,23 @@ function useTheme() {
101
217
  }
102
218
  return context.theme.value;
103
219
  }
220
+ function useThemeMode() {
221
+ const context = inject(marwesContextKey, null);
222
+ if (!context) {
223
+ throw new Error("MarwesProvider is missing. Wrap your app in <MarwesProvider />.");
224
+ }
225
+ return {
226
+ mode: context.mode,
227
+ preference: context.preference,
228
+ systemMode: context.systemMode,
229
+ setMode: context.setMode,
230
+ setPreference: context.setPreference,
231
+ toggleMode: context.toggleMode,
232
+ isDark: computed(() => context.mode.value === ThemeMode.dark),
233
+ isLight: computed(() => context.mode.value === ThemeMode.light),
234
+ isSystem: computed(() => context.preference.value === "system")
235
+ };
236
+ }
104
237
 
105
238
  // src/internal/render-utils.ts
106
239
  function mergeClassNames(...parts) {
@@ -6520,6 +6653,6 @@ var InfoDialog = defineComponent({
6520
6653
  }
6521
6654
  });
6522
6655
 
6523
- export { Accordion, AccordionField, Avatar, AvatarBadge, AvatarGroup, Badge, BadgeGroup, BrightnessSlider, Button, ButtonSpinner, CancelButton, Card, Checkbox, CheckboxField, CheckboxGroupField, CloseButton, ConfirmButton, ConfirmDialog, ContentTabs, CopyButton, CreateButton, CurrencyField, DateOfBirthField, DestructiveButton, DestructiveDialog, Dialog, DialogModal, Divider, DownloadButton, DropdownButton, DropdownField, EditButton, EmailField, EmptyStateSpinner, ErrorToast, FAQAccordion, FeatureToggle, FilterButton, H1, H2, H3, Icon, InfoDialog, InfoToast, Input, InputField, InputOtp, LinkButton, MarwesProvider, NavigationTabs, NotificationBadge, OptionRadioGroup, Paragraph, PasswordField, PermissionSwitch, PhoneField, PreferenceSwitch, PresenceAvatar, PrimaryButton, PriorityBadge, ProductCard, ProfileAvatar, ProfileCard, Radio, RadioGroupField, RadiusSlider, RatingRadioGroup, RefreshButton, RichText, RichTextField, SaveButton, SearchButton, SearchField, SecondaryButton, SectionsAccordion, Select, SelectField, SettingsAccordion, SettingsTabs, Slider, SliderField, SortButton, Spacer, Spacing, Spinner, StatCard, StatusBadge, SubmitButton, SuccessButton, SuccessToast, Switch, SwitchField, Tab, TabGroup, TabPanel, TeamAvatarGroup, TextButton, Textarea, TextareaField, Toast, ToastContainer, ToastProvider, Tooltip, TooltipGroup, URLField, UploadButton, VerifyButton, VolumeSlider, WarningToast, YesNoRadioGroup, ZipCodeField, createHeadingComponent, useTheme, useToast };
6656
+ export { Accordion, AccordionField, Avatar, AvatarBadge, AvatarGroup, Badge, BadgeGroup, BrightnessSlider, Button, ButtonSpinner, CancelButton, Card, Checkbox, CheckboxField, CheckboxGroupField, CloseButton, ConfirmButton, ConfirmDialog, ContentTabs, CopyButton, CreateButton, CurrencyField, DateOfBirthField, DestructiveButton, DestructiveDialog, Dialog, DialogModal, Divider, DownloadButton, DropdownButton, DropdownField, EditButton, EmailField, EmptyStateSpinner, ErrorToast, FAQAccordion, FeatureToggle, FilterButton, H1, H2, H3, Icon, InfoDialog, InfoToast, Input, InputField, InputOtp, LinkButton, MarwesProvider, NavigationTabs, NotificationBadge, OptionRadioGroup, Paragraph, PasswordField, PermissionSwitch, PhoneField, PreferenceSwitch, PresenceAvatar, PrimaryButton, PriorityBadge, ProductCard, ProfileAvatar, ProfileCard, Radio, RadioGroupField, RadiusSlider, RatingRadioGroup, RefreshButton, RichText, RichTextField, SaveButton, SearchButton, SearchField, SecondaryButton, SectionsAccordion, Select, SelectField, SettingsAccordion, SettingsTabs, Slider, SliderField, SortButton, Spacer, Spacing, Spinner, StatCard, StatusBadge, SubmitButton, SuccessButton, SuccessToast, Switch, SwitchField, Tab, TabGroup, TabPanel, TeamAvatarGroup, TextButton, Textarea, TextareaField, Toast, ToastContainer, ToastProvider, Tooltip, TooltipGroup, URLField, UploadButton, VerifyButton, VolumeSlider, WarningToast, YesNoRadioGroup, ZipCodeField, createHeadingComponent, useTheme, useThemeMode, useToast };
6524
6657
  //# sourceMappingURL=index.js.map
6525
6658
  //# sourceMappingURL=index.js.map