vuetiwatch 0.1.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 +232 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +41 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +58 -0
- package/dist/plugin.js.map +1 -0
- package/dist/registry.d.ts +17 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +37 -0
- package/dist/registry.js.map +1 -0
- package/dist/styles/aurora.css +74 -0
- package/dist/styles/aurora.min.css +1 -0
- package/dist/styles/brutalist.css +48 -0
- package/dist/styles/brutalist.min.css +1 -0
- package/dist/styles/calm.css +21 -0
- package/dist/styles/calm.min.css +1 -0
- package/dist/styles/clay.css +44 -0
- package/dist/styles/clay.min.css +1 -0
- package/dist/styles/core.css +140 -0
- package/dist/styles/core.min.css +1 -0
- package/dist/styles/darkGlass.css +43 -0
- package/dist/styles/darkGlass.min.css +1 -0
- package/dist/styles/liquidGlass.css +100 -0
- package/dist/styles/liquidGlass.min.css +1 -0
- package/dist/styles/lux.css +21 -0
- package/dist/styles/lux.min.css +1 -0
- package/dist/styles/morph.css +37 -0
- package/dist/styles/morph.min.css +1 -0
- package/dist/styles/neon.css +41 -0
- package/dist/styles/neon.min.css +1 -0
- package/dist/styles/paper.css +17 -0
- package/dist/styles/paper.min.css +1 -0
- package/dist/styles/sketchy.css +25 -0
- package/dist/styles/sketchy.min.css +1 -0
- package/dist/styles/slate.css +16 -0
- package/dist/styles/slate.min.css +1 -0
- package/dist/styles/soft.css +46 -0
- package/dist/styles/soft.min.css +1 -0
- package/dist/styles.css +686 -0
- package/dist/styles.min.css +1 -0
- package/dist/themes/aurora.d.ts +10 -0
- package/dist/themes/aurora.d.ts.map +1 -0
- package/dist/themes/aurora.js +86 -0
- package/dist/themes/aurora.js.map +1 -0
- package/dist/themes/brutalist.d.ts +10 -0
- package/dist/themes/brutalist.d.ts.map +1 -0
- package/dist/themes/brutalist.js +103 -0
- package/dist/themes/brutalist.js.map +1 -0
- package/dist/themes/calm.d.ts +11 -0
- package/dist/themes/calm.d.ts.map +1 -0
- package/dist/themes/calm.js +88 -0
- package/dist/themes/calm.js.map +1 -0
- package/dist/themes/classic.d.ts +6 -0
- package/dist/themes/classic.d.ts.map +1 -0
- package/dist/themes/classic.js +23 -0
- package/dist/themes/classic.js.map +1 -0
- package/dist/themes/clay.d.ts +8 -0
- package/dist/themes/clay.d.ts.map +1 -0
- package/dist/themes/clay.js +80 -0
- package/dist/themes/clay.js.map +1 -0
- package/dist/themes/darkGlass.d.ts +7 -0
- package/dist/themes/darkGlass.d.ts.map +1 -0
- package/dist/themes/darkGlass.js +77 -0
- package/dist/themes/darkGlass.js.map +1 -0
- package/dist/themes/index.d.ts +15 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +15 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/liquidGlass.d.ts +19 -0
- package/dist/themes/liquidGlass.d.ts.map +1 -0
- package/dist/themes/liquidGlass.js +111 -0
- package/dist/themes/liquidGlass.js.map +1 -0
- package/dist/themes/lux.d.ts +8 -0
- package/dist/themes/lux.d.ts.map +1 -0
- package/dist/themes/lux.js +91 -0
- package/dist/themes/lux.js.map +1 -0
- package/dist/themes/morph.d.ts +10 -0
- package/dist/themes/morph.d.ts.map +1 -0
- package/dist/themes/morph.js +82 -0
- package/dist/themes/morph.js.map +1 -0
- package/dist/themes/neon.d.ts +7 -0
- package/dist/themes/neon.d.ts.map +1 -0
- package/dist/themes/neon.js +96 -0
- package/dist/themes/neon.js.map +1 -0
- package/dist/themes/paper.d.ts +7 -0
- package/dist/themes/paper.d.ts.map +1 -0
- package/dist/themes/paper.js +88 -0
- package/dist/themes/paper.js.map +1 -0
- package/dist/themes/sketchy.d.ts +11 -0
- package/dist/themes/sketchy.d.ts.map +1 -0
- package/dist/themes/sketchy.js +88 -0
- package/dist/themes/sketchy.js.map +1 -0
- package/dist/themes/slate.d.ts +7 -0
- package/dist/themes/slate.d.ts.map +1 -0
- package/dist/themes/slate.js +89 -0
- package/dist/themes/slate.js.map +1 -0
- package/dist/themes/soft.d.ts +7 -0
- package/dist/themes/soft.d.ts.map +1 -0
- package/dist/themes/soft.js +84 -0
- package/dist/themes/soft.js.map +1 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/util/defaults.d.ts +62 -0
- package/dist/util/defaults.d.ts.map +1 -0
- package/dist/util/defaults.js +100 -0
- package/dist/util/defaults.js.map +1 -0
- package/dist/util/defineTheme.d.ts +21 -0
- package/dist/util/defineTheme.d.ts.map +1 -0
- package/dist/util/defineTheme.js +25 -0
- package/dist/util/defineTheme.js.map +1 -0
- package/dist/util/merge.d.ts +8 -0
- package/dist/util/merge.d.ts.map +1 -0
- package/dist/util/merge.js +17 -0
- package/dist/util/merge.js.map +1 -0
- package/package.json +75 -0
- package/src/index.ts +35 -0
- package/src/plugin.ts +116 -0
- package/src/registry.ts +64 -0
- package/src/styles/core.css +311 -0
- package/src/styles/index.css +30 -0
- package/src/styles/themes/aurora.css +86 -0
- package/src/styles/themes/brutalist.css +59 -0
- package/src/styles/themes/calm.css +31 -0
- package/src/styles/themes/clay.css +60 -0
- package/src/styles/themes/darkGlass.css +69 -0
- package/src/styles/themes/liquidGlass.css +172 -0
- package/src/styles/themes/lux.css +31 -0
- package/src/styles/themes/morph.css +53 -0
- package/src/styles/themes/neon.css +56 -0
- package/src/styles/themes/paper.css +32 -0
- package/src/styles/themes/sketchy.css +34 -0
- package/src/styles/themes/slate.css +26 -0
- package/src/styles/themes/soft.css +63 -0
- package/src/themes/aurora.ts +86 -0
- package/src/themes/brutalist.ts +103 -0
- package/src/themes/calm.ts +88 -0
- package/src/themes/classic.ts +23 -0
- package/src/themes/clay.ts +80 -0
- package/src/themes/darkGlass.ts +77 -0
- package/src/themes/index.ts +14 -0
- package/src/themes/liquidGlass.ts +111 -0
- package/src/themes/lux.ts +91 -0
- package/src/themes/morph.ts +82 -0
- package/src/themes/neon.ts +96 -0
- package/src/themes/paper.ts +88 -0
- package/src/themes/sketchy.ts +88 -0
- package/src/themes/slate.ts +89 -0
- package/src/themes/soft.ts +84 -0
- package/src/types.ts +44 -0
- package/src/util/defaults.ts +164 -0
- package/src/util/defineTheme.ts +49 -0
- package/src/util/merge.ts +23 -0
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vuetiwatch",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Free, plug-and-play themes for Vuetify 4 \u2014 a Bootswatch for Vuetify",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "j-tap",
|
|
8
|
+
"homepage": "https://j-tap.github.io/vuetiwatch/",
|
|
9
|
+
"bugs": "https://github.com/j-tap/vuetiwatch/issues",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/j-tap/vuetiwatch.git",
|
|
13
|
+
"directory": "packages/vuetiwatch"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vuetiwatch",
|
|
17
|
+
"vue",
|
|
18
|
+
"vue3",
|
|
19
|
+
"vuetify",
|
|
20
|
+
"vuetify4",
|
|
21
|
+
"theme",
|
|
22
|
+
"themes",
|
|
23
|
+
"dark-mode",
|
|
24
|
+
"design-system"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": [
|
|
27
|
+
"*.css"
|
|
28
|
+
],
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"module": "./dist/index.js",
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"default": "./dist/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./styles": "./dist/styles.css",
|
|
38
|
+
"./styles.css": "./dist/styles.css",
|
|
39
|
+
"./styles.min.css": "./dist/styles.min.css",
|
|
40
|
+
"./styles/*": "./dist/styles/*",
|
|
41
|
+
"./package.json": "./package.json"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"src"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20.19.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "npm run clean && npm run build:types && npm run build:styles",
|
|
52
|
+
"build:types": "tsc",
|
|
53
|
+
"build:styles": "node scripts/build-styles.mjs",
|
|
54
|
+
"clean": "node --eval \"fs.rmSync('dist', { recursive: true, force: true })\"",
|
|
55
|
+
"dev": "tsc --watch",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"prepack": "npm run build"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"vue": "^3.5.0",
|
|
61
|
+
"vuetify": "^4.0.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"browserslist": "^4.28.0",
|
|
65
|
+
"lightningcss": "^1.33.0",
|
|
66
|
+
"typescript": "^7.0.2",
|
|
67
|
+
"vue": "^3.5.41",
|
|
68
|
+
"vuetify": "^4.1.11"
|
|
69
|
+
},
|
|
70
|
+
"browserslist": [
|
|
71
|
+
">0.5%",
|
|
72
|
+
"last 2 versions",
|
|
73
|
+
"not dead"
|
|
74
|
+
]
|
|
75
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export {
|
|
2
|
+
aurora,
|
|
3
|
+
brutalist,
|
|
4
|
+
calm,
|
|
5
|
+
classic,
|
|
6
|
+
clay,
|
|
7
|
+
darkGlass,
|
|
8
|
+
liquidGlass,
|
|
9
|
+
lux,
|
|
10
|
+
morph,
|
|
11
|
+
neon,
|
|
12
|
+
paper,
|
|
13
|
+
sketchy,
|
|
14
|
+
slate,
|
|
15
|
+
soft,
|
|
16
|
+
} from './themes/index.js'
|
|
17
|
+
export {
|
|
18
|
+
themeList,
|
|
19
|
+
vuetiwatchDefaults,
|
|
20
|
+
vuetiwatchMeta,
|
|
21
|
+
vuetiwatchThemes,
|
|
22
|
+
} from './registry.js'
|
|
23
|
+
export { createVuetiwatch } from './plugin.js'
|
|
24
|
+
export { defineTheme } from './util/defineTheme.js'
|
|
25
|
+
export { bars, combine, controls, fields, icons, surfaces, tables } from './util/defaults.js'
|
|
26
|
+
|
|
27
|
+
export type { VuetiwatchOptions } from './plugin.js'
|
|
28
|
+
export type { VuetiwatchThemeName } from './registry.js'
|
|
29
|
+
export type { DefineThemeOptions } from './util/defineTheme.js'
|
|
30
|
+
export type { VuetiwatchIcons } from './util/defaults.js'
|
|
31
|
+
export type {
|
|
32
|
+
VuetiwatchDefaults,
|
|
33
|
+
VuetiwatchMeta,
|
|
34
|
+
VuetiwatchTheme,
|
|
35
|
+
} from './types.js'
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { watch } from 'vue'
|
|
2
|
+
import type { App, Plugin } from 'vue'
|
|
3
|
+
import type { VuetiwatchDefaults, VuetiwatchTheme } from './types.js'
|
|
4
|
+
import { combine } from './util/defaults.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The `defaults` ref exposed by `createVuetify()`, plus the theme instance
|
|
8
|
+
* we need to follow. Typed structurally so the plugin does not depend on
|
|
9
|
+
* Vuetify's internal types.
|
|
10
|
+
*/
|
|
11
|
+
interface VuetifyLike {
|
|
12
|
+
defaults: { value: VuetiwatchDefaults }
|
|
13
|
+
theme: { global: { name: { value: string } } }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface VuetiwatchOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Themes to manage — normally the package's `themeList`, or just the ones
|
|
19
|
+
* an app ships.
|
|
20
|
+
*
|
|
21
|
+
* Required rather than defaulted, because a default would mean importing
|
|
22
|
+
* the registry here, and a static import cannot be shaken out: every app
|
|
23
|
+
* would carry all fourteen themes even after passing one.
|
|
24
|
+
*/
|
|
25
|
+
themes: readonly VuetiwatchTheme[]
|
|
26
|
+
/**
|
|
27
|
+
* Apply each theme's component defaults on top of your own.
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
defaults?: boolean
|
|
31
|
+
/**
|
|
32
|
+
* Mirror the active theme name onto `<html data-vuetiwatch="...">`, so
|
|
33
|
+
* you can hang your own CSS off it.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
attribute?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Keeps Vuetify's global component defaults in sync with the active theme.
|
|
41
|
+
*
|
|
42
|
+
* Vuetify's `defaults` are global rather than per-theme, so a theme that
|
|
43
|
+
* wants outlined cards or compact tables cannot express that through a
|
|
44
|
+
* `ThemeDefinition` alone. This plugin watches the active theme and swaps
|
|
45
|
+
* those defaults for you, merging them over whatever you passed to
|
|
46
|
+
* `createVuetify({ defaults })`.
|
|
47
|
+
*
|
|
48
|
+
* Install it after Vuetify:
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* app.use(vuetify).use(createVuetiwatch(vuetify))
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createVuetiwatch (
|
|
55
|
+
vuetify: unknown,
|
|
56
|
+
options: VuetiwatchOptions,
|
|
57
|
+
): Plugin {
|
|
58
|
+
const {
|
|
59
|
+
themes,
|
|
60
|
+
defaults: applyDefaults = true,
|
|
61
|
+
attribute = true,
|
|
62
|
+
} = options
|
|
63
|
+
|
|
64
|
+
// Only the defaults are ever read here, so the closure holds those rather
|
|
65
|
+
// than the whole theme objects — palettes and metadata for every theme
|
|
66
|
+
// would otherwise stay pinned for the life of the app.
|
|
67
|
+
const defaultsByName = new Map(themes.map(theme => [theme.name, theme.defaults]))
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
install (_app: App) {
|
|
71
|
+
const instance = vuetify as VuetifyLike
|
|
72
|
+
|
|
73
|
+
if (!instance?.theme?.global?.name) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
'[vuetiwatch] createVuetiwatch() expects the object returned by createVuetify(). ' +
|
|
76
|
+
'Install it after Vuetify: app.use(vuetify).use(createVuetiwatch(vuetify))',
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Whatever the app configured itself — theme defaults layer on top of it.
|
|
81
|
+
const userDefaults = instance.defaults.value ?? {}
|
|
82
|
+
|
|
83
|
+
// The merge is a pure function of the theme name, so it is computed at
|
|
84
|
+
// most once per theme however often the app switches.
|
|
85
|
+
const merged = new Map<string, VuetiwatchDefaults>()
|
|
86
|
+
const defaultsFor = (name: string) => {
|
|
87
|
+
const themeDefaults = defaultsByName.get(name)
|
|
88
|
+
|
|
89
|
+
if (!themeDefaults) return userDefaults
|
|
90
|
+
|
|
91
|
+
let result = merged.get(name)
|
|
92
|
+
|
|
93
|
+
if (!result) {
|
|
94
|
+
result = combine(userDefaults, themeDefaults)
|
|
95
|
+
merged.set(name, result)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
watch(
|
|
102
|
+
() => instance.theme.global.name.value,
|
|
103
|
+
name => {
|
|
104
|
+
if (applyDefaults) {
|
|
105
|
+
instance.defaults.value = defaultsFor(name)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (attribute && typeof document !== 'undefined') {
|
|
109
|
+
document.documentElement.setAttribute('data-vuetiwatch', name)
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
{ immediate: true },
|
|
113
|
+
)
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ThemeDefinition } from 'vuetify'
|
|
2
|
+
import type { VuetiwatchDefaults, VuetiwatchMeta, VuetiwatchTheme } from './types.js'
|
|
3
|
+
import {
|
|
4
|
+
aurora,
|
|
5
|
+
brutalist,
|
|
6
|
+
calm,
|
|
7
|
+
classic,
|
|
8
|
+
clay,
|
|
9
|
+
darkGlass,
|
|
10
|
+
liquidGlass,
|
|
11
|
+
lux,
|
|
12
|
+
morph,
|
|
13
|
+
neon,
|
|
14
|
+
paper,
|
|
15
|
+
sketchy,
|
|
16
|
+
slate,
|
|
17
|
+
soft,
|
|
18
|
+
} from './themes/index.js'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Every theme in the package, in the order a picker should show them:
|
|
22
|
+
* the everyday ones first, then the niche ones.
|
|
23
|
+
*/
|
|
24
|
+
export const themeList = [
|
|
25
|
+
classic,
|
|
26
|
+
paper,
|
|
27
|
+
slate,
|
|
28
|
+
calm,
|
|
29
|
+
lux,
|
|
30
|
+
soft,
|
|
31
|
+
clay,
|
|
32
|
+
morph,
|
|
33
|
+
sketchy,
|
|
34
|
+
brutalist,
|
|
35
|
+
liquidGlass,
|
|
36
|
+
darkGlass,
|
|
37
|
+
aurora,
|
|
38
|
+
neon,
|
|
39
|
+
] as const satisfies readonly VuetiwatchTheme[]
|
|
40
|
+
|
|
41
|
+
export type VuetiwatchThemeName = (typeof themeList)[number]['name']
|
|
42
|
+
|
|
43
|
+
type ByName<T> = Record<VuetiwatchThemeName, T>
|
|
44
|
+
|
|
45
|
+
const themes = {} as ByName<ThemeDefinition>
|
|
46
|
+
const defaults = {} as ByName<VuetiwatchDefaults>
|
|
47
|
+
const meta = {} as ByName<VuetiwatchMeta>
|
|
48
|
+
|
|
49
|
+
for (const theme of themeList) {
|
|
50
|
+
const name = theme.name as VuetiwatchThemeName
|
|
51
|
+
|
|
52
|
+
themes[name] = theme.theme
|
|
53
|
+
defaults[name] = theme.defaults
|
|
54
|
+
meta[name] = theme.meta
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Ready for `createVuetify({ theme: { themes: vuetiwatchThemes } })`. */
|
|
58
|
+
export const vuetiwatchThemes: ByName<ThemeDefinition> = themes
|
|
59
|
+
|
|
60
|
+
/** Per-theme component defaults, applied for you by `createVuetiwatch()`. */
|
|
61
|
+
export const vuetiwatchDefaults: ByName<VuetiwatchDefaults> = defaults
|
|
62
|
+
|
|
63
|
+
/** Titles, descriptions and preview swatches, for building a theme picker. */
|
|
64
|
+
export const vuetiwatchMeta: ByName<VuetiwatchMeta> = meta
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vuetiwatch core layer.
|
|
3
|
+
*
|
|
4
|
+
* Vuetify's radius, border width and button typography are SASS variables
|
|
5
|
+
* baked in at build time, so a `ThemeDefinition` cannot reach them. This
|
|
6
|
+
* file re-expresses them as custom properties that each theme sets through
|
|
7
|
+
* its `variables`, which Vuetify re-emits on `:root` whenever the active
|
|
8
|
+
* theme changes.
|
|
9
|
+
*
|
|
10
|
+
* The scope is either a Vuetiwatch theme class or the `data-vuetiwatch`
|
|
11
|
+
* attribute `createVuetiwatch()` sets on `<html>` — the latter is what
|
|
12
|
+
* extends this layer to themes you build yourself with `defineTheme()`.
|
|
13
|
+
* `VUETIWATCH_THEMES` is a placeholder: the build expands it into one class
|
|
14
|
+
* per registered theme, so this list can never drift from the registry.
|
|
15
|
+
* Importing this file therefore cannot affect an app that is not running a
|
|
16
|
+
* Vuetiwatch theme, and every value falls back to the stock Vuetify one, so
|
|
17
|
+
* a theme only pays for what it overrides.
|
|
18
|
+
*
|
|
19
|
+
* Component classes are doubled to out-specify Vuetify's own rules without
|
|
20
|
+
* relying on an ancestor, which keeps teleported overlays covered.
|
|
21
|
+
*
|
|
22
|
+
* Authored with CSS nesting; the published file is flattened at build time.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
:is([data-vuetiwatch], .v-theme--VUETIWATCH_THEMES) {
|
|
26
|
+
/* -------------------------------------------------------------- radius */
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* An explicit `rounded` prop always wins — hence the first three guards.
|
|
30
|
+
*
|
|
31
|
+
* The last three keep out of containers that own their children's
|
|
32
|
+
* corners: `v-btn-group` squares every child and hands the first and last
|
|
33
|
+
* an inherited radius, `v-toolbar-items` makes buttons full-bleed, and
|
|
34
|
+
* `v-tabs` drives its own radius from `--v-tabs-inset-radius`. Rounding
|
|
35
|
+
* those children individually turns a joined group into separate pills
|
|
36
|
+
* and a full-height toolbar button into a rounded slab.
|
|
37
|
+
*/
|
|
38
|
+
& .v-btn.v-btn:not(
|
|
39
|
+
.v-btn--icon,
|
|
40
|
+
.v-btn--rounded,
|
|
41
|
+
[class*='rounded-'],
|
|
42
|
+
.v-btn-group *,
|
|
43
|
+
.v-toolbar-items *,
|
|
44
|
+
.v-tabs *
|
|
45
|
+
) {
|
|
46
|
+
border-radius: var(--v-vw-radius-btn, var(--v-vw-radius, 4px));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Vuetify's square-inset switch hardcodes 6px and 4px, so a theme with no
|
|
51
|
+
* corners at all still got a rounded toggle. Only this variant follows the
|
|
52
|
+
* theme — the pill inset is meant to stay a pill.
|
|
53
|
+
*/
|
|
54
|
+
& .v-switch--inset-square .v-switch__track {
|
|
55
|
+
border-radius: var(--v-vw-radius, 6px);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
& .v-switch--inset-square .v-switch__thumb {
|
|
59
|
+
border-radius: var(--v-vw-radius, 4px);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A side utility squares one edge so two controls sit flush — a field and
|
|
64
|
+
* the button on its end. It says nothing about the other corners, which
|
|
65
|
+
* were left at Vuetify's stock 4px because the `rounded-` guard above
|
|
66
|
+
* treats any such class as the author taking over.
|
|
67
|
+
*
|
|
68
|
+
* So set only the corners the utility leaves alone. Vuetify's own rule
|
|
69
|
+
* keeps owning the squared edge, which means nothing here has to fight
|
|
70
|
+
* it, and the logical properties keep that edge leading in RTL.
|
|
71
|
+
*/
|
|
72
|
+
& :is(.rounded-s-0, .rounded-e-0, .rounded-t-0, .rounded-b-0) {
|
|
73
|
+
--vw-attached: var(--v-vw-radius, 4px);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
& .v-btn:is(.rounded-s-0, .rounded-e-0, .rounded-t-0, .rounded-b-0) {
|
|
77
|
+
--vw-attached: var(--v-vw-radius-btn, var(--v-vw-radius, 4px));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
& .rounded-s-0 {
|
|
81
|
+
border-start-end-radius: var(--vw-attached);
|
|
82
|
+
border-end-end-radius: var(--vw-attached);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
& .rounded-e-0 {
|
|
86
|
+
border-start-start-radius: var(--vw-attached);
|
|
87
|
+
border-end-start-radius: var(--vw-attached);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
& .rounded-t-0 {
|
|
91
|
+
border-end-start-radius: var(--vw-attached);
|
|
92
|
+
border-end-end-radius: var(--vw-attached);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
& .rounded-b-0 {
|
|
96
|
+
border-start-start-radius: var(--vw-attached);
|
|
97
|
+
border-start-end-radius: var(--vw-attached);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* The group keeps the corners its children gave up. */
|
|
101
|
+
& .v-btn-group.v-btn-group:not([class*='rounded-']) {
|
|
102
|
+
border-radius: var(--v-vw-radius, 4px);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Pagination items are `icon` buttons so the rule above leaves them
|
|
107
|
+
* round, but they read as a row of tiles rather than icons — they follow
|
|
108
|
+
* the button radius instead.
|
|
109
|
+
*/
|
|
110
|
+
& .v-pagination .v-btn.v-btn {
|
|
111
|
+
border-radius: var(--v-vw-radius-btn, var(--v-vw-radius, 4px));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The same containers join their buttons into one shape, so a per-button
|
|
116
|
+
* shadow draws a seam down every join. Decoration belongs on the
|
|
117
|
+
* container — this out-specifies the theme layers that give `.v-btn` a
|
|
118
|
+
* relief, and a theme that wants a decorated group styles
|
|
119
|
+
* `.v-btn-group` itself.
|
|
120
|
+
*/
|
|
121
|
+
& :is(.v-btn-group, .v-toolbar-items) .v-btn.v-btn {
|
|
122
|
+
box-shadow: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
& :is(
|
|
126
|
+
.v-card.v-card:not([class*='rounded-']),
|
|
127
|
+
.v-table.v-table
|
|
128
|
+
) {
|
|
129
|
+
border-radius: var(--v-vw-radius-lg, var(--v-vw-radius, 4px));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Panels inherit their corners from the group, and Vuetify decides which
|
|
134
|
+
* of them are rounded from `@layer vuetify-overrides`. Layered rules lose
|
|
135
|
+
* to unlayered ones at any specificity, so styling `.v-expansion-panel`
|
|
136
|
+
* here would flatten that logic and round every panel in the stack.
|
|
137
|
+
*/
|
|
138
|
+
& .v-expansion-panels.v-expansion-panels {
|
|
139
|
+
border-radius: var(--v-vw-radius-lg, var(--v-vw-radius, 4px));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
& :is(
|
|
143
|
+
.v-alert.v-alert:not([class*='rounded-']),
|
|
144
|
+
.v-field.v-field:not([class*='rounded-']),
|
|
145
|
+
.v-list-item.v-list-item:not([class*='rounded-']),
|
|
146
|
+
.v-snackbar__wrapper,
|
|
147
|
+
.v-menu > .v-overlay__content,
|
|
148
|
+
.v-tooltip > .v-overlay__content,
|
|
149
|
+
.v-overlay__content > :is(.v-list, .v-sheet)
|
|
150
|
+
) {
|
|
151
|
+
border-radius: var(--v-vw-radius, 4px);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Filled and underlined fields only round their top corners. */
|
|
155
|
+
& .v-field.v-field:is(.v-field--variant-filled, .v-field--variant-underlined):not([class*='rounded-']) {
|
|
156
|
+
border-radius: var(--v-vw-radius, 4px) var(--v-vw-radius, 4px) 0 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
& .v-chip.v-chip:not([class*='rounded-']) {
|
|
160
|
+
border-radius: var(--v-vw-radius-chip, 9999px);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The slider track is a bar like any other, so it follows the theme
|
|
165
|
+
* radius. The thumb keeps its own variable because round is right for
|
|
166
|
+
* most themes even when nothing else is — only the square-cornered ones
|
|
167
|
+
* set it.
|
|
168
|
+
*/
|
|
169
|
+
& .v-slider-track.v-slider-track {
|
|
170
|
+
border-radius: var(--v-vw-radius, 4px);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
& .v-slider-thumb__surface {
|
|
174
|
+
border-radius: var(--v-vw-radius-thumb, 50%);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
& .v-slider-thumb__label {
|
|
178
|
+
border-radius: var(--v-vw-radius, 4px);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* --------------------------------------------------- active pagination */
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Vuetify ties the active page's overlay to `--v-border-opacity`, which
|
|
185
|
+
* is not what that variable means: a theme with solid borders gets an
|
|
186
|
+
* opaque block over the page number, and a theme with invisible ones
|
|
187
|
+
* loses the active state entirely. `--v-activated-opacity` is the
|
|
188
|
+
* variable for an activated item, so use that instead.
|
|
189
|
+
*/
|
|
190
|
+
& .v-pagination .v-pagination__item--is-active .v-btn__overlay {
|
|
191
|
+
background: var(--v-vw-pagination-active-color, currentColor);
|
|
192
|
+
opacity: var(--v-vw-pagination-active-opacity, var(--v-activated-opacity));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* --------------------------------------------------- overlay surfaces */
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* A floating surface has to be opaque. Vuetify renders the outlined, text,
|
|
199
|
+
* plain and tonal card variants transparent, so a theme that defaults
|
|
200
|
+
* `VCard` to one of them leaves dialogs and menus showing the page through
|
|
201
|
+
* them. Elevated and flat cards already paint themselves — and `darkGlass`
|
|
202
|
+
* depends on that to stay translucent — so they are left alone, as is any
|
|
203
|
+
* card given an explicit `color`.
|
|
204
|
+
*/
|
|
205
|
+
& .v-overlay__content > .v-card:is(
|
|
206
|
+
.v-card--variant-outlined,
|
|
207
|
+
.v-card--variant-text,
|
|
208
|
+
.v-card--variant-plain,
|
|
209
|
+
.v-card--variant-tonal
|
|
210
|
+
):not([class*='bg-']) {
|
|
211
|
+
background-color: rgb(var(--v-theme-surface));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* -------------------------------------------------------- border width */
|
|
215
|
+
|
|
216
|
+
& :is(
|
|
217
|
+
.v-btn--variant-outlined,
|
|
218
|
+
.v-card--variant-outlined,
|
|
219
|
+
.v-alert--variant-outlined,
|
|
220
|
+
.v-chip--variant-outlined
|
|
221
|
+
) {
|
|
222
|
+
border-width: var(--v-vw-border-width, thin);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Vuetify already funnels every outline segment through one property, so
|
|
227
|
+
* the field only needs that one set. Specificity matters here: this must
|
|
228
|
+
* beat `.v-field__outline` (0,1,0) but lose to the focused rule (0,3,0)
|
|
229
|
+
* that thickens the outline to 2px.
|
|
230
|
+
*/
|
|
231
|
+
& .v-field__outline {
|
|
232
|
+
--v-field-border-width: var(--v-vw-border-width, 1px);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* ---------------------------------------------------- button typography */
|
|
236
|
+
|
|
237
|
+
& .v-btn.v-btn {
|
|
238
|
+
font-weight: var(--v-vw-btn-weight, 500);
|
|
239
|
+
letter-spacing: var(--v-vw-btn-tracking, 0.0892857143em);
|
|
240
|
+
text-transform: var(--v-vw-btn-transform, uppercase);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ------------------------------------------------- shared theme surfaces */
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Five ideas below were written out in seven to ten theme stylesheets
|
|
247
|
+
* each, and several copies had already drifted apart. Core owns the
|
|
248
|
+
* selector once; a theme supplies a value from its `variables`, and a
|
|
249
|
+
* theme built with `defineTheme()` gets the same knobs for free.
|
|
250
|
+
*/
|
|
251
|
+
|
|
252
|
+
/* Every floating surface, so a tooltip is not left behind by a menu. */
|
|
253
|
+
& :is(.v-menu, .v-dialog, .v-tooltip) > .v-overlay__content {
|
|
254
|
+
border: var(--v-vw-overlay-border, none);
|
|
255
|
+
box-shadow: var(--v-vw-overlay-shadow, revert-layer);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
& .v-tab__slider {
|
|
259
|
+
height: var(--v-vw-tab-slider-height, 2px);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
& :is(.v-table thead th, .v-data-table__th) {
|
|
263
|
+
font-weight: var(--v-vw-th-weight, revert-layer);
|
|
264
|
+
letter-spacing: var(--v-vw-th-tracking, revert-layer);
|
|
265
|
+
text-transform: var(--v-vw-th-transform, none);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* A logical property, so the bar sits on the leading edge in both
|
|
270
|
+
* directions — the three themes that hand-rolled this each carried a
|
|
271
|
+
* manual RTL mirror that only won by accident of specificity.
|
|
272
|
+
*/
|
|
273
|
+
& .v-list-item.v-list-item--active {
|
|
274
|
+
border-inline-start: var(--v-vw-list-bar, 0) solid var(--v-vw-list-bar-color, currentColor);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ------------------------------------------------------------ headings */
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* `--v-font-heading` is native to Vuetify 4 and already drives the
|
|
281
|
+
* `text-display-*` / `text-headline-*` utilities. Bare heading tags are not, so they
|
|
282
|
+
* pick it up here.
|
|
283
|
+
*/
|
|
284
|
+
& :is(
|
|
285
|
+
h1, h2, h3, h4, h5, h6,
|
|
286
|
+
[class*='-display-'],
|
|
287
|
+
[class*='-headline-'],
|
|
288
|
+
[class*='-title-large']
|
|
289
|
+
) {
|
|
290
|
+
font-family: var(--v-font-heading, var(--v-font-body, inherit));
|
|
291
|
+
font-weight: var(--v-vw-heading-weight, revert-layer);
|
|
292
|
+
letter-spacing: var(--v-vw-heading-tracking, revert-layer);
|
|
293
|
+
text-transform: var(--v-vw-heading-transform, none);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
&.v-application {
|
|
297
|
+
-webkit-font-smoothing: antialiased;
|
|
298
|
+
-moz-osx-font-smoothing: grayscale;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Vuetify styles anchors it renders itself, but never a bare `<a>` in
|
|
303
|
+
* content — which left every theme showing browser blue. Components keep
|
|
304
|
+
* their own colours because they all carry classes.
|
|
305
|
+
*/
|
|
306
|
+
& a:not([class]) {
|
|
307
|
+
color: rgb(var(--v-theme-primary));
|
|
308
|
+
text-decoration: var(--v-vw-link-decoration, underline);
|
|
309
|
+
text-underline-offset: 0.15em;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every Vuetiwatch stylesheet in one import.
|
|
3
|
+
*
|
|
4
|
+
* The core layer is generic and variable-driven; each theme file adds only
|
|
5
|
+
* the surface treatment — shadows, blur, glow, relief — that a
|
|
6
|
+
* `ThemeDefinition` cannot express. Rules are scoped per theme, so
|
|
7
|
+
* importing the whole file costs nothing for themes you do not use.
|
|
8
|
+
*
|
|
9
|
+
* `classic` has no file: it is stock Vuetify by definition.
|
|
10
|
+
*/
|
|
11
|
+
@import './core.css';
|
|
12
|
+
|
|
13
|
+
/* Everyday */
|
|
14
|
+
@import './themes/paper.css';
|
|
15
|
+
@import './themes/slate.css';
|
|
16
|
+
@import './themes/calm.css';
|
|
17
|
+
@import './themes/lux.css';
|
|
18
|
+
@import './themes/soft.css';
|
|
19
|
+
|
|
20
|
+
/* Tactile */
|
|
21
|
+
@import './themes/clay.css';
|
|
22
|
+
@import './themes/morph.css';
|
|
23
|
+
|
|
24
|
+
/* Expressive */
|
|
25
|
+
@import './themes/sketchy.css';
|
|
26
|
+
@import './themes/brutalist.css';
|
|
27
|
+
@import './themes/liquidGlass.css';
|
|
28
|
+
@import './themes/darkGlass.css';
|
|
29
|
+
@import './themes/aurora.css';
|
|
30
|
+
@import './themes/neon.css';
|