Skip to content

Commit abfdb77

Browse files
committed
refactor(theme): add internal methods for testing tw implementation
1 parent 3190331 commit abfdb77

File tree

3 files changed

+81
-27
lines changed

3 files changed

+81
-27
lines changed

packages/api-generator/src/locale/en/useTheme.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
"global": "Reference to the global theme instance.",
99
"isDisabled": "Indicates if theming is disabled.",
1010
"name": "Name of current theme.",
11+
"prefix": "**FOR INTERNAL USE ONLY**",
12+
"scoped": "**FOR INTERNAL USE ONLY**",
1113
"styles": "**FOR INTERNAL USE ONLY**",
1214
"themeClasses": "**FOR INTERNAL USE ONLY**",
1315
"themes": "Raw theme definitions.",
14-
"unimportant": "Generate utility classes without the !important declaration."
16+
"unimportant": "Generate utility classes without the !important declaration.",
17+
"utilities": "**FOR INTERNAL USE ONLY**"
1518
}
1619
}

packages/vuetify/src/composables/__tests__/theme.spec.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,49 @@ describe('createTheme', () => {
281281
theme.install(app)
282282

283283
const stylesheet = document.getElementById('vuetify-theme-stylesheet')
284-
285284
const css = stylesheet!.innerHTML
286285

287286
expect(css).not.toContain('!important')
288287
})
288+
289+
it('should generate utility classes with a custom prefix', async () => {
290+
// @ts-expect-error next-line
291+
const theme = createTheme({ prefix: 'custom-' })
292+
293+
theme.install(app)
294+
295+
const stylesheet = document.getElementById('vuetify-theme-stylesheet')
296+
const css = stylesheet!.innerHTML
297+
298+
expect(css).not.toContain('--v-theme-primary')
299+
expect(css).toContain('--custom-theme-primary')
300+
})
301+
302+
it('should use defined prefix for utility classes', async () => {
303+
// @ts-expect-error next-line
304+
const theme = createTheme({ prefix: 'custom-', scoped: true })
305+
306+
theme.install(app)
307+
308+
const stylesheet = document.getElementById('vuetify-theme-stylesheet')
309+
const css = stylesheet!.innerHTML
310+
311+
expect(css).toContain('.custom-bg-primary')
312+
expect(css).toContain('.custom-text-primary')
313+
expect(css).toContain('.custom-border-primary')
314+
})
315+
316+
it('should not generate utility classes if disabled', async () => {
317+
// @ts-expect-error next-line
318+
const theme = createTheme({ utilities: false })
319+
320+
theme.install(app)
321+
322+
const stylesheet = document.getElementById('vuetify-theme-stylesheet')
323+
const css = stylesheet!.innerHTML
324+
325+
expect(css).not.toContain('.bg-primary')
326+
expect(css).not.toContain('.text-primary')
327+
expect(css).not.toContain('.border-primary')
328+
})
289329
})

packages/vuetify/src/composables/theme.ts

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,14 @@ interface InternalThemeOptions {
4646
cspNonce?: string
4747
isDisabled: boolean
4848
defaultTheme: string
49+
prefix: string
4950
variations: false | VariationsOptions
5051
themes: Record<string, InternalThemeDefinition>
5152
stylesheetId: string
5253
scope?: string
54+
scoped: boolean
5355
unimportant: boolean
56+
utilities: boolean
5457
}
5558

