feat: theme colors (#1195)

zio/stable
Anthony Fu 2023-01-16 11:26:19 +01:00 committed by GitHub
parent 2e79f3aa37
commit 8753a94aae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 298 additions and 35 deletions

View File

@ -12,7 +12,7 @@ defineProps<{
<div <div
sticky top-0 z10 backdrop-blur sticky top-0 z10 backdrop-blur
pt="[env(safe-area-inset-top,0)]" pt="[env(safe-area-inset-top,0)]"
border="b base" bg="[rgba(var(--c-bg-base-rgb),0.7)]" border="b base" bg="[rgba(var(--rbg-bg-base),0.7)]"
> >
<div flex justify-between px5 py2 :class="{ 'xl:hidden': $route.name !== 'tag' }"> <div flex justify-between px5 py2 :class="{ 'xl:hidden': $route.name !== 'tag' }">
<div flex gap-3 items-center overflow-hidden py2> <div flex gap-3 items-center overflow-hidden py2>

View File

@ -1,7 +1,7 @@
<template> <template>
<button <button
v-if="$pwa?.needRefresh" v-if="$pwa?.needRefresh"
bg="fade" relative rounded bg="primary-fade" relative rounded
flex="~ gap-1 center" px3 py1 text-primary flex="~ gap-1 center" px3 py1 text-primary
@click="$pwa.updateServiceWorker()" @click="$pwa.updateServiceWorker()"
> >

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
v-if="$pwa?.needRefresh" v-if="$pwa?.needRefresh"
m-2 p5 bg="fade" relative m-2 p5 bg="primary-fade" relative
rounded-lg of-hidden rounded-lg of-hidden
flex="~ col gap-3" flex="~ col gap-3"
> >

View File

@ -0,0 +1,29 @@
<script setup lang="ts">
import type { ThemeColors } from '~/composables/settings'
const themes = await import('~/constants/themes.json').then(r => r.default) as [string, ThemeColors][]
const settings = $(useUserSettings())
const currentTheme = $computed(() => settings.themeColors?.['--theme-color-name'] || themes[0][0])
function updateTheme(theme: ThemeColors) {
settings.themeColors = theme
}
</script>
<template>
<div flex="~ gap4" p2>
<button
v-for="[key, theme] in themes" :key="key"
:style="{
'background': key,
'--local-ring-color': key,
}"
:class="currentTheme === key ? 'ring-2' : 'scale-90'"
:title="key"
w-8 h-8 rounded-full transition-all
ring="$local-ring-color offset-3 offset-$c-bg-base"
@click="updateTheme(theme)"
/>
</div>
</template>

View File

@ -18,6 +18,23 @@ export interface UserSettings {
fontSize: FontSize fontSize: FontSize
language: string language: string
zenMode: boolean zenMode: boolean
themeColors?: ThemeColors
}
export interface ThemeColors {
'--theme-color-name': string
'--c-primary': string
'--c-primary-active': string
'--c-primary-light': string
'--c-primary-fade': string
'--c-dark-primary': string
'--c-dark-primary-active': string
'--c-dark-primary-light': string
'--c-dark-primary-fade': string
'--rgb-primary': string
'--rgb-dark-primary': string
} }
export function getDefaultLanguage(languages: string[]) { export function getDefaultLanguage(languages: string[]) {

View File

@ -0,0 +1,146 @@
[
[
"#cc7d24",
{
"--theme-color-name": "#cc7d24",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#b16605",
"--c-primary-light": "#cc7d2480",
"--c-primary-fade": "#c7781f1a",
"--rgb-primary": "204, 125, 36",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#b66b0d",
"--c-dark-primary-light": "#d1822980",
"--c-dark-primary-fade": "#cc7d241a",
"--rgb-dark-primary": "204, 125, 36"
}
],
[
"#8d9614",
{
"--theme-color-name": "#8d9614",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#747f00",
"--c-primary-light": "#8d961480",
"--c-primary-fade": "#88910c1a",
"--rgb-primary": "141, 150, 20",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#798300",
"--c-dark-primary-light": "#929b1b80",
"--c-dark-primary-fade": "#8d96141a",
"--rgb-dark-primary": "141, 150, 20"
}
],
[
"#24a451",
{
"--theme-color-name": "#24a451",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#008c3b",
"--c-primary-light": "#24a45180",
"--c-primary-fade": "#1c9f4d1a",
"--rgb-primary": "36, 164, 81",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#00903f",
"--c-dark-primary-light": "#2ba95580",
"--c-dark-primary-fade": "#24a4511a",
"--rgb-dark-primary": "36, 164, 81"
}
],
[
"#00a99b",
{
"--theme-color-name": "#00a99b",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#009184",
"--c-primary-light": "#00a99b80",
"--c-primary-fade": "#00a4961a",
"--rgb-primary": "0, 169, 155",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#009688",
"--c-dark-primary-light": "#13aea080",
"--c-dark-primary-fade": "#00a99b1a",
"--rgb-dark-primary": "0, 169, 155"
}
],
[
"#00a6df",
{
"--theme-color-name": "#00a6df",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#008ec6",
"--c-primary-light": "#00a6df80",
"--c-primary-fade": "#00a1da1a",
"--rgb-primary": "0, 166, 223",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#0093cb",
"--c-dark-primary-light": "#17abe480",
"--c-dark-primary-fade": "#00a6df1a",
"--rgb-dark-primary": "0, 166, 223"
}
],
[
"#0097fd",
{
"--theme-color-name": "#0097fd",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#0080e3",
"--c-primary-light": "#0097fd80",
"--c-primary-fade": "#0092f81a",
"--rgb-primary": "0, 151, 253",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#0085e5",
"--c-dark-primary-light": "#1a9cff80",
"--c-dark-primary-fade": "#0197fa1a",
"--rgb-dark-primary": "0, 151, 253"
}
],
[
"#a27be7",
{
"--theme-color-name": "#a27be7",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#8964cd",
"--c-primary-light": "#a27be780",
"--c-primary-fade": "#9d76e21a",
"--rgb-primary": "162, 123, 231",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#8e69d2",
"--c-dark-primary-light": "#a780ec80",
"--c-dark-primary-fade": "#a27be71a",
"--rgb-dark-primary": "162, 123, 231"
}
],
[
"#e65da9",
{
"--theme-color-name": "#e65da9",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#cb4391",
"--c-primary-light": "#e65da980",
"--c-primary-fade": "#e158a41a",
"--rgb-primary": "230, 93, 169",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#d14896",
"--c-dark-primary-light": "#eb62ae80",
"--c-dark-primary-fade": "#e65da91a",
"--rgb-dark-primary": "230, 93, 169"
}
],
[
"#ef6061",
{
"--theme-color-name": "#ef6061",
"--c-primary": "rgb(var(--rgb-primary))",
"--c-primary-active": "#d3474c",
"--c-primary-light": "#ef606180",
"--c-primary-fade": "#e95b5d1a",
"--rgb-primary": "239, 96, 97",
"--c-dark-primary": "rgb(var(--rgb-dark-primary))",
"--c-dark-primary-active": "#d94c50",
"--c-dark-primary-light": "#f5656580",
"--c-dark-primary-fade": "#ef60611a",
"--rgb-dark-primary": "239, 96, 97"
}
]
]

View File

@ -291,7 +291,8 @@
"xl": "Extra large", "xl": "Extra large",
"xs": "Extra small" "xs": "Extra small"
}, },
"system_mode": "System" "system_mode": "System",
"theme_color": "Theme Color"
}, },
"language": { "language": {
"display_language": "Display Language", "display_language": "Display Language",

View File

@ -45,6 +45,7 @@ export default defineNuxtConfig({
css: [ css: [
'@unocss/reset/tailwind.css', '@unocss/reset/tailwind.css',
'floating-vue/dist/style.css', 'floating-vue/dist/style.css',
'~/styles/default-theme.css',
'~/styles/vars.css', '~/styles/vars.css',
'~/styles/global.css', '~/styles/global.css',
'~/styles/tiptap.css', '~/styles/tiptap.css',

View File

@ -80,6 +80,7 @@
"@nuxtjs/color-mode": "^3.2.0", "@nuxtjs/color-mode": "^3.2.0",
"@nuxtjs/i18n": "^8.0.0-beta.7", "@nuxtjs/i18n": "^8.0.0-beta.7",
"@pinia/nuxt": "^0.4.6", "@pinia/nuxt": "^0.4.6",
"@types/chroma-js": "^2.1.4",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/fnando__sparkline": "^0.3.4", "@types/fnando__sparkline": "^0.3.4",
"@types/fs-extra": "^11.0.0", "@types/fs-extra": "^11.0.0",
@ -92,6 +93,7 @@
"@vueuse/math": "^9.10.0", "@vueuse/math": "^9.10.0",
"@vueuse/nuxt": "^9.10.0", "@vueuse/nuxt": "^9.10.0",
"bumpp": "^8.2.1", "bumpp": "^8.2.1",
"chroma-js": "^2.4.2",
"emoji-mart": "^5.4.0", "emoji-mart": "^5.4.0",
"eslint": "^8.31.0", "eslint": "^8.31.0",
"esno": "^0.16.3", "esno": "^0.16.3",

View File

@ -24,6 +24,12 @@ useHeadFixed({
</p> </p>
<SettingsColorMode /> <SettingsColorMode />
</div> </div>
<div space-y-2>
<p font-medium>
{{ $t('settings.interface.theme_color') }}
</p>
<SettingsThemeColors />
</div>
</div> </div>
</MainContent> </MainContent>
</template> </template>

View File

@ -3,11 +3,14 @@ import { DEFAULT_FONT_SIZE } from '~/constants'
export default defineNuxtPlugin(() => { export default defineNuxtPlugin(() => {
const userSettings = useUserSettings() const userSettings = useUserSettings()
const html = document.querySelector('html')! const html = document.documentElement
watchEffect(() => { watchEffect(() => {
html.style.setProperty('--font-size', fontSizeMap[userSettings.value.fontSize || DEFAULT_FONT_SIZE]) html.style.setProperty('--font-size', fontSizeMap[userSettings.value.fontSize || DEFAULT_FONT_SIZE])
}) })
watchEffect(() => { watchEffect(() => {
html.classList.toggle('zen', userSettings.value.zenMode) html.classList.toggle('zen', userSettings.value.zenMode)
}) })
watchEffect(() => {
Object.entries(userSettings.value.themeColors || {}).forEach(([k, v]) => html.style.setProperty(k, v))
})
}) })

View File

@ -15,7 +15,7 @@ export default defineNuxtPlugin(() => {
const settings = allSettings[handle] const settings = allSettings[handle]
if (!settings) { return } if (!settings) { return }
const html = document.querySelector('html') const html = document.documentElement
${process.dev ? 'console.log({ settings })' : ''} ${process.dev ? 'console.log({ settings })' : ''}
if (settings.fontSize) { if (settings.fontSize) {
@ -28,6 +28,9 @@ export default defineNuxtPlugin(() => {
if (settings.zenMode) { if (settings.zenMode) {
html.classList.add('zen') html.classList.add('zen')
} }
if (settings.themeColors) {
Object.entries(settings.themeColors).map(i => html.style.setProperty(i[0], i[1]))
}
})()`.trim().replace(/\s*\n+\s*/g, ';'), })()`.trim().replace(/\s*\n+\s*/g, ';'),
}, },
], ],

View File

@ -59,6 +59,7 @@ importers:
'@tiptap/starter-kit': 2.0.0-beta.204 '@tiptap/starter-kit': 2.0.0-beta.204
'@tiptap/suggestion': 2.0.0-beta.204 '@tiptap/suggestion': 2.0.0-beta.204
'@tiptap/vue-3': 2.0.0-beta.204 '@tiptap/vue-3': 2.0.0-beta.204
'@types/chroma-js': ^2.1.4
'@types/file-saver': ^2.0.5 '@types/file-saver': ^2.0.5
'@types/fnando__sparkline': ^0.3.4 '@types/fnando__sparkline': ^0.3.4
'@types/fs-extra': ^11.0.0 '@types/fs-extra': ^11.0.0
@ -77,6 +78,7 @@ importers:
blurhash: ^2.0.4 blurhash: ^2.0.4
browser-fs-access: ^0.31.1 browser-fs-access: ^0.31.1
bumpp: ^8.2.1 bumpp: ^8.2.1
chroma-js: ^2.4.2
emoji-mart: ^5.4.0 emoji-mart: ^5.4.0
eslint: ^8.31.0 eslint: ^8.31.0
esno: ^0.16.3 esno: ^0.16.3
@ -179,6 +181,7 @@ importers:
'@nuxtjs/color-mode': 3.2.0 '@nuxtjs/color-mode': 3.2.0
'@nuxtjs/i18n': 8.0.0-beta.7 '@nuxtjs/i18n': 8.0.0-beta.7
'@pinia/nuxt': 0.4.6_typescript@4.9.4 '@pinia/nuxt': 0.4.6_typescript@4.9.4
'@types/chroma-js': 2.1.4
'@types/file-saver': 2.0.5 '@types/file-saver': 2.0.5
'@types/fnando__sparkline': 0.3.4 '@types/fnando__sparkline': 0.3.4
'@types/fs-extra': 11.0.0 '@types/fs-extra': 11.0.0
@ -191,6 +194,7 @@ importers:
'@vueuse/math': 9.10.0 '@vueuse/math': 9.10.0
'@vueuse/nuxt': 9.10.0_nuxt@3.0.0 '@vueuse/nuxt': 9.10.0_nuxt@3.0.0
bumpp: 8.2.1 bumpp: 8.2.1
chroma-js: 2.4.2
emoji-mart: 5.4.0 emoji-mart: 5.4.0
eslint: 8.31.0 eslint: 8.31.0
esno: 0.16.3 esno: 0.16.3
@ -3116,6 +3120,10 @@ packages:
resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
dev: true dev: true
/@types/chroma-js/2.1.4:
resolution: {integrity: sha512-l9hWzP7cp7yleJUI7P2acmpllTJNYf5uU6wh50JzSIZt3fFHe+w2FM6w9oZGBTYzjjm2qHdnQvI+fF/JF/E5jQ==}
dev: true
/@types/debug/4.1.7: /@types/debug/4.1.7:
resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
dependencies: dependencies:

View File

@ -0,0 +1,31 @@
import chroma from 'chroma-js'
import type { ThemeColors } from '~/composables/settings'
// #cc7d24 -> hcl(67.14,62.19,59.56)
export const themesColor = Array.from(
{ length: 9 },
(_, i) => chroma.hcl((67.14 + i * 40) % 360, 62.19, 59.56).hex(),
)
export function getThemeColors(primary: string): ThemeColors {
const c = chroma(primary)
const dc = c.brighten(0.1)
return {
'--theme-color-name': primary,
'--c-primary': 'rgb(var(--rgb-primary))',
'--c-primary-active': c.darken(0.5).hex(),
'--c-primary-light': c.alpha(0.5).hex(),
'--c-primary-fade': c.darken(0.1).alpha(0.1).hex(),
'--rgb-primary': c.rgb().join(', '),
'--c-dark-primary': 'rgb(var(--rgb-dark-primary))',
'--c-dark-primary-active': dc.darken(0.5).hex(),
'--c-dark-primary-light': dc.alpha(0.5).hex(),
'--c-dark-primary-fade': dc.darken(0.1).alpha(0.1).hex(),
'--rgb-dark-primary': c.rgb().join(', '),
}
}
export const colorsMap = themesColor.map(color => [color, getThemeColors(color)])

View File

@ -1,12 +1,16 @@
import { copy } from 'fs-extra' import fs from 'fs-extra'
import { emojiPrefix, iconifyEmojiPackage } from '../config/emojis' import { emojiPrefix, iconifyEmojiPackage } from '../config/emojis'
import { colorsMap } from './generate-themes'
const dereference = process.platform === 'win32' ? true : undefined const dereference = process.platform === 'win32' ? true : undefined
await copy('node_modules/shiki-es/dist/assets', 'public/shiki/', { await fs.copy('node_modules/shiki-es/dist/assets', 'public/shiki/', {
dereference, dereference,
filter: src => src === 'node_modules/shiki/' || src.includes('languages') || src.includes('dist'), filter: src => src === 'node_modules/shiki/' || src.includes('languages') || src.includes('dist'),
}) })
await copy('node_modules/theme-vitesse/themes', 'public/shiki/themes', { dereference }) await fs.copy('node_modules/theme-vitesse/themes', 'public/shiki/themes', { dereference })
await copy('node_modules/theme-vitesse/themes', 'node_modules/shiki/themes', { overwrite: true, dereference }) await fs.copy('node_modules/theme-vitesse/themes', 'node_modules/shiki/themes', { overwrite: true, dereference })
await copy(`node_modules/${iconifyEmojiPackage}/icons`, `public/emojis/${emojiPrefix}`, { overwrite: true, dereference }) await fs.copy(`node_modules/${iconifyEmojiPackage}/icons`, `public/emojis/${emojiPrefix}`, { overwrite: true, dereference })
await fs.writeJSON('constants/themes.json', colorsMap, { spaces: 2, EOL: '\n' })
await fs.writeFile('styles/default-theme.css', `:root {\n${Object.entries(colorsMap[0][1]).map(([k, v]) => ` ${k}: ${v};`).join('\n')}\n}\n`, { encoding: 'utf-8' })

View File

@ -0,0 +1,13 @@
:root {
--theme-color-name: #cc7d24;
--c-primary: rgb(var(--rgb-primary));
--c-primary-active: #b16605;
--c-primary-light: #cc7d2480;
--c-primary-fade: #c7781f1a;
--rgb-primary: 204, 125, 36;
--c-dark-primary: rgb(var(--rgb-dark-primary));
--c-dark-primary-active: #b66b0d;
--c-dark-primary-light: #d1822980;
--c-dark-primary-fade: #cc7d241a;
--rgb-dark-primary: 204, 125, 36;
}

View File

@ -1,12 +1,13 @@
.ProseMirror p.is-editor-empty:first-child::before { .ProseMirror {
content: attr(data-placeholder); p.is-editor-empty:first-child::before {
float: left; content: attr(data-placeholder);
pointer-events: none; float: left;
height: 0; pointer-events: none;
opacity: 0.4; height: 0;
} opacity: 0.4;
}
span[data-type='mention'], span[data-type='mention'],
span[data-type='hashtag'] { span[data-type='hashtag'] {
--at-apply: text-primary; --at-apply: text-primary;
}
} }

View File

@ -1,17 +1,15 @@
:root { :root {
--c-primary: #d98018;
--c-primary-active: #9a5420;
--c-primary-light: #d980181A;
--c-border: #eee; --c-border: #eee;
--c-border-dark: #dccfcf; --c-border-dark: #dccfcf;
--c-bg-base: #fafafa; --rgb-bg-base: 250, 250, 250;
--c-bg-base-rgb: 250, 250, 250;
--c-bg-base: rgb(var(--rgb-bg-base));
--c-bg-active: #f2f2f2; --c-bg-active: #f2f2f2;
--c-bg-card: #00000006; --c-bg-card: #00000006;
--c-bg-code: #00000006; --c-bg-code: #00000006;
--c-bg-selection: #8885; --c-bg-selection: #8885;
--c-bg-fade: #EA9E4411;
--c-bg-dm: #f1e8e6; --c-bg-dm: #f1e8e6;
--c-text-base: #232323; --c-text-base: #232323;
@ -29,19 +27,19 @@
} }
.dark { .dark {
--c-primary: #EA9E44; --c-primary: var(--c-dark-primary);
--c-primary-active: #C16929; --c-primary-active: var(--c-dark-primary-active);
--c-primary-light: #EA9E441A; --c-primary-light: var(--c-dark-primary-light);
--c-primary-fade: var(--c-dark-primary-fade);
--c-border: #222; --c-border: #222;
--c-border-dark: #545251; --c-border-dark: #545251;
--c-bg-base: #111; --rgb-bg-base: 17, 17, 17;
--c-bg-base-rgb: 17, 17, 17;
--c-bg-active: #191919; --c-bg-active: #191919;
--c-bg-card: #ffffff06; --c-bg-card: #ffffff06;
--c-bg-code: #ffffff06; --c-bg-code: #ffffff06;
--c-bg-fade: #EA9E4411;
--c-bg-dm: #0a2f35; --c-bg-dm: #0a2f35;
--c-text-base: #f3f3f3; --c-text-base: #f3f3f3;

View File

@ -23,9 +23,9 @@ export default defineConfig({
'bg-border': 'bg-$c-border', 'bg-border': 'bg-$c-border',
'bg-active': 'bg-$c-bg-active', 'bg-active': 'bg-$c-bg-active',
'bg-primary-light': 'bg-$c-primary-light', 'bg-primary-light': 'bg-$c-primary-light',
'bg-primary-fade': 'bg-$c-primary-fade',
'bg-card': 'bg-$c-bg-card', 'bg-card': 'bg-$c-bg-card',
'bg-code': 'bg-$c-bg-code', 'bg-code': 'bg-$c-bg-code',
'bg-fade': 'bg-$c-bg-fade',
'bg-dm': 'bg-$c-bg-dm', 'bg-dm': 'bg-$c-bg-dm',
// text colors // text colors