@venix-sistemas/jade 1.0.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/LICENSE +21 -0
- package/README.md +481 -0
- package/dist/module.d.mts +26 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +249 -0
- package/dist/runtime/components/VenixIcon.d.vue.ts +14 -0
- package/dist/runtime/components/VenixIcon.vue +37 -0
- package/dist/runtime/components/VenixIcon.vue.d.ts +14 -0
- package/dist/runtime/components/VenixThemeSwitcher.d.vue.ts +9 -0
- package/dist/runtime/components/VenixThemeSwitcher.vue +191 -0
- package/dist/runtime/components/VenixThemeSwitcher.vue.d.ts +9 -0
- package/dist/runtime/composables/useThemeColors.d.ts +10 -0
- package/dist/runtime/composables/useThemeColors.js +25 -0
- package/dist/runtime/composables/useThemeCookies.d.ts +7 -0
- package/dist/runtime/composables/useThemeCookies.js +33 -0
- package/dist/runtime/composables/useThemeLocale.d.ts +12 -0
- package/dist/runtime/composables/useThemeLocale.js +87 -0
- package/dist/runtime/composables/useThemeSeasonal.d.ts +4 -0
- package/dist/runtime/composables/useThemeSeasonal.js +7 -0
- package/dist/runtime/composables/useVenixIcon.d.ts +3 -0
- package/dist/runtime/composables/useVenixIcon.js +11 -0
- package/dist/runtime/composables/useVenixTheme.d.ts +33 -0
- package/dist/runtime/composables/useVenixTheme.js +186 -0
- package/dist/runtime/nitro/theme-init.d.ts +2 -0
- package/dist/runtime/nitro/theme-init.js +11 -0
- package/dist/runtime/plugin.d.ts +2 -0
- package/dist/runtime/plugin.js +3 -0
- package/dist/runtime/plugins/theme-init.server.d.ts +2 -0
- package/dist/runtime/plugins/theme-init.server.js +37 -0
- package/dist/runtime/plugins/vuetify-theme.d.ts +2 -0
- package/dist/runtime/plugins/vuetify-theme.js +42 -0
- package/dist/runtime/public/fonts/Iceberg/Iceberg-Regular.woff2 +0 -0
- package/dist/runtime/public/images/ui/cursor/cursor-hand.png +0 -0
- package/dist/runtime/public/images/ui/cursor/cursor-text.png +0 -0
- package/dist/runtime/public/images/ui/cursor/cursor.png +0 -0
- package/dist/runtime/scripts/theme-init.d.ts +27 -0
- package/dist/runtime/scripts/theme-init.js +89 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/shared/constants.d.ts +28 -0
- package/dist/shared/constants.js +40 -0
- package/dist/shared/css/colors.d.ts +2 -0
- package/dist/shared/css/colors.js +19 -0
- package/dist/shared/css/cursor.d.ts +2 -0
- package/dist/shared/css/cursor.js +74 -0
- package/dist/shared/css/index.d.ts +8 -0
- package/dist/shared/css/index.js +31 -0
- package/dist/shared/css/scrollbar.d.ts +2 -0
- package/dist/shared/css/scrollbar.js +51 -0
- package/dist/shared/css/transition.d.ts +2 -0
- package/dist/shared/css/transition.js +20 -0
- package/dist/shared/css/typography.d.ts +3 -0
- package/dist/shared/css/typography.js +62 -0
- package/dist/shared/theme.json +193 -0
- package/dist/shared/types/color-options.d.ts +14 -0
- package/dist/shared/types/color-options.js +0 -0
- package/dist/shared/types/colors.d.ts +38 -0
- package/dist/shared/types/colors.js +0 -0
- package/dist/shared/types/cursor.d.ts +7 -0
- package/dist/shared/types/cursor.js +0 -0
- package/dist/shared/types/icon.d.ts +7 -0
- package/dist/shared/types/icon.js +0 -0
- package/dist/shared/types/index.d.ts +10 -0
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/runtime-config.d.ts +33 -0
- package/dist/shared/types/runtime-config.js +1 -0
- package/dist/shared/types/scrollbar.d.ts +12 -0
- package/dist/shared/types/scrollbar.js +0 -0
- package/dist/shared/types/theme.d.ts +11 -0
- package/dist/shared/types/theme.js +0 -0
- package/dist/shared/types/translation.d.ts +16 -0
- package/dist/shared/types/translation.js +0 -0
- package/dist/shared/types/typography.d.ts +15 -0
- package/dist/shared/types/typography.js +0 -0
- package/dist/shared/types/vuetify.d.ts +3 -0
- package/dist/shared/types/vuetify.js +0 -0
- package/dist/shared/unocss-preset.d.ts +32 -0
- package/dist/shared/unocss-preset.js +13 -0
- package/dist/shared/utils/consent.d.ts +8 -0
- package/dist/shared/utils/consent.js +12 -0
- package/dist/shared/utils/icon-refs.d.ts +10 -0
- package/dist/shared/utils/icon-refs.js +24 -0
- package/dist/shared/utils/icon.d.ts +27 -0
- package/dist/shared/utils/icon.js +20 -0
- package/dist/shared/utils/index.d.ts +9 -0
- package/dist/shared/utils/index.js +12 -0
- package/dist/shared/utils/load.d.ts +4 -0
- package/dist/shared/utils/load.js +24 -0
- package/dist/shared/utils/normalize.d.ts +20 -0
- package/dist/shared/utils/normalize.js +38 -0
- package/dist/shared/utils/options.d.ts +10 -0
- package/dist/shared/utils/options.js +12 -0
- package/dist/shared/utils/theme-config.d.ts +14 -0
- package/dist/shared/utils/theme-config.js +22 -0
- package/dist/shared/utils/theme-resolve.d.ts +24 -0
- package/dist/shared/utils/theme-resolve.js +45 -0
- package/dist/types.d.mts +3 -0
- package/package.json +100 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Venix Sistemas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./docs/images/jade-transparent.png" alt="Jade logo" width="200">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Jade</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<code>@venix-sistemas/jade</code>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@venix-sistemas/jade"><img src="https://badge.fury.io/js/@venix-sistemas%2Fjade.svg" alt="npm version"></a>
|
|
13
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
A complete centralized theming system for Nuxt applications: colors, typography, cursor, scrollbar, internationalization and seasonal themes, with UnoCSS, Vuetify and Iconify integrations.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
* 📝 **Typography and font faces customization**
|
|
21
|
+
* 🖱️ **Cursor customization**
|
|
22
|
+
* 📜 **Scrollbar customization**
|
|
23
|
+
* 🎨 **Color customization**, with dark and light themes included
|
|
24
|
+
* 🖥️ **System theme preference detection**, including seasonal overrides
|
|
25
|
+
* 🎃 **Seasonal themes**: Carnival, Halloween, Christmas and more
|
|
26
|
+
* 💾 **Cookie-consent-aware persistence**: nothing is written until the user opts in
|
|
27
|
+
* 🌐 **i18n**: internationalization and locale detection for theme names and your own strings
|
|
28
|
+
* ⚡ **Nuxt-native integration**
|
|
29
|
+
* 🎯 **UnoCSS preset** for the theme's color variables
|
|
30
|
+
* 🎭 **Vuetify 4** theme auto-configuration, kept in sync with live theme changes
|
|
31
|
+
* ✨ **Unified icons**: emoji, Iconify (animated `line-md`, `mdi`, both bundled and tree-shaken offline) or inline SVG through one component
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Install the package using your preferred package manager:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @venix-sistemas/jade
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or with pnpm:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add @venix-sistemas/jade
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Setup
|
|
48
|
+
|
|
49
|
+
### 1. Add the module
|
|
50
|
+
|
|
51
|
+
Add `@venix-sistemas/jade` to the `modules` section of your `nuxt.config.ts`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export default defineNuxtConfig({
|
|
55
|
+
modules: ['@venix-sistemas/jade'],
|
|
56
|
+
|
|
57
|
+
venixTheme: {
|
|
58
|
+
color: {
|
|
59
|
+
themes: {
|
|
60
|
+
dark: {
|
|
61
|
+
primary: '#FF6B6B',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Translation, color, scrollbar, cursor and typography are all on by default with sensible built-in values — the example above only overrides what it needs to. See [Configuration](#configuration) for the full reference.
|
|
70
|
+
|
|
71
|
+
### 2. Use the theme
|
|
72
|
+
|
|
73
|
+
You can access the theme utilities directly from your components via the auto-imported `useVenixTheme()` composable (named this way, rather than `useTheme`, to avoid colliding with the composable UI libraries like Vuetify auto-import under that same name):
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<template>
|
|
77
|
+
<div>
|
|
78
|
+
<button @click="theme.toggle('dark')">
|
|
79
|
+
Dark
|
|
80
|
+
</button>
|
|
81
|
+
|
|
82
|
+
<button @click="theme.toggle('light')">
|
|
83
|
+
Light
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</template>
|
|
87
|
+
|
|
88
|
+
<script setup lang="ts">
|
|
89
|
+
const { theme } = useVenixTheme()
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`useVenixTheme()` returns four grouped values:
|
|
94
|
+
|
|
95
|
+
- `theme` — current state and controls: `value` (resolved theme), `preference` (user's raw choice, including `'system'`), `data` (the active theme's `{ value, name, icon }`), `toggle(name?)`, `isSeasonalActive`, `activeSeasonalTheme`, `shouldApplyColors`, `colors` (the raw theme config).
|
|
96
|
+
- `themes` — the full list of selectable themes, ready to render a picker (see [Theme icons](#theme-icons)).
|
|
97
|
+
- `locale` — `{ current, set }` for the module's i18n state.
|
|
98
|
+
- `persistence` — cookie-consent gating for the theme cookies (see [Cookie Consent](#cookie-consent)): `hasConsent`, `grant()`, `revoke()`, plus the lower-level `enable()`/`disable()`.
|
|
99
|
+
|
|
100
|
+
`useVenixTheme()`'s preference is shared app-wide (via Nuxt's `useState`) — calling it from multiple components (e.g. your own page and `<VenixThemeSwitcher>` below) always reads/writes the same active theme, they never go out of sync.
|
|
101
|
+
|
|
102
|
+
### 3. Ready-made component
|
|
103
|
+
|
|
104
|
+
`<VenixThemeSwitcher>` is an auto-imported, framework-agnostic (no Vuetify/UI-kit dependency) dropdown for picking a color theme, using `<VenixIcon>` internally for each theme's icon:
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<template>
|
|
108
|
+
<VenixThemeSwitcher label="Color theme" />
|
|
109
|
+
</template>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Props:
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Default | Description |
|
|
115
|
+
| ----------- | --------- | ------------------------------ | -------------------------------------------------------------- |
|
|
116
|
+
| `label` | `string` | auto (translated, see below) | Accessible label for the trigger button and the menu heading |
|
|
117
|
+
| `showLabel` | `boolean` | `false` | Also show the active theme's name next to the icon on the button |
|
|
118
|
+
|
|
119
|
+
Without a `label`, it picks one of its own built-in translations based on the resolved locale (same detection as theme-name translations — see [Internationalization](#internationalization)), so it isn't stuck in a single hardcoded language.
|
|
120
|
+
|
|
121
|
+
A few behaviors worth knowing about:
|
|
122
|
+
|
|
123
|
+
- The trigger's icon key changes on every theme switch, forcing it to remount — so animated icons (Iconify `line-md`, etc.) replay their animation each time instead of staying frozen mid-frame.
|
|
124
|
+
- The trigger fades in from `opacity: 0` on mount rather than popping in.
|
|
125
|
+
- The dropdown's item names and icons use fixed, hardcoded neutral colors (a dark surface with light text) instead of the active theme's `--color-*` variables — on purpose, so the list of themes stays equally legible no matter which theme is currently applied. The trigger button itself is unaffected and still uses the active theme's colors.
|
|
126
|
+
- Selecting a theme announces the change to screen readers via a visually-hidden live region, and keyboard users get arrow-key/Home/End navigation between items (`role="menu"` + `role="menuitemradio"`), with focus returning to the trigger on close.
|
|
127
|
+
|
|
128
|
+
It ships with minimal, self-contained CSS, so it looks reasonable out of the box in any project — style it further with `.venix-theme-switcher`, `.venix-theme-switcher__trigger`, `.venix-theme-switcher__menu` and `.venix-theme-switcher__item` (see [`VenixThemeSwitcher.vue`](./src/runtime/components/VenixThemeSwitcher.vue)).
|
|
129
|
+
|
|
130
|
+
### Theme transition
|
|
131
|
+
|
|
132
|
+
Switching themes (via `theme.toggle()`, the switcher above, or a system/seasonal auto-change) is animated with the browser's native [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) — a soft cross-fade between the old and new appearance, no extra setup needed. It falls back to an instant swap on browsers without support, and is skipped automatically when the user has `prefers-reduced-motion: reduce` set.
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
### Module Options
|
|
137
|
+
|
|
138
|
+
Every feature (`translation`, `color`, `scrollbar`, `cursor`, `typography`) is configured through a single key that accepts either a `boolean` (quick enable/disable) or a config object (which also enables the feature):
|
|
139
|
+
|
|
140
|
+
| Option | Type | Default | Description |
|
|
141
|
+
| ------------- | -------------------- | ------- | ----------------------------------------------- |
|
|
142
|
+
| `translation` | `boolean \| object` | `true` | Enable or configure locale detection/i18n |
|
|
143
|
+
| `color` | `boolean \| object` | `true` | Enable or configure the color system |
|
|
144
|
+
| `scrollbar` | `boolean \| object` | `true` | Enable or configure the custom scrollbar |
|
|
145
|
+
| `cursor` | `boolean \| object` | `true` | Enable or configure the custom cursor |
|
|
146
|
+
| `typography` | `boolean \| object` | `true` | Enable or configure typography |
|
|
147
|
+
| `vuetify` | `boolean \| object` | `false` | Auto-configure Vuetify's theme with the theme colors |
|
|
148
|
+
| `icon` | `boolean \| object` | `true` | Install and configure `@nuxt/icon`, register `<VenixIcon>` / `useVenixIcon` |
|
|
149
|
+
|
|
150
|
+
#### `translation` object
|
|
151
|
+
|
|
152
|
+
| Property | Type | Default | Description |
|
|
153
|
+
| -------------- | --------- | -------------------- | -------------------------------------------------- |
|
|
154
|
+
| `locale` | `string` | — | Force a specific locale |
|
|
155
|
+
| `defaultLocale`| `string` | `'en-US'` | Default fallback locale |
|
|
156
|
+
| `cookieSync` | `string` | `'i18n_redirected'` | Cookie used to persist/sync the selected locale |
|
|
157
|
+
| `manageHtmlLang` | `boolean` | `false` | Keep `<html lang>` in sync with the resolved locale (see [Internationalization](#internationalization)) |
|
|
158
|
+
|
|
159
|
+
Setting `translation: false` fully disables locale detection and stops `locale.set()` from ever writing to the locale cookie — theme names and `<VenixThemeSwitcher>`'s own strings always render in `defaultLocale` instead. Useful if you don't need theme-name translations at all and want this module to leave locale-related state alone entirely, not just because another module also reads that cookie (reading it is harmless either way — this module only ever writes to it through `locale.set()`, which nothing calls automatically).
|
|
160
|
+
|
|
161
|
+
#### `color` object
|
|
162
|
+
|
|
163
|
+
| Property | Type | Default | Description |
|
|
164
|
+
| ------------- | --------- | -------- | ----------------------------------------------------- |
|
|
165
|
+
| `apply` | `boolean` | `true` | Automatically apply the resolved theme (`data-theme`) |
|
|
166
|
+
| `defaultColor`| `string` | `'dark'` | Name of the theme used when no preference is set |
|
|
167
|
+
| `themes` | `object` | `{}` | Override or add custom color themes |
|
|
168
|
+
| `iconFormat` | `'emote' \| 'css' \| 'svg'` | `'svg'` | Preferred variant for themes with an `icon` object — see [Theme icons](#theme-icons) |
|
|
169
|
+
|
|
170
|
+
## UnoCSS integration
|
|
171
|
+
|
|
172
|
+
`jade` ships a UnoCSS preset that exposes the theme's color variables under `theme.colors` — add it to your own `uno.config.ts`:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
// uno.config.ts
|
|
176
|
+
import { defineConfig } from 'unocss'
|
|
177
|
+
import { venixUnoPreset } from '@venix-sistemas/jade/unocss'
|
|
178
|
+
|
|
179
|
+
export default defineConfig({
|
|
180
|
+
presets: [
|
|
181
|
+
venixUnoPreset(),
|
|
182
|
+
// ...your other presets
|
|
183
|
+
],
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`venixUnoPreset()` is equivalent to writing this yourself:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
theme: {
|
|
191
|
+
colors: {
|
|
192
|
+
primary: 'var(--color-primary)',
|
|
193
|
+
secondary: 'var(--color-secondary)',
|
|
194
|
+
accent: 'var(--color-accent)',
|
|
195
|
+
error: 'var(--color-error)',
|
|
196
|
+
info: 'var(--color-info)',
|
|
197
|
+
success: 'var(--color-success)',
|
|
198
|
+
warning: 'var(--color-warning)',
|
|
199
|
+
background: 'var(--color-background)',
|
|
200
|
+
background2: 'var(--color-background2)',
|
|
201
|
+
background3: 'var(--color-background3)',
|
|
202
|
+
inverse: 'var(--color-inverse)',
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
This means utilities like `text-primary`, `bg-background2` or `border-accent` work out of the box. If you already define any of these colors yourself in `uno.config.ts`, your values take precedence.
|
|
208
|
+
|
|
209
|
+
This has to be added manually rather than auto-injected: `@unocss/nuxt` reloads `uno.config.ts` from disk on its own (to support HMR) and shallow-merges that file against anything injected through the `unocss:config` hook, which silently discards a hook-injected `theme` whenever your `uno.config.ts` already declares its own `theme` key — even one without any colors in it. As a preset living inside your own `presets` array, these colors become part of what's actually read from the file, so they survive that merge.
|
|
210
|
+
|
|
211
|
+
## Vuetify integration
|
|
212
|
+
|
|
213
|
+
If [`vuetify-nuxt-module`](https://nuxt.vuetifyjs.com) is installed, enabling `vuetify: true` registers every color theme as a Vuetify `ThemeDefinition` and keeps Vuetify's active theme in sync with the cookie the rest of the module uses — no more hand-written `theme.themes` mapping in `nuxt.config.ts`:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
export default defineNuxtConfig({
|
|
217
|
+
modules: [
|
|
218
|
+
'@venix-sistemas/jade', // must come before the Vuetify module
|
|
219
|
+
'vuetify-nuxt-module',
|
|
220
|
+
],
|
|
221
|
+
venixTheme: {
|
|
222
|
+
vuetify: true,
|
|
223
|
+
},
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Unlike the UnoCSS integration (which points at CSS variables), Vuetify computes contrast and `on-*` colors in JavaScript, so it needs real hex values — the module passes the actual colors from each theme, not `var(...)` strings. `background2` is also mapped to Vuetify's `surface` slot (used by cards, toolbars, etc.), since that's the closest match in this module's color system, and every color is still available under its own name too (`bg-background2`, `text-inverse`, ...).
|
|
228
|
+
|
|
229
|
+
`vuetify: true` also adds a small runtime plugin that keeps Vuetify's active theme in sync at every point: it resolves the same `theme-preference`/`theme-resolved` cookies used elsewhere in the module so Vuetify's `defaultTheme` matches what's rendered from the first paint (server and client), and it applies every later runtime switch (`theme.toggle()`, the switcher, a system/seasonal auto-change) via `theme.change()` (the current, non-deprecated API), inside the same View Transition the rest of the module uses rather than racing it as a separate update.
|
|
230
|
+
|
|
231
|
+
**This must come before the Vuetify module in your `modules` array** — the registration happens through Vuetify's own [`vuetify:registerModule`](https://nuxt.vuetifyjs.com/guide/advanced/layers-and-hooks.html) build hook, which only picks up registrations made before Vuetify resolves its configuration. If you already define `vuetify.vuetifyOptions.theme.themes` yourself, your values take precedence over the generated ones (merged per color, not replaced wholesale).
|
|
232
|
+
|
|
233
|
+
`vuetify` defaults to `false` — unlike the other integrations, it ships a runtime plugin, so it's opt-in rather than automatic. There's no hard dependency on Vuetify: if `vuetify-nuxt-module` isn't installed, enabling this option is a no-op. Tested against Vuetify `^4.2.1` with `vuetify-nuxt-module@1.0.0-rc.6` — as that module is still pre-1.0, its hooks may still change between releases.
|
|
234
|
+
|
|
235
|
+
## Icons
|
|
236
|
+
|
|
237
|
+
`jade` installs and configures [`@nuxt/icon`](https://github.com/nuxt/icon) automatically and registers `<VenixIcon>` (and the equivalent `useVenixIcon()` composable), which accept **one single `icon` value** in any of three formats:
|
|
238
|
+
|
|
239
|
+
```vue
|
|
240
|
+
<template>
|
|
241
|
+
<VenixIcon icon="🎨" />
|
|
242
|
+
<VenixIcon icon="line-md:home" />
|
|
243
|
+
<VenixIcon icon="<svg viewBox=\"0 0 24 24\">...</svg>" />
|
|
244
|
+
</template>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
* **Emoji** — any other string, rendered as text.
|
|
248
|
+
* **Iconify icon name** — `collection:name` format (e.g. `line-md:home`, `mdi:home`). Includes full support for [`line-md`](https://icon-sets.iconify.design/line-md/)'s animated icons.
|
|
249
|
+
* **Inline SVG** — a string starting with `<svg`, rendered via `v-html`. Only pass SVGs you or your theme config author, not end-user input — like any other `v-html` usage, this is not sanitized.
|
|
250
|
+
|
|
251
|
+
### Aliases
|
|
252
|
+
|
|
253
|
+
Define short names for any of the three formats through `icon.aliases`, so consuming components don't need to remember full Iconify names:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
venixTheme: {
|
|
257
|
+
icon: {
|
|
258
|
+
aliases: {
|
|
259
|
+
home: 'line-md:home',
|
|
260
|
+
favorite: 'line-md:heart-filled',
|
|
261
|
+
brand: '🎨',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```vue
|
|
268
|
+
<VenixIcon icon="home" />
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Offline icon collections
|
|
272
|
+
|
|
273
|
+
By default, the `line-md` collection is bundled at build time via `@iconify-json/line-md` (a direct dependency of this module) — icons resolve from the published package itself, not from the Iconify API, so they keep working the same way in the playground and once this module is installed as a dependency elsewhere, including offline. `@iconify-json/mdi` is also a direct dependency for the same reason: the built-in themes' `css` icon variant uses `mdi:*` names (see [Theme icons](#theme-icons)), and switching `color.iconFormat` to `'css'` pulls from it — only the handful of `mdi` icons actually referenced get bundled (see the tree-shaking note below), not the whole collection. Add more collections with `icon.collections` (each one needs its matching `@iconify-json/<collection>` package installed in your project — this fully bundles the whole collection, so any icon from it can be used anywhere in your app, not just in theme icons):
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
venixTheme: {
|
|
277
|
+
icon: {
|
|
278
|
+
collections: ['line-md', 'tabler'],
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Independently of `icon.collections`, any `collection:name` value used in a theme's `icon` (see [Theme icons](#theme-icons) below) or in `icon.aliases` — `mdi` included — is detected automatically and bundled offline with **only the icons actually referenced**, the same tree-shaking `mdi` gets by default (via [`@iconify/utils`](https://iconify.design/docs/libraries/utils/)). This lookup tries this module's own dependencies first, then your project's `node_modules`, so a collection you install yourself for a custom theme icon (without adding it to `icon.collections`) is found and tree-shaken the same way. If a referenced collection isn't found in either place, `@nuxt/icon` falls back to fetching it from the Iconify API at runtime (needs internet).
|
|
284
|
+
|
|
285
|
+
Setting `icon: false` skips installing `@nuxt/icon` entirely — useful if your project already configures it directly.
|
|
286
|
+
|
|
287
|
+
### Theme icons
|
|
288
|
+
|
|
289
|
+
Each color theme's `icon` (used by `useVenixTheme().themes` for things like a theme picker) can be a plain emoji string, like the built-in themes ship by default:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
"dark": {
|
|
293
|
+
"icon": "🌙"
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
...or an object offering up to three variants, letting the app pick the best one for its needs:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
"light": {
|
|
301
|
+
"icon": {
|
|
302
|
+
"emote": "☀️",
|
|
303
|
+
"css": "mdi:sun-compass",
|
|
304
|
+
"svg": "line-md:sunny-filled-loop"
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
* `emote` — an emoji, always required as the fallback.
|
|
310
|
+
* `css` — an Iconify icon name rendered via `@nuxt/icon` in **CSS mode** (background/mask, no animation, lighter weight).
|
|
311
|
+
* `svg` — an Iconify icon name rendered via `@nuxt/icon` in **SVG mode** (a real `<svg>` element — required for animated icons like `line-md`'s to actually animate).
|
|
312
|
+
|
|
313
|
+
Which variant gets used is controlled globally by `color.iconFormat` (`'emote' | 'css' | 'svg'`, defaults to `'svg'`) — it falls back to `emote` automatically for any theme that doesn't define the chosen format (including themes that just use a plain string):
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
venixTheme: {
|
|
317
|
+
color: {
|
|
318
|
+
iconFormat: 'css', // lighter weight, no animation — trade the default 'svg' for this if you don't need line-md's animated icons
|
|
319
|
+
},
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`useVenixTheme().themes` exposes the already-resolved icon as `{ format, value }`, ready to feed into `<VenixIcon>`:
|
|
324
|
+
|
|
325
|
+
```vue
|
|
326
|
+
<VenixIcon
|
|
327
|
+
v-if="item.icon"
|
|
328
|
+
:icon="item.icon.value"
|
|
329
|
+
:mode="item.icon.format === 'emote' ? undefined : item.icon.format"
|
|
330
|
+
/>
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Themes
|
|
334
|
+
|
|
335
|
+
The module ships with a built-in set of color themes (`dark`, `light`, plus the [seasonal ones](#seasonal-themes)) and can be extended through `color.themes`, which does two different things depending on the key:
|
|
336
|
+
|
|
337
|
+
- An **existing** name (`dark`, `light`, or any seasonal one) overrides only the properties you give it, keeping the rest of that theme intact.
|
|
338
|
+
- A **new** name registers a theme that doesn't exist in the built-in set at all — nothing further to opt into, it works the same as any built-in theme: real CSS variables, its own entry in `useVenixTheme().themes` (so it shows up in `<VenixThemeSwitcher>`), and Vuetify/UnoCSS integration if those are enabled. Useful for a theme specific to your project, e.g. a brand color scheme or a one-off campaign theme with no equivalent in the base set.
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
color: {
|
|
342
|
+
themes: {
|
|
343
|
+
// Overrides an existing theme — the rest of `dark` stays as shipped
|
|
344
|
+
dark: {
|
|
345
|
+
primary: '#FF6B6B',
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
// Registers a brand new one, project-specific — 'summerSale' isn't a
|
|
349
|
+
// built-in theme name, so this doesn't override anything
|
|
350
|
+
summerSale: {
|
|
351
|
+
dark: false,
|
|
352
|
+
primary: '#FF8A00',
|
|
353
|
+
background: '#FFF8EE',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
This is currently the only supported way to customize colors — there's no option yet to load a completely custom theme file in place of the built-in one.
|
|
360
|
+
|
|
361
|
+
## Cookie Consent
|
|
362
|
+
|
|
363
|
+
The module writes two cookies to remember the user's choice across visits:
|
|
364
|
+
|
|
365
|
+
| Cookie | Purpose |
|
|
366
|
+
| -------------------------- | ------------------------------------------------- |
|
|
367
|
+
| `venix-theme-preference` | The user's raw choice (`'dark'`, `'system'`, a custom theme name, …) |
|
|
368
|
+
| `venix-theme-resolved` | The actually-applied theme (e.g. `'system'` resolved to `'dark'`) — lets SSR render the right theme on the next visit without a flash |
|
|
369
|
+
|
|
370
|
+
Neither is written until cookie consent is granted — **switching themes always works immediately for the current visit either way**; without consent it just isn't remembered on the next one. (The module's own locale cookie, used for `@nuxtjs/i18n` interop, is a separate concern — see [Integrating with a routing-based i18n module](#integrating-with-a-routing-based-i18n-module-eg-nuxtjsi18n).)
|
|
371
|
+
|
|
372
|
+
Consent is read from `localStorage['venix-cookie-consent']` (a JSON object with a `functionality: boolean` field) — the same format most cookie-consent banners already use for a "functional cookies" category. This module never writes that key on its own; either wire your own consent banner to it, or use the convenience methods below for a minimal one:
|
|
373
|
+
|
|
374
|
+
```vue
|
|
375
|
+
<script setup lang="ts">
|
|
376
|
+
const { persistence } = useVenixTheme()
|
|
377
|
+
</script>
|
|
378
|
+
|
|
379
|
+
<template>
|
|
380
|
+
<p>Cookies: {{ persistence.hasConsent.value ? 'allowed' : 'not allowed' }}</p>
|
|
381
|
+
<button @click="persistence.grant()">Allow cookies</button>
|
|
382
|
+
<button @click="persistence.revoke()">Revoke cookies</button>
|
|
383
|
+
</template>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
- `persistence.hasConsent` — reactive `Ref<boolean>`, current consent state (shared app-wide, like `theme.preference`).
|
|
387
|
+
- `persistence.grant()` — records consent (`{ functionality: true }`) and immediately persists the current theme preference to cookies.
|
|
388
|
+
- `persistence.revoke()` — records the opposite and immediately deletes both cookies.
|
|
389
|
+
|
|
390
|
+
If you already have your own consent-management setup (a full CMP, a custom banner, etc.), skip `grant()`/`revoke()` and just make sure it writes the same `localStorage` key/shape and dispatches a `window` event named `venix-cookie-preferences-updated` after any change — the module listens for that event (from any source) to re-sync immediately, rather than waiting for the next theme change:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
localStorage.setItem('venix-cookie-consent', JSON.stringify({ functionality: true }))
|
|
394
|
+
window.dispatchEvent(new Event('venix-cookie-preferences-updated'))
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`persistence.enable()` / `persistence.disable()` remain available as the lower-level primitives (`enable()` still checks the stored consent before writing, so it's safe to call speculatively — it's a no-op without consent).
|
|
398
|
+
|
|
399
|
+
## Internationalization
|
|
400
|
+
|
|
401
|
+
The module supports locale detection and can integrate with the application's internationalization setup — it's used to translate each color theme's name (`theme.colors.themes.<name>.translations`) and the built-in strings of `<VenixThemeSwitcher>`.
|
|
402
|
+
|
|
403
|
+
The locale resolution can use:
|
|
404
|
+
|
|
405
|
+
1. The explicitly configured locale (`translation.locale`), if set — forces that locale everywhere, skipping every other step
|
|
406
|
+
2. The persisted locale cookie (`translation.cookieSync`, default `'i18n_redirected'`)
|
|
407
|
+
3. The current URL's locale prefix (e.g. `/en/...`), if it matches one of the theme's available locales — covers the case where a user opens a locale-prefixed route (e.g. via `@nuxtjs/i18n`) with no cookie yet and a browser language that disagrees with the URL
|
|
408
|
+
4. The `Accept-Language` header (SSR) / `navigator.language` (client)
|
|
409
|
+
5. The configured default locale (`translation.defaultLocale`)
|
|
410
|
+
|
|
411
|
+
### Using your own translations
|
|
412
|
+
|
|
413
|
+
`useVenixTheme().locale` also exposes the same resolution/fallback logic the module uses internally for theme names, in case you want to translate your own strings the same way:
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
const { locale } = useVenixTheme()
|
|
417
|
+
|
|
418
|
+
locale.translate({ 'en-US': 'Hello', 'pt-BR': 'Olá' }, 'Hello') // fallback if no match
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Integrating with a routing-based i18n module (e.g. `@nuxtjs/i18n`)
|
|
422
|
+
|
|
423
|
+
`translation.cookieSync` defaults to `'i18n_redirected'` — the same cookie `@nuxtjs/i18n` uses by default — so theme-name translations automatically follow whichever locale your app's i18n module resolves, with no extra wiring in the common case. If you've customized either side's cookie name, point `cookieSync` at the same one.
|
|
424
|
+
|
|
425
|
+
By default, this module does **not** touch `<html lang>`. If your app has no routing-based i18n module and you still want `<html lang>` to reflect the resolved locale (recommended for accessibility — WCAG 3.1.1 — so screen readers pronounce translated theme names correctly), turn it on explicitly:
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
venixTheme: {
|
|
429
|
+
translation: {
|
|
430
|
+
manageHtmlLang: true,
|
|
431
|
+
},
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Leave it off (the default) if you use `@nuxtjs/i18n` or a similar module: that module's locale reflects the current route and is the correct source of truth for `<html lang>`, while this module's own locale detection (cookie/header/browser) can briefly disagree with it (e.g. right after navigating to a localized route, before any cookie write happens) — having both write to `lang` would make them fight over it.
|
|
436
|
+
|
|
437
|
+
## Seasonal Themes
|
|
438
|
+
|
|
439
|
+
Built in: 🎭 Carnival, 🎃 Halloween, 🎄 Christmas — applied automatically, in place of `dark`/`light`, when the `'system'` preference is active and today falls inside their date range.
|
|
440
|
+
|
|
441
|
+
Add your own through `color.themes`, the same as any other custom theme — `seasonal: true` and `dateRange` (`MM-DD`, inclusive, wraps across year-end if `end` < `start`) opt it into this rotation instead of making it directly selectable. `dark` still matters here: it decides which system mode (light/dark) the theme pairs with.
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
color: {
|
|
445
|
+
themes: {
|
|
446
|
+
blackFriday: {
|
|
447
|
+
dark: true,
|
|
448
|
+
seasonal: true,
|
|
449
|
+
dateRange: { start: '11-24', end: '11-30' },
|
|
450
|
+
primary: '#111111',
|
|
451
|
+
background: '#000000',
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Development
|
|
458
|
+
|
|
459
|
+
Clone the repository, then:
|
|
460
|
+
|
|
461
|
+
| Command | Description |
|
|
462
|
+
| ------- | ----------- |
|
|
463
|
+
| `pnpm install` | Install dependencies |
|
|
464
|
+
| `pnpm dev` | Start the playground |
|
|
465
|
+
| `pnpm test` | Run the tests |
|
|
466
|
+
| `pnpm lint` | Run the linter |
|
|
467
|
+
| `pnpm prepack` | Build the package |
|
|
468
|
+
|
|
469
|
+
See the [Configuration](#configuration) section above for the full options reference, and the [Playground](./playground) for a working example.
|
|
470
|
+
|
|
471
|
+
## Brand
|
|
472
|
+
|
|
473
|
+
The Jade logo, in the variants shipped with the repository (`docs/images`):
|
|
474
|
+
|
|
475
|
+
| Transparent | Dark background | Emblem (1254 px) |
|
|
476
|
+
| :---------: | :-------------: | :--------------: |
|
|
477
|
+
| <img src="./docs/images/jade-transparent.png" alt="Jade, transparent background" width="200"> | <img src="./docs/images/jade.png" alt="Jade, dark background" width="200"> | <img src="./docs/images/jade-emblem.png" alt="Jade emblem" width="200"> |
|
|
478
|
+
|
|
479
|
+
## License
|
|
480
|
+
|
|
481
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { TranslationConfig, ColorOptions, ScrollbarConfig, CursorConfig, TypographyConfig, VuetifyOptions, IconOptions } from '../dist/shared/types/index.js';
|
|
3
|
+
|
|
4
|
+
interface ModuleOptions {
|
|
5
|
+
translation?: boolean | Partial<TranslationConfig>;
|
|
6
|
+
color?: boolean | Partial<ColorOptions>;
|
|
7
|
+
scrollbar?: boolean | Partial<ScrollbarConfig>;
|
|
8
|
+
cursor?: boolean | Partial<CursorConfig>;
|
|
9
|
+
typography?: boolean | Partial<TypographyConfig>;
|
|
10
|
+
/**
|
|
11
|
+
* Registers the color themes with Vuetify (`vuetify-nuxt-module`), if
|
|
12
|
+
* installed. Off by default — enable explicitly in projects using Vuetify.
|
|
13
|
+
* Must come BEFORE the Vuetify module in `modules`.
|
|
14
|
+
*/
|
|
15
|
+
vuetify?: boolean | Partial<VuetifyOptions>;
|
|
16
|
+
/**
|
|
17
|
+
* Integrates `@nuxt/icon` and registers `<VenixIcon>` / `useVenixIcon`,
|
|
18
|
+
* able to render an emoji, an Iconify icon (e.g. `line-md:home`, animated)
|
|
19
|
+
* or an inline SVG from a single value. Installs `@nuxt/icon` automatically.
|
|
20
|
+
*/
|
|
21
|
+
icon?: boolean | Partial<IconOptions>;
|
|
22
|
+
}
|
|
23
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
24
|
+
|
|
25
|
+
export { _default as default };
|
|
26
|
+
export type { ModuleOptions };
|