5659
interface VariationsOptions {
@@ -102,6 +105,7 @@ export interface ThemeInstance {
102105
readonly name: Readonly<Ref<string>>
103106
readonly current: DeepReadonly<Ref<InternalThemeDefinition>>
104107
readonly computedThemes: DeepReadonly<Ref<Record<string, InternalThemeDefinition>>>
108+
readonly prefix: string
105109

106110
readonly themeClasses: Readonly<Ref<string | undefined>>
107111
readonly styles: Readonly<Ref<string>>
@@ -121,6 +125,7 @@ export const makeThemeProps = propsFactory({
121125
function genDefaults () {
122126
return {
123127
defaultTheme: 'light',
128+
prefix: 'v-',
124129
variations: { colors: [], lighten: 0, darken: 0 },
125130
themes: {
126131
light: {
@@ -199,7 +204,9 @@ function genDefaults () {
199204
},
200205
},
201206
stylesheetId: 'vuetify-theme-stylesheet',
207+
scoped: false,
202208
unimportant: false,
209+
utilities: true,
203210
}
204211
}
205212

@@ -230,23 +237,23 @@ function createCssClass (lines: string[], selector: string, content: string[], s
230237
)
231238
}
232239

233-
function genCssVariables (theme: InternalThemeDefinition) {
240+
function genCssVariables (theme: InternalThemeDefinition, prefix: string) {
234241
const lightOverlay = theme.dark ? 2 : 1
235242
const darkOverlay = theme.dark ? 1 : 2
236243

237244
const variables: string[] = []
238245
for (const [key, value] of Object.entries(theme.colors)) {
239246
const rgb = parseColor(value)
240-
variables.push(`--v-theme-${key}: ${rgb.r},${rgb.g},${rgb.b}`)
247+
variables.push(`--${prefix}theme-${key}: ${rgb.r},${rgb.g},${rgb.b}`)
241248
if (!key.startsWith('on-')) {
242-
variables.push(`--v-theme-${key}-overlay-multiplier: ${getLuma(value) > 0.18 ? lightOverlay : darkOverlay}`)
249+
variables.push(`--${prefix}theme-${key}-overlay-multiplier: ${getLuma(value) > 0.18 ? lightOverlay : darkOverlay}`)
243250
}
244251
}
245252

246253
for (const [key, value] of Object.entries(theme.variables)) {
247254
const color = typeof value === 'string' && value.startsWith('#') ? parseColor(value) : undefined
248255
const rgb = color ? `${color.r}, ${color.g}, ${color.b}` : undefined
249-
variables.push(`--v-${key}: ${rgb ?? value}`)
256+
variables.push(`--${prefix}${key}: ${rgb ?? value}`)
250257
}
251258

252259
return variables
@@ -361,44 +368,47 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install:
361368
const styles = computed(() => {
362369
const lines: string[] = []
363370
const important = parsedOptions.unimportant ? '' : ' !important'
371+
const scoped = parsedOptions.scoped ? parsedOptions.prefix : ''
364372

365373
if (current.value?.dark) {
366374
createCssClass(lines, ':root', ['color-scheme: dark'], parsedOptions.scope)
367375
}
368376

369-
createCssClass(lines, ':root', genCssVariables(current.value), parsedOptions.scope)
377+
createCssClass(lines, ':root', genCssVariables(current.value, parsedOptions.prefix), parsedOptions.scope)
370378

371379
for (const [themeName, theme] of Object.entries(computedThemes.value)) {
372-
createCssClass(lines, `.v-theme--${themeName}`, [
380+
createCssClass(lines, `.${parsedOptions.prefix}theme--${themeName}`, [
373381
`color-scheme: ${theme.dark ? 'dark' : 'normal'}`,
374-
...genCssVariables(theme),
382+
...genCssVariables(theme, parsedOptions.prefix),
375383
], parsedOptions.scope)
376384
}
377385

378-
const bgLines: string[] = []
379-
const fgLines: string[] = []
386+
if (parsedOptions.utilities) {
387+
const bgLines: string[] = []
388+
const fgLines: string[] = []
380389

381-
const colors = new Set(Object.values(computedThemes.value).flatMap(theme => Object.keys(theme.colors)))
382-
for (const key of colors) {
383-
if (key.startsWith('on-')) {
384-
createCssClass(fgLines, `.${key}`, [`color: rgb(var(--v-theme-${key}))${important}`], parsedOptions.scope)
385-
} else {
386-
createCssClass(bgLines, `.bg-${key}`, [
387-
`--v-theme-overlay-multiplier: var(--v-theme-${key}-overlay-multiplier)`,
388-
`background-color: rgb(var(--v-theme-${key}))${important}`,
389-
`color: rgb(var(--v-theme-on-${key}))${important}`,
390-
], parsedOptions.scope)
391-
createCssClass(fgLines, `.text-${key}`, [`color: rgb(var(--v-theme-${key}))${important}`], parsedOptions.scope)
392-
createCssClass(fgLines, `.border-${key}`, [`--v-border-color: var(--v-theme-${key})`], parsedOptions.scope)
390+
const colors = new Set(Object.values(computedThemes.value).flatMap(theme => Object.keys(theme.colors)))
391+
for (const key of colors) {
392+
if (key.startsWith('on-')) {
393+
createCssClass(fgLines, `.${key}`, [`color: rgb(var(--${parsedOptions.prefix}theme-${key}))${important}`], parsedOptions.scope)
394+
} else {
395+
createCssClass(bgLines, `.${scoped}bg-${key}`, [
396+
`--${parsedOptions.prefix}theme-overlay-multiplier: var(--${parsedOptions.prefix}theme-${key}-overlay-multiplier)`,
397+
`background-color: rgb(var(--${parsedOptions.prefix}theme-${key}))${important}`,
398+
`color: rgb(var(--${parsedOptions.prefix}theme-on-${key}))${important}`,
399+
], parsedOptions.scope)
400+
createCssClass(fgLines, `.${scoped}text-${key}`, [`color: rgb(var(--${parsedOptions.prefix}theme-${key}))${important}`], parsedOptions.scope)
401+
createCssClass(fgLines, `.${scoped}border-${key}`, [`--${parsedOptions.prefix}border-color: var(--${parsedOptions.prefix}theme-${key})`], parsedOptions.scope)
402+
}
393403
}
394-
}
395404

396-
lines.push(...bgLines, ...fgLines)
405+
lines.push(...bgLines, ...fgLines)
406+
}
397407

398408
return lines.map((str, i) => i === 0 ? str : ` ${str}`).join('')
399409
})
400410

401-
const themeClasses = computed(() => parsedOptions.isDisabled ? undefined : `v-theme--${name.value}`)
411+
const themeClasses = computed(() => parsedOptions.isDisabled ? undefined : `${parsedOptions.prefix}theme--${name.value}`)
402412
const themeNames = computed(() => Object.keys(computedThemes.value))
403413

404414
function install (app: App) {
@@ -486,6 +496,7 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install:
486496
themes,
487497
current,
488498
computedThemes,
499+
prefix: parsedOptions.prefix,
489500
themeClasses,
490501
styles,
491502
global: {
@@ -505,7 +516,7 @@ export function provideTheme (props: { theme?: string }) {
505516
const name = computed(() => props.theme ?? theme.name.value)
506517
const current = computed(() => theme.themes.value[name.value])
507518

508-
const themeClasses = computed(() => theme.isDisabled ? undefined : `v-theme--${name.value}`)
519+
const themeClasses = computed(() => theme.isDisabled ? undefined : `${theme.prefix}theme--${name.value}`)
509520

510521
const newTheme: ThemeInstance = {
511522
...theme,

0 commit comments

Comments
 (0)