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.
Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +232 -0
  3. package/dist/index.d.ts +11 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +6 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/plugin.d.ts +41 -0
  8. package/dist/plugin.d.ts.map +1 -0
  9. package/dist/plugin.js +58 -0
  10. package/dist/plugin.js.map +1 -0
  11. package/dist/registry.d.ts +17 -0
  12. package/dist/registry.d.ts.map +1 -0
  13. package/dist/registry.js +37 -0
  14. package/dist/registry.js.map +1 -0
  15. package/dist/styles/aurora.css +74 -0
  16. package/dist/styles/aurora.min.css +1 -0
  17. package/dist/styles/brutalist.css +48 -0
  18. package/dist/styles/brutalist.min.css +1 -0
  19. package/dist/styles/calm.css +21 -0
  20. package/dist/styles/calm.min.css +1 -0
  21. package/dist/styles/clay.css +44 -0
  22. package/dist/styles/clay.min.css +1 -0
  23. package/dist/styles/core.css +140 -0
  24. package/dist/styles/core.min.css +1 -0
  25. package/dist/styles/darkGlass.css +43 -0
  26. package/dist/styles/darkGlass.min.css +1 -0
  27. package/dist/styles/liquidGlass.css +100 -0
  28. package/dist/styles/liquidGlass.min.css +1 -0
  29. package/dist/styles/lux.css +21 -0
  30. package/dist/styles/lux.min.css +1 -0
  31. package/dist/styles/morph.css +37 -0
  32. package/dist/styles/morph.min.css +1 -0
  33. package/dist/styles/neon.css +41 -0
  34. package/dist/styles/neon.min.css +1 -0
  35. package/dist/styles/paper.css +17 -0
  36. package/dist/styles/paper.min.css +1 -0
  37. package/dist/styles/sketchy.css +25 -0
  38. package/dist/styles/sketchy.min.css +1 -0
  39. package/dist/styles/slate.css +16 -0
  40. package/dist/styles/slate.min.css +1 -0
  41. package/dist/styles/soft.css +46 -0
  42. package/dist/styles/soft.min.css +1 -0
  43. package/dist/styles.css +686 -0
  44. package/dist/styles.min.css +1 -0
  45. package/dist/themes/aurora.d.ts +10 -0
  46. package/dist/themes/aurora.d.ts.map +1 -0
  47. package/dist/themes/aurora.js +86 -0
  48. package/dist/themes/aurora.js.map +1 -0
  49. package/dist/themes/brutalist.d.ts +10 -0
  50. package/dist/themes/brutalist.d.ts.map +1 -0
  51. package/dist/themes/brutalist.js +103 -0
  52. package/dist/themes/brutalist.js.map +1 -0
  53. package/dist/themes/calm.d.ts +11 -0
  54. package/dist/themes/calm.d.ts.map +1 -0
  55. package/dist/themes/calm.js +88 -0
  56. package/dist/themes/calm.js.map +1 -0
  57. package/dist/themes/classic.d.ts +6 -0
  58. package/dist/themes/classic.d.ts.map +1 -0
  59. package/dist/themes/classic.js +23 -0
  60. package/dist/themes/classic.js.map +1 -0
  61. package/dist/themes/clay.d.ts +8 -0
  62. package/dist/themes/clay.d.ts.map +1 -0
  63. package/dist/themes/clay.js +80 -0
  64. package/dist/themes/clay.js.map +1 -0
  65. package/dist/themes/darkGlass.d.ts +7 -0
  66. package/dist/themes/darkGlass.d.ts.map +1 -0
  67. package/dist/themes/darkGlass.js +77 -0
  68. package/dist/themes/darkGlass.js.map +1 -0
  69. package/dist/themes/index.d.ts +15 -0
  70. package/dist/themes/index.d.ts.map +1 -0
  71. package/dist/themes/index.js +15 -0
  72. package/dist/themes/index.js.map +1 -0
  73. package/dist/themes/liquidGlass.d.ts +19 -0
  74. package/dist/themes/liquidGlass.d.ts.map +1 -0
  75. package/dist/themes/liquidGlass.js +111 -0
  76. package/dist/themes/liquidGlass.js.map +1 -0
  77. package/dist/themes/lux.d.ts +8 -0
  78. package/dist/themes/lux.d.ts.map +1 -0
  79. package/dist/themes/lux.js +91 -0
  80. package/dist/themes/lux.js.map +1 -0
  81. package/dist/themes/morph.d.ts +10 -0
  82. package/dist/themes/morph.d.ts.map +1 -0
  83. package/dist/themes/morph.js +82 -0
  84. package/dist/themes/morph.js.map +1 -0
  85. package/dist/themes/neon.d.ts +7 -0
  86. package/dist/themes/neon.d.ts.map +1 -0
  87. package/dist/themes/neon.js +96 -0
  88. package/dist/themes/neon.js.map +1 -0
  89. package/dist/themes/paper.d.ts +7 -0
  90. package/dist/themes/paper.d.ts.map +1 -0
  91. package/dist/themes/paper.js +88 -0
  92. package/dist/themes/paper.js.map +1 -0
  93. package/dist/themes/sketchy.d.ts +11 -0
  94. package/dist/themes/sketchy.d.ts.map +1 -0
  95. package/dist/themes/sketchy.js +88 -0
  96. package/dist/themes/sketchy.js.map +1 -0
  97. package/dist/themes/slate.d.ts +7 -0
  98. package/dist/themes/slate.d.ts.map +1 -0
  99. package/dist/themes/slate.js +89 -0
  100. package/dist/themes/slate.js.map +1 -0
  101. package/dist/themes/soft.d.ts +7 -0
  102. package/dist/themes/soft.d.ts.map +1 -0
  103. package/dist/themes/soft.js +84 -0
  104. package/dist/themes/soft.js.map +1 -0
  105. package/dist/types.d.ts +40 -0
  106. package/dist/types.d.ts.map +1 -0
  107. package/dist/types.js +2 -0
  108. package/dist/types.js.map +1 -0
  109. package/dist/util/defaults.d.ts +62 -0
  110. package/dist/util/defaults.d.ts.map +1 -0
  111. package/dist/util/defaults.js +100 -0
  112. package/dist/util/defaults.js.map +1 -0
  113. package/dist/util/defineTheme.d.ts +21 -0
  114. package/dist/util/defineTheme.d.ts.map +1 -0
  115. package/dist/util/defineTheme.js +25 -0
  116. package/dist/util/defineTheme.js.map +1 -0
  117. package/dist/util/merge.d.ts +8 -0
  118. package/dist/util/merge.d.ts.map +1 -0
  119. package/dist/util/merge.js +17 -0
  120. package/dist/util/merge.js.map +1 -0
  121. package/package.json +75 -0
  122. package/src/index.ts +35 -0
  123. package/src/plugin.ts +116 -0
  124. package/src/registry.ts +64 -0
  125. package/src/styles/core.css +311 -0
  126. package/src/styles/index.css +30 -0
  127. package/src/styles/themes/aurora.css +86 -0
  128. package/src/styles/themes/brutalist.css +59 -0
  129. package/src/styles/themes/calm.css +31 -0
  130. package/src/styles/themes/clay.css +60 -0
  131. package/src/styles/themes/darkGlass.css +69 -0
  132. package/src/styles/themes/liquidGlass.css +172 -0
  133. package/src/styles/themes/lux.css +31 -0
  134. package/src/styles/themes/morph.css +53 -0
  135. package/src/styles/themes/neon.css +56 -0
  136. package/src/styles/themes/paper.css +32 -0
  137. package/src/styles/themes/sketchy.css +34 -0
  138. package/src/styles/themes/slate.css +26 -0
  139. package/src/styles/themes/soft.css +63 -0
  140. package/src/themes/aurora.ts +86 -0
  141. package/src/themes/brutalist.ts +103 -0
  142. package/src/themes/calm.ts +88 -0
  143. package/src/themes/classic.ts +23 -0
  144. package/src/themes/clay.ts +80 -0
  145. package/src/themes/darkGlass.ts +77 -0
  146. package/src/themes/index.ts +14 -0
  147. package/src/themes/liquidGlass.ts +111 -0
  148. package/src/themes/lux.ts +91 -0
  149. package/src/themes/morph.ts +82 -0
  150. package/src/themes/neon.ts +96 -0
  151. package/src/themes/paper.ts +88 -0
  152. package/src/themes/sketchy.ts +88 -0
  153. package/src/themes/slate.ts +89 -0
  154. package/src/themes/soft.ts +84 -0
  155. package/src/types.ts +44 -0
  156. package/src/util/defaults.ts +164 -0
  157. package/src/util/defineTheme.ts +49 -0
  158. 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
+ }
@@ -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';