@zl-ui-kit/theme 0.0.0-stage → 0.3.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 +31 -2
- package/dist/index.d.ts +86 -0
- package/dist/index.js +1332 -0
- package/package.json +66 -3
- package/src/_json/blue.json +226 -0
- package/src/_json/dark.json +226 -0
- package/src/_json/light.json +226 -0
- package/src/_json/red-dark.json +226 -0
- package/src/_json/red-light.json +226 -0
- package/src/assets/fonts/NOTOSANS-OFL.txt +93 -0
- package/src/assets/fonts/SMILEYSANS-OFL.txt +6 -0
- package/src/assets/fonts/noto-sans-sc-400.woff2 +0 -0
- package/src/assets/fonts/noto-sans-sc-500.woff2 +0 -0
- package/src/assets/fonts/noto-sans-sc-700.woff2 +0 -0
- package/src/assets/fonts/smiley-sans.woff2 +0 -0
- package/src/common/reset.css +100 -0
- package/src/generated/_constants.scss +3 -0
- package/src/generated/constants.ts +5 -0
- package/src/generated/theme-blue.css +447 -0
- package/src/generated/theme-dark.css +447 -0
- package/src/generated/theme-light.css +447 -0
- package/src/generated/theme-red-dark.css +447 -0
- package/src/generated/theme-red-light.css +447 -0
- package/src/generated/themes-data.json +1122 -0
- package/src/generated/themes.css +1654 -0
- package/src/index.ts +12 -0
- package/src/overrides/components.css +15 -0
- package/src/runtime/api.ts +247 -0
- package/src/runtime/colors.ts +61 -0
- package/src/utils/colors.scss +27 -0
- package/src/utils/map.scss +16 -0
- package/src/utils/theme.scss +13 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* @zl-ui-kit/theme 入口(运行时 API + 常量)
|
|
4
|
+
* 主题 CSS 入口:@zl-ui-kit/theme/light|dark|red-light|red-dark|blue 或 @zl-ui-kit/theme/themes.css
|
|
5
|
+
* SCSS 工具入口:@zl-ui-kit/theme/scss
|
|
6
|
+
* 自定义主题:见 docs/主题定制指南.md(L1 变量直覆 / L2 defineTheme / L3 构建时生成)
|
|
7
|
+
* ============================================================
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { COLOR_TYPES, type ColorType } from './generated/constants'
|
|
11
|
+
export * from './runtime/api'
|
|
12
|
+
export { mix, generateElShades } from './runtime/colors'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Element Plus 组件样式覆盖(占位骨架)
|
|
3
|
+
* 参考实现的 18 个组件覆盖基于原 UI 库类名体系,需按 EP 的 .el-* BEM 结构逐组件重写,
|
|
4
|
+
* 以 playground 主题预览页视觉验收为准(需求 §7.2.2 / P15)。
|
|
5
|
+
* 本文件随 per-theme CSS 一同生效;以下为首批全局性微调。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* 与 zl 设计一致的圆角与动效基调 */
|
|
9
|
+
.el-button {
|
|
10
|
+
--el-button-border-radius: var(--zl-radius-base, 4px);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.el-dialog {
|
|
14
|
+
--el-dialog-border-radius: var(--zl-radius-base, 4px);
|
|
15
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @zl-ui-kit/theme 运行时 API(需求 §7.2.3,D7)
|
|
3
|
+
* L2:defineTheme 运行时定义主题 + setTheme/getTheme/useTheme 切换(可选持久化)
|
|
4
|
+
* 自定义主题与内置主题变量完全同构(--zl-* + --el-*),components 无差别渲染
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import themesDataRaw from '../generated/themes-data.json'
|
|
8
|
+
import { generateElShades, mix } from './colors'
|
|
9
|
+
import type { ComputedRef } from 'vue'
|
|
10
|
+
import { computed, readonly, ref } from 'vue'
|
|
11
|
+
|
|
12
|
+
/** 各主题解析后的 token 值表(构建期由 themegen 生成) */
|
|
13
|
+
export const themesData = themesDataRaw as Record<string, Record<string, string>>
|
|
14
|
+
|
|
15
|
+
/** 内置主题清单(数据源与 generated/constants.ts 一致) */
|
|
16
|
+
export const THEMES = Object.keys(themesData) as ThemeName[]
|
|
17
|
+
|
|
18
|
+
export type ThemeName = string
|
|
19
|
+
|
|
20
|
+
export interface DefineThemeColors {
|
|
21
|
+
/** 主色(EP primary + --zl-primary-6 联动派生) */
|
|
22
|
+
primary?: string
|
|
23
|
+
success?: string
|
|
24
|
+
warning?: string
|
|
25
|
+
danger?: string
|
|
26
|
+
info?: string
|
|
27
|
+
/** 页面底色(覆盖 --zl-bg-1..5 / --el-bg-*) */
|
|
28
|
+
bg?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DefineThemeOptions {
|
|
32
|
+
/** 主题名(切换时使用) */
|
|
33
|
+
name: string
|
|
34
|
+
/** 以哪套内置主题为基线(默认 'light'),未指定的 token 全部继承 */
|
|
35
|
+
base?: string
|
|
36
|
+
/** 少量语义色定制,其余自动派生 */
|
|
37
|
+
colors?: DefineThemeColors
|
|
38
|
+
/** 任意 token 覆盖(键为原始 token 名,如 color-text-1 / color-modo-6) */
|
|
39
|
+
tokens?: Record<string, string>
|
|
40
|
+
/** 是否立即切换到该主题 */
|
|
41
|
+
apply?: boolean
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const THEME_STORAGE_KEY = 'zl-theme'
|
|
45
|
+
const customThemes = new Map<string, { cssText: string }>()
|
|
46
|
+
const currentTheme = ref<string>(initThemeFromStorage())
|
|
47
|
+
const persistenceEnabled = ref(false)
|
|
48
|
+
const changeListeners = new Set<(theme: string) => void>()
|
|
49
|
+
|
|
50
|
+
function initThemeFromStorage(): string {
|
|
51
|
+
try {
|
|
52
|
+
return localStorage.getItem(THEME_STORAGE_KEY) || 'light'
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return 'light'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 生成某主题完整变量声明文本(--zl-* + --el-* + 布局资产变量) */
|
|
60
|
+
function buildCssVars(resolved: Record<string, string>, themeName: string): string {
|
|
61
|
+
const isDark = themeName.includes('dark')
|
|
62
|
+
const get = (k: string, fallback = '') => resolved[`color-${k}`] ?? fallback
|
|
63
|
+
const getLevel = (name: string, level: number) => resolved[`color-${name}-${level}`] ?? ''
|
|
64
|
+
|
|
65
|
+
const decls: string[] = []
|
|
66
|
+
|
|
67
|
+
// --zl-* 业务层
|
|
68
|
+
for (const [key, value] of Object.entries(resolved)) {
|
|
69
|
+
decls.push(`--zl-${key.replace(/^color-/, '')}: ${value};`)
|
|
70
|
+
}
|
|
71
|
+
for (let i = 1; i <= 10; i++) {
|
|
72
|
+
decls.push(`--zl-primary-${i}: ${getLevel('modo', i)};`)
|
|
73
|
+
decls.push(`--zl-link-${i}: ${getLevel('link', i)};`)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --el-* EP 层
|
|
77
|
+
const semantic: Array<[string, string]> = [
|
|
78
|
+
['primary', getLevel('modo', 6)],
|
|
79
|
+
['success', getLevel('green', 6)],
|
|
80
|
+
['warning', getLevel('orange', 6)],
|
|
81
|
+
['danger', getLevel('red', 6)],
|
|
82
|
+
['info', getLevel('gray', 6)],
|
|
83
|
+
]
|
|
84
|
+
for (const [name, hex] of semantic) {
|
|
85
|
+
decls.push(`--el-color-${name}: ${hex};`)
|
|
86
|
+
for (const [shade, value] of Object.entries(generateElShades(hex))) {
|
|
87
|
+
decls.push(`--el-color-${name}-${shade}: ${value};`)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const textMap: Record<string, string> = { primary: 'text-1', regular: 'text-2', secondary: 'text-3', placeholder: 'text-4', disabled: 'text-5' }
|
|
92
|
+
for (const [el, token] of Object.entries(textMap))
|
|
93
|
+
decls.push(`--el-text-color-${el}: ${get(token)};`)
|
|
94
|
+
|
|
95
|
+
const borderMap: Array<[string, string]> = [['extra-light', 'border-1'], ['lighter', 'border-2'], ['light', 'border-3'], ['', 'border-4']]
|
|
96
|
+
for (const [suffix, token] of borderMap)
|
|
97
|
+
decls.push(`--el-border-color${suffix ? `-${suffix}` : ''}: ${get(token)};`)
|
|
98
|
+
|
|
99
|
+
const fillMap: Array<[string, string]> = [['extra-light', 'fill-1'], ['lighter', 'fill-2'], ['light', 'fill-3'], ['', 'fill-4']]
|
|
100
|
+
for (const [suffix, token] of fillMap)
|
|
101
|
+
decls.push(`--el-fill-color${suffix ? `-${suffix}` : ''}: ${get(token)};`)
|
|
102
|
+
decls.push(`--el-fill-color-blank: ${isDark ? get('bg-2') : '#ffffff'};`)
|
|
103
|
+
|
|
104
|
+
decls.push(`--el-bg-color: ${get('bg-2')};`)
|
|
105
|
+
decls.push(`--el-bg-color-page: ${get('bg-1')};`)
|
|
106
|
+
decls.push(`--el-bg-color-overlay: ${get('bg-3')};`)
|
|
107
|
+
decls.push(`--el-color-white: ${isDark ? '#141414' : '#ffffff'};`)
|
|
108
|
+
decls.push(`--el-color-black: ${isDark ? '#ffffff' : '#000000'};`)
|
|
109
|
+
decls.push(`color-scheme: ${isDark ? 'dark' : 'light'};`)
|
|
110
|
+
|
|
111
|
+
return decls.join('\n ')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function injectStyle(name: string, cssText: string) {
|
|
115
|
+
const styleId = `zl-theme-style-${name}`
|
|
116
|
+
let style = document.getElementById(styleId) as HTMLStyleElement | null
|
|
117
|
+
if (!style) {
|
|
118
|
+
style = document.createElement('style')
|
|
119
|
+
style.id = styleId
|
|
120
|
+
document.head.appendChild(style)
|
|
121
|
+
}
|
|
122
|
+
style.textContent = cssText
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 运行时定义主题(L2)
|
|
127
|
+
* 以某内置主题为基线,覆盖语义色后自动派生 EP 色阶与业务变量并注入样式
|
|
128
|
+
*/
|
|
129
|
+
export function defineTheme(options: DefineThemeOptions): void {
|
|
130
|
+
const { name, base = 'light', colors = {}, tokens = {}, apply = false } = options
|
|
131
|
+
if (THEMES.includes(name) && !customThemes.has(name)) {
|
|
132
|
+
throw new Error(`主题 ${name} 为内置主题,不可覆盖`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 基线数据克隆
|
|
136
|
+
const resolved: Record<string, string> = { ...(themesData[base] ?? themesData.light) }
|
|
137
|
+
for (const [key, value] of Object.entries(tokens)) {
|
|
138
|
+
resolved[`color-${key.replace(/^color-/, '')}`] = value
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const colorEntries: Array<[keyof DefineThemeColors, string, string]> = [
|
|
142
|
+
['primary', 'modo', '6'],
|
|
143
|
+
['success', 'green', '6'],
|
|
144
|
+
['warning', 'orange', '6'],
|
|
145
|
+
['danger', 'red', '6'],
|
|
146
|
+
['info', 'gray', '6'],
|
|
147
|
+
]
|
|
148
|
+
for (const [semantic, palette, level] of colorEntries) {
|
|
149
|
+
const hex = colors[semantic]
|
|
150
|
+
if (hex) {
|
|
151
|
+
resolved[`color-${palette}-${level}`] = hex
|
|
152
|
+
// 主色同时派生整组色板(浅→白、深→黑,各 10 级),保证 --zl-{palette}-N 可用
|
|
153
|
+
const seedLevel = Number(level)
|
|
154
|
+
for (let i = 1; i <= 10; i++) {
|
|
155
|
+
if (i === seedLevel)
|
|
156
|
+
continue
|
|
157
|
+
const weight = (Math.abs(i - seedLevel) / 5) * 50
|
|
158
|
+
resolved[`color-${palette}-${i}`] = i < seedLevel
|
|
159
|
+
? mix('#ffffff', hex, 100 - weight)
|
|
160
|
+
: mix('#000000', hex, 100 - weight)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (colors.bg) {
|
|
166
|
+
resolved['color-bg-1'] = colors.bg
|
|
167
|
+
resolved['color-bg-2'] = colors.bg
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const cssText = `body[data-theme='${name}'] {\n ${buildCssVars(resolved, name)}\n}`
|
|
171
|
+
injectStyle(name, cssText)
|
|
172
|
+
customThemes.set(name, { cssText })
|
|
173
|
+
if (!THEMES.includes(name)) {
|
|
174
|
+
THEMES.push(name)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (apply) {
|
|
178
|
+
setTheme(name)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface SetThemeOptions {
|
|
183
|
+
/** 本次切换是否持久化(需已启用持久化或显式指定) */
|
|
184
|
+
persist?: boolean
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** 切换主题(内置/自定义均可;light 作用于 body 本身) */
|
|
188
|
+
export function setTheme(name: string, options: SetThemeOptions = {}): void {
|
|
189
|
+
currentTheme.value = name
|
|
190
|
+
if (name === 'light') {
|
|
191
|
+
document.body.removeAttribute('data-theme')
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
document.body.setAttribute('data-theme', name)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (persistenceEnabled.value || options.persist) {
|
|
198
|
+
try {
|
|
199
|
+
localStorage.setItem(THEME_STORAGE_KEY, name)
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// storage 不可用时忽略
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
changeListeners.forEach(fn => fn(name))
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** 当前主题名 */
|
|
210
|
+
export function getTheme(): string {
|
|
211
|
+
return currentTheme.value
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** 全部可用主题(内置 + 运行时自定义) */
|
|
215
|
+
export function getThemes(): string[] {
|
|
216
|
+
return [...THEMES]
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** 开启主题持久化(同一浏览器记住所选主题) */
|
|
220
|
+
export function enableThemePersistence() {
|
|
221
|
+
persistenceEnabled.value = true
|
|
222
|
+
setTheme(currentTheme.value, { persist: true })
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** 主题变化监听 */
|
|
226
|
+
export function onThemeChange(callback: (theme: string) => void): () => void {
|
|
227
|
+
changeListeners.add(callback)
|
|
228
|
+
return () => changeListeners.delete(callback)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface UseThemeReturn {
|
|
232
|
+
/** 当前主题(只读 ref) */
|
|
233
|
+
theme: ComputedRef<string>
|
|
234
|
+
/** 可用主题列表(只读) */
|
|
235
|
+
themes: ComputedRef<string[]>
|
|
236
|
+
/** 切换主题 */
|
|
237
|
+
setTheme: (name: string, options?: SetThemeOptions) => void
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** 主题 composable(配合 defineTheme 实现运行时换肤) */
|
|
241
|
+
export function useTheme(): UseThemeReturn {
|
|
242
|
+
return {
|
|
243
|
+
theme: readonly(currentTheme) as unknown as ComputedRef<string>,
|
|
244
|
+
themes: computed(() => getThemes()),
|
|
245
|
+
setTheme,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 颜色工具:与 Element Plus theme-chalk 对齐的混色与色阶派生算法
|
|
3
|
+
* (themegen.mjs 与运行时 API 共用同一套规则,保证 L2/L3 产物一致)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function parseColor(str: string): [number, number, number] | null {
|
|
7
|
+
const s = String(str).trim()
|
|
8
|
+
if (s.startsWith('#')) {
|
|
9
|
+
let hex = s.slice(1)
|
|
10
|
+
if (hex.length === 3)
|
|
11
|
+
hex = hex.split('').map(c => c + c).join('')
|
|
12
|
+
return [Number.parseInt(hex.slice(0, 2), 16), Number.parseInt(hex.slice(2, 4), 16), Number.parseInt(hex.slice(4, 6), 16)]
|
|
13
|
+
}
|
|
14
|
+
const m = s.match(/rgba?\(([^)]+)\)/)
|
|
15
|
+
if (m) {
|
|
16
|
+
const parts = m[1]!.split(',').map(Number)
|
|
17
|
+
return [parts[0] || 0, parts[1] || 0, parts[2] || 0]
|
|
18
|
+
}
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function toHex([r, g, b]: [number, number, number]): string {
|
|
23
|
+
const c = (n: number) => {
|
|
24
|
+
n = Math.max(0, Math.min(255, Math.round(n)))
|
|
25
|
+
return n.toString(16).padStart(2, '0')
|
|
26
|
+
}
|
|
27
|
+
return `#${c(r)}${c(g)}${c(b)}`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** 混色:weight 为混入 color2 的百分比(与 EP mix 一致) */
|
|
31
|
+
export function mix(color1: string, color2: string, weight: number): string {
|
|
32
|
+
const c1 = parseColor(color1)
|
|
33
|
+
const c2 = parseColor(color2)
|
|
34
|
+
if (!c1 || !c2)
|
|
35
|
+
return color1
|
|
36
|
+
const w = weight / 100
|
|
37
|
+
return toHex([c1[0] * (1 - w) + c2[0] * w, c1[1] * (1 - w) + c2[1] * w, c1[2] * (1 - w) + c2[2] * w])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ElColorShades {
|
|
41
|
+
'light-1': string
|
|
42
|
+
'light-2': string
|
|
43
|
+
'light-3': string
|
|
44
|
+
'light-4': string
|
|
45
|
+
'light-5': string
|
|
46
|
+
'light-6': string
|
|
47
|
+
'light-7': string
|
|
48
|
+
'light-8': string
|
|
49
|
+
'light-9': string
|
|
50
|
+
'dark-2': string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** EP 主色色阶:light-n = mix(白, 主色, n*10%);dark-2 = mix(黑, 主色, 20%) */
|
|
54
|
+
export function generateElShades(hex: string): ElColorShades {
|
|
55
|
+
const shades = {} as ElColorShades
|
|
56
|
+
for (let i = 1; i <= 9; i++) {
|
|
57
|
+
shades[`light-${i}` as keyof ElColorShades] = mix('#ffffff', hex, i * 10)
|
|
58
|
+
}
|
|
59
|
+
shades['dark-2'] = mix('#000000', hex, 20)
|
|
60
|
+
return shades
|
|
61
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// SCSS 工具(供消费方深度定制编译使用;变量前缀 zl-)
|
|
3
|
+
// 入口:@zl/theme/scss(colors / map / theme 三个模块)
|
|
4
|
+
// ============================================================
|
|
5
|
+
|
|
6
|
+
@use 'sass:list';
|
|
7
|
+
@use '../generated/_constants' as *;
|
|
8
|
+
|
|
9
|
+
$color-gradient-range: (6, 3) !default;
|
|
10
|
+
|
|
11
|
+
/** 遍历全部色彩类型 */
|
|
12
|
+
@mixin colorForEach() {
|
|
13
|
+
@each $color in $color-types {
|
|
14
|
+
@content ($color);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 色板渐变背景(默认 6→3 级) */
|
|
19
|
+
@mixin colorGradient($color, $direction, $range: $color-gradient-range) {
|
|
20
|
+
$color-stops: ();
|
|
21
|
+
|
|
22
|
+
@each $stop in $range {
|
|
23
|
+
$color-stops: list.append($color-stops, var(--zl-#{$color}-#{$stop}), comma);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
background-image: linear-gradient($direction, $color-stops);
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// 嵌套 map 取值工具
|
|
2
|
+
|
|
3
|
+
@use 'sass:map';
|
|
4
|
+
@use 'sass:meta';
|
|
5
|
+
|
|
6
|
+
@function mapGet($map, $keys...) {
|
|
7
|
+
@each $key in $keys {
|
|
8
|
+
@if meta.type-of($map) == 'map' and map.has-key($map, $key) {
|
|
9
|
+
$map: map.get($map, $key);
|
|
10
|
+
} @else {
|
|
11
|
+
@warn "Key `#{$key}` not found in map or map is not nested.";
|
|
12
|
+
@return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
@return $map;
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// 主题作用域 mixin:为每套主题生成选择器块(light 无属性选择器)
|
|
2
|
+
|
|
3
|
+
@use '../generated/_constants' as *;
|
|
4
|
+
|
|
5
|
+
@mixin theme() {
|
|
6
|
+
@each $theme in $themes-name {
|
|
7
|
+
$filter: if($theme == 'light', '', "[data-theme='#{$theme}']");
|
|
8
|
+
|
|
9
|
+
body#{$filter} {
|
|
10
|
+
@content ($theme);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|