docs: add custom theme colors tools
This commit is contained in:
parent
5f6825a645
commit
7a1bc8f73d
@ -6,19 +6,19 @@ defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="`code-viewer language-${lang}`" :data-ext="lang">
|
||||
<button class="copy-code-button" :data-lang="lang" />
|
||||
<div :class="`code-viewer language-${lang}`" :data-title="lang">
|
||||
<button class="copy" :data-lang="lang" title="Copy code" data-copied="已复制" />
|
||||
<pre class="shiki shiki-themes vitesse-light vitesse-dark vp-code"><code>{{ content }}</code></pre>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.code-viewer .copy-code-button {
|
||||
.code-viewer .copy {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.code-viewer .copy-code-button {
|
||||
.code-viewer .copy {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
69
docs/.vuepress/themes/components/ColorPick.vue
Normal file
69
docs/.vuepress/themes/components/ColorPick.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.es5.min.js'
|
||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()
|
||||
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
declare namespace window {
|
||||
const Pickr: any
|
||||
}
|
||||
|
||||
const color = defineModel<string>('modelValue', { default: 'rgba(0,0,0,1)' })
|
||||
const pickerEl = ref<HTMLDivElement>()
|
||||
let picker: any
|
||||
|
||||
onMounted(() => {
|
||||
if (!pickerEl.value || picker)
|
||||
return
|
||||
|
||||
picker = window.Pickr.create({
|
||||
el: pickerEl.value,
|
||||
theme: 'nano',
|
||||
default: color.value,
|
||||
defaultRepresentation: 'RGBA',
|
||||
showAlways: false,
|
||||
components: {
|
||||
preview: true,
|
||||
opacity: true,
|
||||
hue: true,
|
||||
interaction: { input: true },
|
||||
},
|
||||
})
|
||||
picker.on('change', (hsva) => {
|
||||
emit('update:modelValue', hsva.toRGBA().toString(0))
|
||||
})
|
||||
watch(color, () => {
|
||||
picker?.setColor(color.value)
|
||||
})
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
picker?.destroyAndRemove()
|
||||
picker = null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="color-picker">
|
||||
<div ref="pickerEl" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.color-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
border-radius: 32px;
|
||||
transition: border-color var(--t-color), background-color var(--t-color);
|
||||
}
|
||||
|
||||
.pickr .pcr-button {
|
||||
overflow: hidden;
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
</style>
|
||||
74
docs/.vuepress/themes/components/ThemeColors.vue
Normal file
74
docs/.vuepress/themes/components/ThemeColors.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import VPButton from '@theme/VPButton.vue'
|
||||
import { useThemeColors } from '../composables/theme-colors.js'
|
||||
import ColorPick from './ColorPick.vue'
|
||||
import CodeViewer from './CodeViewer.vue'
|
||||
|
||||
const { lightColors, darkColors, css, reset } = useThemeColors()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPButton theme="alt" text="重置" @click="reset" />
|
||||
|
||||
<h2>浅色主题</h2>
|
||||
<div class="theme-colors-wrapper">
|
||||
<div v-for="({ name, group }, index) in lightColors" :key="index" class="group">
|
||||
<h4>{{ name }}</h4>
|
||||
<section v-for="color in group" :key="color.key" class="theme-color">
|
||||
<ColorPick v-model="color.value" />
|
||||
<h5>{{ color.name }}</h5>
|
||||
<span class="desc">{{ color.desc }}</span>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<h2>深色主题</h2>
|
||||
<div class="theme-colors-wrapper">
|
||||
<div v-for="({ name, group }, index) in darkColors" :key="index" class="group">
|
||||
<h4>{{ name }}</h4>
|
||||
<section v-for="color in group" :key="color.key" class="theme-color">
|
||||
<ColorPick v-model="color.value" />
|
||||
<h5>{{ color.name }}</h5>
|
||||
<span class="desc">{{ color.desc }}</span>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
<p>复制下方的代码到您的项目中,请参考 <a href="/custom-style/">主题定制</a> </p>
|
||||
<CodeViewer :content="css" lang="css" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.theme-colors-wrapper {
|
||||
display: grid;
|
||||
gap: 8px 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.theme-colors-wrapper {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1440px) {
|
||||
.theme-colors-wrapper {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-color {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.theme-color h5 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.theme-color .desc {
|
||||
font-size: 14px;
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color var(--t-color);
|
||||
}
|
||||
</style>
|
||||
148
docs/.vuepress/themes/composables/theme-colors.ts
Normal file
148
docs/.vuepress/themes/composables/theme-colors.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import { type InjectionKey, type Ref, inject, provide, watch } from 'vue'
|
||||
import { useSessionStorage, useStyleTag } from '@vueuse/core'
|
||||
|
||||
export interface ThemeColor {
|
||||
name: string
|
||||
key: string
|
||||
value: string
|
||||
desc: string
|
||||
}
|
||||
export type ThemeColors = ThemeColor[]
|
||||
export interface ThemeColorsGroup {
|
||||
name: string
|
||||
group: ThemeColors
|
||||
}
|
||||
|
||||
const DEFAULT_PRESET = {
|
||||
light: {
|
||||
'--vp-c-brand-1': '#5086a1',
|
||||
'--vp-c-brand-2': '#6aa1b7',
|
||||
'--vp-c-brand-3': '#8cccd5',
|
||||
'--vp-c-brand-soft': 'rgba(131, 208, 218, 0.314)',
|
||||
|
||||
'--vp-c-text-1': 'rgba(60, 60, 67)',
|
||||
'--vp-c-text-2': 'rgba(60, 60, 67, 0.78)',
|
||||
'--vp-c-text-3': 'rgba(60, 60, 67, 0.56)',
|
||||
|
||||
'--vp-c-bg': '#fff',
|
||||
'--vp-nav-bg-color': '#fff',
|
||||
'--vp-nav-screen-bg-color': '#fff',
|
||||
'--vp-local-nav-bg-color': '#fff',
|
||||
'--vp-sidebar-bg-color': '#f6f6f7',
|
||||
'--vp-code-block-bg': '#f6f8fa',
|
||||
},
|
||||
dark: {
|
||||
'--vp-c-brand-1': '#8cccd5',
|
||||
'--vp-c-brand-2': '#6aa1b7',
|
||||
'--vp-c-brand-3': '#5086a1',
|
||||
'--vp-c-brand-soft': 'rgba(131, 208, 218, 0.314)',
|
||||
|
||||
'--vp-c-text-1': 'rgba(255, 255, 245, 0.86)',
|
||||
'--vp-c-text-2': 'rgba(235, 235, 245, 0.6)',
|
||||
'--vp-c-text-3': 'rgba(235, 235, 245, 0.38)',
|
||||
|
||||
'--vp-c-bg': '#1b1b1f',
|
||||
'--vp-nav-bg-color': '#1b1b1f',
|
||||
'--vp-nav-screen-bg-color': '#1b1b1f',
|
||||
'--vp-local-nav-bg-color': '#1b1b1f',
|
||||
'--vp-sidebar-bg-color': '#161618',
|
||||
'--vp-code-block-bg': '#202127',
|
||||
},
|
||||
}
|
||||
|
||||
const preset: ThemeColorsGroup[] = [
|
||||
{
|
||||
name: '主题色',
|
||||
group: [
|
||||
{ name: 'brand-1', key: '--vp-c-brand-1', value: '', desc: '链接颜色、强调色' },
|
||||
{ name: 'brand-2', key: '--vp-c-brand-2', value: '', desc: '链接、按钮 hover 颜色' },
|
||||
{ name: 'brand-3', key: '--vp-c-brand-3', value: '', desc: '背景色、边框色' },
|
||||
{ name: 'brand-soft', key: '--vp-c-brand-soft', value: '', desc: '辅助色' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '文本颜色',
|
||||
group: [
|
||||
{ name: 'text-1', key: '--vp-c-text-1', value: '', desc: '主要文本' },
|
||||
{ name: 'text-2', key: '--vp-c-text-2', value: '', desc: '次要文本' },
|
||||
{ name: 'text-3', key: '--vp-c-text-3', value: '', desc: '辅助文本' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '背景色',
|
||||
group: [
|
||||
{ name: 'bg', key: '--vp-c-bg', value: '', desc: '主体背景' },
|
||||
{ name: 'nav-bg', key: '--vp-nav-bg-color', value: '', desc: '导航栏背景' },
|
||||
{ name: 'nav-screen-bg', key: '--vp-nav-screen-bg-color', value: '', desc: '移动端导航栏' },
|
||||
{ name: 'local-nav-bg', key: '--vp-local-nav-bg-color', value: '', desc: '页面内导航栏' },
|
||||
{ name: 'sidebar-bg', key: '--vp-sidebar-bg-color', value: '', desc: '侧边栏背景' },
|
||||
{ name: 'code-block-bg', key: '--vp-code-block-bg', value: '', desc: '代码块背景' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const themeColorSymbol: InjectionKey<{
|
||||
lightColors: Ref<ThemeColorsGroup[]>
|
||||
darkColors: Ref<ThemeColorsGroup[]>
|
||||
css: Ref<string>
|
||||
reset: () => void
|
||||
}> = Symbol(__VUEPRESS_DEV__ ? 'theme-color' : '')
|
||||
|
||||
export function setupThemeColors() {
|
||||
const lightColors = useSessionStorage<ThemeColorsGroup[]>('custom-theme-colors-light', resolveDefaultColors('light'))
|
||||
const darkColors = useSessionStorage<ThemeColorsGroup[]>('custom-theme-colors-dark', resolveDefaultColors('dark'))
|
||||
|
||||
const { css, load } = useStyleTag('')
|
||||
|
||||
watch([lightColors, darkColors], () => {
|
||||
const content = `${resolveContent(lightColors.value, 'light')}\n${resolveContent(darkColors.value, 'dark')}`
|
||||
css.value = content
|
||||
load()
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
function resolveContent(colors: ThemeColorsGroup[], type: 'light' | 'dark') {
|
||||
const name = type === 'light' ? ':root' : '.dark'
|
||||
let content = `${name} {\n`
|
||||
colors.forEach(({ name, group }) => {
|
||||
content += `\n /**\n * ${name}\n * -------------------------------------------------------------------------- */\n\n`
|
||||
group.forEach((item) => {
|
||||
const str = ` ${item.key}: ${item.value};`
|
||||
content += `${str}${' '.repeat(54 - str.length)}/* ${item.desc} */\n`
|
||||
})
|
||||
})
|
||||
content += '}\n'
|
||||
return content
|
||||
}
|
||||
|
||||
function resolveDefaultColors(type: 'light' | 'dark') {
|
||||
return preset.map(group => ({
|
||||
name: group.name,
|
||||
group: group.group.map(item => ({
|
||||
...item,
|
||||
value: DEFAULT_PRESET[type][item.key],
|
||||
})),
|
||||
}))
|
||||
}
|
||||
|
||||
function reset() {
|
||||
lightColors.value = resolveDefaultColors('light')
|
||||
darkColors.value = resolveDefaultColors('dark')
|
||||
}
|
||||
|
||||
provide(themeColorSymbol, {
|
||||
lightColors,
|
||||
darkColors,
|
||||
css,
|
||||
reset,
|
||||
})
|
||||
}
|
||||
|
||||
export function useThemeColors() {
|
||||
const result = inject(themeColorSymbol)
|
||||
|
||||
if (!result) {
|
||||
throw new Error('useThemeColors() can only be used inside `setupThemeColors()`.')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
26
docs/notes/tools/custom-theme.md
Normal file
26
docs/notes/tools/custom-theme.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: 主题颜色工具
|
||||
icon: unjs:theme-colors
|
||||
author: pengzhanbo
|
||||
createTime: 2024/07/29 13:58:29
|
||||
permalink: /tools/theme-colors/
|
||||
readingTime: false
|
||||
editLink: false
|
||||
contributors: false
|
||||
lastUpdated: false
|
||||
head:
|
||||
-
|
||||
- link
|
||||
- href: https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css
|
||||
rel: stylesheet
|
||||
---
|
||||
|
||||
::: tip
|
||||
这个工具可以帮助您快速创建自定义主题颜色。点击下方的按钮,选择您想要的主题颜色进行配置。
|
||||
|
||||
工具会自动应用您的修改到当前站点,您可以随意切换到任意页面查看效果。
|
||||
|
||||
本工具只能帮助您简单的创建主题颜色。如果您期望更加复杂的主题颜色配置,请查看 [styles/vars.css](https://github.com/pengzhanbo/vuepress-theme-plume/blob/main/theme/src/client/styles/vars.css)
|
||||
:::
|
||||
|
||||
<ThemeColors />
|
||||
Loading…
x
Reference in New Issue
Block a user