mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
feat: 预定义主题
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { Button } from '@heroui/button'
|
import { Button } from '@heroui/button'
|
||||||
|
import clsx from 'clsx'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { IoMdRefresh } from 'react-icons/io'
|
import { IoMdRefresh } from 'react-icons/io'
|
||||||
|
|
||||||
@@ -7,15 +8,22 @@ export interface SaveButtonsProps {
|
|||||||
reset: () => void
|
reset: () => void
|
||||||
refresh?: () => void
|
refresh?: () => void
|
||||||
isSubmitting: boolean
|
isSubmitting: boolean
|
||||||
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
const SaveButtons: React.FC<SaveButtonsProps> = ({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
reset,
|
reset,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
refresh
|
refresh,
|
||||||
|
className
|
||||||
}) => (
|
}) => (
|
||||||
<div className="max-w-full mx-3 w-96 flex flex-col justify-center gap-3">
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'max-w-full mx-3 w-96 flex flex-col justify-center gap-3',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-center gap-2 mt-5">
|
<div className="flex items-center justify-center gap-2 mt-5">
|
||||||
<Button
|
<Button
|
||||||
color="default"
|
color="default"
|
||||||
|
@@ -252,7 +252,5 @@ export default {
|
|||||||
theme,
|
theme,
|
||||||
author: 'NapCat',
|
author: 'NapCat',
|
||||||
name: 'nc_pink',
|
name: 'nc_pink',
|
||||||
bgColor: 'hsl(339.2,90.36%,51.18%)',
|
|
||||||
textColor: 'hsl(0,0%,100%)',
|
|
||||||
description: 'NapCat Pink Theme'
|
description: 'NapCat Pink Theme'
|
||||||
} satisfies ThemeInfo
|
} satisfies ThemeInfo
|
||||||
|
@@ -1,8 +1,14 @@
|
|||||||
import { Accordion, AccordionItem } from '@heroui/accordion'
|
import { Accordion, AccordionItem } from '@heroui/accordion'
|
||||||
|
import { Card, CardBody, CardHeader } from '@heroui/card'
|
||||||
import { useRequest } from 'ahooks'
|
import { useRequest } from 'ahooks'
|
||||||
import { useEffect } from 'react'
|
import clsx from 'clsx'
|
||||||
import { Controller, useForm } from 'react-hook-form'
|
import { useEffect, useRef } from 'react'
|
||||||
|
import { Controller, useForm, useWatch } from 'react-hook-form'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
import { FaUserAstronaut } from 'react-icons/fa'
|
||||||
|
import { FaPaintbrush } from 'react-icons/fa6'
|
||||||
|
import { IoIosColorPalette } from 'react-icons/io'
|
||||||
|
import { MdDarkMode, MdLightMode } from 'react-icons/md'
|
||||||
|
|
||||||
import themes from '@/const/themes'
|
import themes from '@/const/themes'
|
||||||
|
|
||||||
@@ -10,120 +16,82 @@ import ColorPicker from '@/components/ColorPicker'
|
|||||||
import SaveButtons from '@/components/button/save_buttons'
|
import SaveButtons from '@/components/button/save_buttons'
|
||||||
import PageLoading from '@/components/page_loading'
|
import PageLoading from '@/components/page_loading'
|
||||||
|
|
||||||
import { loadTheme } from '@/utils/theme'
|
import { colorKeys, generateTheme, loadTheme } from '@/utils/theme'
|
||||||
|
|
||||||
import WebUIManager from '@/controllers/webui_manager'
|
import WebUIManager from '@/controllers/webui_manager'
|
||||||
|
|
||||||
// 将颜色 key 补全为 ThemeConfigItem 中定义的所有颜色相关属性
|
export type PreviewThemeCardProps = {
|
||||||
const colorKeys = [
|
theme: ThemeInfo
|
||||||
'--heroui-background',
|
onPreview: () => void
|
||||||
|
}
|
||||||
|
|
||||||
'--heroui-foreground-50',
|
const values = [
|
||||||
'--heroui-foreground-100',
|
'',
|
||||||
'--heroui-foreground-200',
|
'-50',
|
||||||
'--heroui-foreground-300',
|
'-100',
|
||||||
'--heroui-foreground-400',
|
'-200',
|
||||||
'--heroui-foreground-500',
|
'-300',
|
||||||
'--heroui-foreground-600',
|
'-400',
|
||||||
'--heroui-foreground-700',
|
'-500',
|
||||||
'--heroui-foreground-800',
|
'-600',
|
||||||
'--heroui-foreground-900',
|
'-700',
|
||||||
'--heroui-foreground',
|
'-800',
|
||||||
|
'-900'
|
||||||
|
]
|
||||||
|
const colors = ['primary', 'secondary', 'success', 'danger', 'warning']
|
||||||
|
|
||||||
'--heroui-content1',
|
function PreviewThemeCard({ theme, onPreview }: PreviewThemeCardProps) {
|
||||||
'--heroui-content1-foreground',
|
const style = document.createElement('style')
|
||||||
'--heroui-content2',
|
style.innerHTML = generateTheme(theme.theme, theme.name)
|
||||||
'--heroui-content2-foreground',
|
const cardRef = useRef<HTMLDivElement>(null)
|
||||||
'--heroui-content3',
|
useEffect(() => {
|
||||||
'--heroui-content3-foreground',
|
document.head.appendChild(style)
|
||||||
'--heroui-content4',
|
return () => {
|
||||||
'--heroui-content4-foreground',
|
document.head.removeChild(style)
|
||||||
|
}
|
||||||
'--heroui-default-50',
|
}, [])
|
||||||
'--heroui-default-100',
|
return (
|
||||||
'--heroui-default-200',
|
<Card
|
||||||
'--heroui-default-300',
|
ref={cardRef}
|
||||||
'--heroui-default-400',
|
shadow="sm"
|
||||||
'--heroui-default-500',
|
radius="sm"
|
||||||
'--heroui-default-600',
|
isPressable
|
||||||
'--heroui-default-700',
|
onPress={onPreview}
|
||||||
'--heroui-default-800',
|
className={clsx('text-primary bg-primary-50', theme.name)}
|
||||||
'--heroui-default-900',
|
>
|
||||||
'--heroui-default-foreground',
|
<CardHeader className="pb-0 flex flex-col items-start gap-1">
|
||||||
'--heroui-default',
|
<div className="px-1 rounded-md bg-primary text-primary-foreground">
|
||||||
|
{theme.name}
|
||||||
'--heroui-danger-50',
|
</div>
|
||||||
'--heroui-danger-100',
|
<div className="text-xs flex items-center gap-1 text-primary-300">
|
||||||
'--heroui-danger-200',
|
<FaUserAstronaut />
|
||||||
'--heroui-danger-300',
|
{theme.author ?? '未知'}
|
||||||
'--heroui-danger-400',
|
</div>
|
||||||
'--heroui-danger-500',
|
<div className="text-xs text-primary-200">{theme.description}</div>
|
||||||
'--heroui-danger-600',
|
</CardHeader>
|
||||||
'--heroui-danger-700',
|
<CardBody>
|
||||||
'--heroui-danger-800',
|
<div className="flex flex-col gap-1">
|
||||||
'--heroui-danger-900',
|
{colors.map((color) => (
|
||||||
'--heroui-danger-foreground',
|
<div className="flex gap-1 items-center flex-wrap" key={color}>
|
||||||
'--heroui-danger',
|
<div className="text-xs w-4 text-right">
|
||||||
|
{color[0].toUpperCase()}
|
||||||
'--heroui-primary-50',
|
</div>
|
||||||
'--heroui-primary-100',
|
{values.map((value) => (
|
||||||
'--heroui-primary-200',
|
<div
|
||||||
'--heroui-primary-300',
|
key={value}
|
||||||
'--heroui-primary-400',
|
className={clsx(
|
||||||
'--heroui-primary-500',
|
'w-2 h-2 rounded-full shadow-small',
|
||||||
'--heroui-primary-600',
|
`bg-${color}${value}`
|
||||||
'--heroui-primary-700',
|
)}
|
||||||
'--heroui-primary-800',
|
></div>
|
||||||
'--heroui-primary-900',
|
))}
|
||||||
'--heroui-primary-foreground',
|
</div>
|
||||||
'--heroui-primary',
|
))}
|
||||||
|
</div>
|
||||||
'--heroui-secondary-50',
|
</CardBody>
|
||||||
'--heroui-secondary-100',
|
</Card>
|
||||||
'--heroui-secondary-200',
|
)
|
||||||
'--heroui-secondary-300',
|
}
|
||||||
'--heroui-secondary-400',
|
|
||||||
'--heroui-secondary-500',
|
|
||||||
'--heroui-secondary-600',
|
|
||||||
'--heroui-secondary-700',
|
|
||||||
'--heroui-secondary-800',
|
|
||||||
'--heroui-secondary-900',
|
|
||||||
'--heroui-secondary-foreground',
|
|
||||||
'--heroui-secondary',
|
|
||||||
|
|
||||||
'--heroui-success-50',
|
|
||||||
'--heroui-success-100',
|
|
||||||
'--heroui-success-200',
|
|
||||||
'--heroui-success-300',
|
|
||||||
'--heroui-success-400',
|
|
||||||
'--heroui-success-500',
|
|
||||||
'--heroui-success-600',
|
|
||||||
'--heroui-success-700',
|
|
||||||
'--heroui-success-800',
|
|
||||||
'--heroui-success-900',
|
|
||||||
'--heroui-success-foreground',
|
|
||||||
'--heroui-success',
|
|
||||||
|
|
||||||
'--heroui-warning-50',
|
|
||||||
'--heroui-warning-100',
|
|
||||||
'--heroui-warning-200',
|
|
||||||
'--heroui-warning-300',
|
|
||||||
'--heroui-warning-400',
|
|
||||||
'--heroui-warning-500',
|
|
||||||
'--heroui-warning-600',
|
|
||||||
'--heroui-warning-700',
|
|
||||||
'--heroui-warning-800',
|
|
||||||
'--heroui-warning-900',
|
|
||||||
'--heroui-warning-foreground',
|
|
||||||
'--heroui-warning',
|
|
||||||
|
|
||||||
'--heroui-focus',
|
|
||||||
'--heroui-overlay',
|
|
||||||
'--heroui-divider',
|
|
||||||
'--heroui-code-background',
|
|
||||||
'--heroui-strong',
|
|
||||||
'--heroui-code-mdx'
|
|
||||||
] as const
|
|
||||||
|
|
||||||
const ThemeConfigCard = () => {
|
const ThemeConfigCard = () => {
|
||||||
const { data, loading, error, refreshAsync } = useRequest(
|
const { data, loading, error, refreshAsync } = useRequest(
|
||||||
@@ -145,6 +113,23 @@ const ThemeConfigCard = () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 使用 useRef 存储 style 标签引用
|
||||||
|
const styleTagRef = useRef<HTMLStyleElement | null>(null)
|
||||||
|
|
||||||
|
// 在组件挂载时创建 style 标签,并在卸载时清理
|
||||||
|
useEffect(() => {
|
||||||
|
const styleTag = document.createElement('style')
|
||||||
|
document.head.appendChild(styleTag)
|
||||||
|
styleTagRef.current = styleTag
|
||||||
|
return () => {
|
||||||
|
if (styleTagRef.current) {
|
||||||
|
document.head.removeChild(styleTagRef.current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const theme = useWatch({ control, name: 'theme' })
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
if (data) setOnebotValue('theme', data)
|
if (data) setOnebotValue('theme', data)
|
||||||
}
|
}
|
||||||
@@ -174,6 +159,13 @@ const ThemeConfigCard = () => {
|
|||||||
reset()
|
reset()
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme && styleTagRef.current) {
|
||||||
|
const css = generateTheme(theme)
|
||||||
|
styleTagRef.current.innerHTML = css
|
||||||
|
}
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
if (loading) return <PageLoading loading={true} />
|
if (loading) return <PageLoading loading={true} />
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
@@ -184,31 +176,33 @@ const ThemeConfigCard = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<title>主题配置 - NapCat WebUI</title>
|
<title>主题配置 - NapCat WebUI</title>
|
||||||
<Accordion variant="splitted">
|
|
||||||
|
<SaveButtons
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
reset={reset}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
refresh={onRefresh}
|
||||||
|
className="items-end w-full p-4"
|
||||||
|
/>
|
||||||
|
<div className="px-4 text-sm text-default-600">实时预览,记得保存!</div>
|
||||||
|
<Accordion variant="splitted" defaultExpandedKeys={['select']}>
|
||||||
<AccordionItem
|
<AccordionItem
|
||||||
key="select"
|
key="select"
|
||||||
aria-label="Pick Color"
|
aria-label="Pick Color"
|
||||||
title="选择主题"
|
title="选择主题"
|
||||||
subtitle="点击立即生效"
|
subtitle="可以切换夜间/白昼模式查看对应颜色"
|
||||||
className="shadow-small"
|
className="shadow-small"
|
||||||
|
startContent={<IoIosColorPalette />}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{themes.map((theme) => (
|
{themes.map((theme) => (
|
||||||
<div
|
<PreviewThemeCard
|
||||||
key={theme.name}
|
key={theme.name}
|
||||||
className="p-4 rounded-md cursor-pointer"
|
theme={theme}
|
||||||
style={{
|
onPreview={() => {
|
||||||
backgroundColor: theme.bgColor,
|
|
||||||
color: theme.textColor
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
setOnebotValue('theme', theme.theme)
|
setOnebotValue('theme', theme.theme)
|
||||||
onSubmit()
|
|
||||||
}}
|
}}
|
||||||
>
|
/>
|
||||||
<div>{theme.name}</div>
|
|
||||||
<div>{theme.author}</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
@@ -217,47 +211,58 @@ const ThemeConfigCard = () => {
|
|||||||
key="pick"
|
key="pick"
|
||||||
aria-label="Pick Color"
|
aria-label="Pick Color"
|
||||||
title="自定义配色"
|
title="自定义配色"
|
||||||
subtitle="需手动点击保存"
|
|
||||||
className="shadow-small"
|
className="shadow-small"
|
||||||
|
startContent={<FaPaintbrush />}
|
||||||
>
|
>
|
||||||
{(['dark', 'light'] as const).map((mode) => (
|
<div className="space-y-2">
|
||||||
<div key={mode}>
|
{(['dark', 'light'] as const).map((mode) => (
|
||||||
<h3>{mode === 'dark' ? '暗色主题' : '亮色主题'}</h3>
|
<div
|
||||||
{colorKeys.map((key) => (
|
key={mode}
|
||||||
<div
|
className={clsx(
|
||||||
key={key}
|
'p-2 rounded-md',
|
||||||
className="grid grid-cols-2 items-center mb-2 gap-2"
|
mode === 'dark' ? 'text-white' : 'text-black',
|
||||||
>
|
mode === 'dark'
|
||||||
<label className="text-right">{key}</label>
|
? 'bg-content1-foreground dark:bg-content1'
|
||||||
<Controller
|
: 'bg-content1 dark:bg-content1-foreground'
|
||||||
control={control}
|
)}
|
||||||
name={`theme.${mode}.${key}`}
|
>
|
||||||
render={({ field: { value, onChange } }) => {
|
<h3 className="text-center p-2 rounded-md bg-content2 mb-2 text-default-800 flex items-center justify-center">
|
||||||
const hslArray = value?.split(' ') ?? [0, 0, 0]
|
{mode === 'dark' ? (
|
||||||
const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`
|
<MdDarkMode size={24} />
|
||||||
return (
|
) : (
|
||||||
<ColorPicker
|
<MdLightMode size={24} />
|
||||||
color={color}
|
)}
|
||||||
onChange={(result) => {
|
{mode === 'dark' ? '夜间模式主题' : '白昼模式主题'}
|
||||||
onChange(
|
</h3>
|
||||||
`${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%`
|
{colorKeys.map((key) => (
|
||||||
)
|
<div
|
||||||
}}
|
key={key}
|
||||||
/>
|
className="grid grid-cols-2 items-center mb-2 gap-2"
|
||||||
)
|
>
|
||||||
}}
|
<label className="text-right">{key}</label>
|
||||||
/>
|
<Controller
|
||||||
</div>
|
control={control}
|
||||||
))}
|
name={`theme.${mode}.${key}`}
|
||||||
</div>
|
render={({ field: { value, onChange } }) => {
|
||||||
))}
|
const hslArray = value?.split(' ') ?? [0, 0, 0]
|
||||||
|
const color = `hsl(${hslArray[0]}, ${hslArray[1]}, ${hslArray[2]})`
|
||||||
<SaveButtons
|
return (
|
||||||
onSubmit={onSubmit}
|
<ColorPicker
|
||||||
reset={reset}
|
color={color}
|
||||||
isSubmitting={isSubmitting}
|
onChange={(result) => {
|
||||||
refresh={onRefresh}
|
onChange(
|
||||||
/>
|
`${result.hsl.h} ${result.hsl.s * 100}% ${result.hsl.l * 100}%`
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</>
|
</>
|
||||||
|
2
napcat.webui/src/types/theme.d.ts
vendored
2
napcat.webui/src/types/theme.d.ts
vendored
@@ -1,8 +1,6 @@
|
|||||||
interface ThemeInfo {
|
interface ThemeInfo {
|
||||||
theme: ThemeConfig
|
theme: ThemeConfig
|
||||||
name: string
|
name: string
|
||||||
bgColor: string
|
|
||||||
textColor: string
|
|
||||||
description?: string
|
description?: string
|
||||||
author?: string
|
author?: string
|
||||||
}
|
}
|
||||||
|
@@ -13,3 +13,129 @@ export function loadTheme() {
|
|||||||
console.error('Failed to load theme.css')
|
console.error('Failed to load theme.css')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const colorKeys = [
|
||||||
|
'--heroui-background',
|
||||||
|
|
||||||
|
'--heroui-foreground-50',
|
||||||
|
'--heroui-foreground-100',
|
||||||
|
'--heroui-foreground-200',
|
||||||
|
'--heroui-foreground-300',
|
||||||
|
'--heroui-foreground-400',
|
||||||
|
'--heroui-foreground-500',
|
||||||
|
'--heroui-foreground-600',
|
||||||
|
'--heroui-foreground-700',
|
||||||
|
'--heroui-foreground-800',
|
||||||
|
'--heroui-foreground-900',
|
||||||
|
'--heroui-foreground',
|
||||||
|
|
||||||
|
'--heroui-content1',
|
||||||
|
'--heroui-content1-foreground',
|
||||||
|
'--heroui-content2',
|
||||||
|
'--heroui-content2-foreground',
|
||||||
|
'--heroui-content3',
|
||||||
|
'--heroui-content3-foreground',
|
||||||
|
'--heroui-content4',
|
||||||
|
'--heroui-content4-foreground',
|
||||||
|
|
||||||
|
'--heroui-default-50',
|
||||||
|
'--heroui-default-100',
|
||||||
|
'--heroui-default-200',
|
||||||
|
'--heroui-default-300',
|
||||||
|
'--heroui-default-400',
|
||||||
|
'--heroui-default-500',
|
||||||
|
'--heroui-default-600',
|
||||||
|
'--heroui-default-700',
|
||||||
|
'--heroui-default-800',
|
||||||
|
'--heroui-default-900',
|
||||||
|
'--heroui-default-foreground',
|
||||||
|
'--heroui-default',
|
||||||
|
|
||||||
|
'--heroui-danger-50',
|
||||||
|
'--heroui-danger-100',
|
||||||
|
'--heroui-danger-200',
|
||||||
|
'--heroui-danger-300',
|
||||||
|
'--heroui-danger-400',
|
||||||
|
'--heroui-danger-500',
|
||||||
|
'--heroui-danger-600',
|
||||||
|
'--heroui-danger-700',
|
||||||
|
'--heroui-danger-800',
|
||||||
|
'--heroui-danger-900',
|
||||||
|
'--heroui-danger-foreground',
|
||||||
|
'--heroui-danger',
|
||||||
|
|
||||||
|
'--heroui-primary-50',
|
||||||
|
'--heroui-primary-100',
|
||||||
|
'--heroui-primary-200',
|
||||||
|
'--heroui-primary-300',
|
||||||
|
'--heroui-primary-400',
|
||||||
|
'--heroui-primary-500',
|
||||||
|
'--heroui-primary-600',
|
||||||
|
'--heroui-primary-700',
|
||||||
|
'--heroui-primary-800',
|
||||||
|
'--heroui-primary-900',
|
||||||
|
'--heroui-primary-foreground',
|
||||||
|
'--heroui-primary',
|
||||||
|
|
||||||
|
'--heroui-secondary-50',
|
||||||
|
'--heroui-secondary-100',
|
||||||
|
'--heroui-secondary-200',
|
||||||
|
'--heroui-secondary-300',
|
||||||
|
'--heroui-secondary-400',
|
||||||
|
'--heroui-secondary-500',
|
||||||
|
'--heroui-secondary-600',
|
||||||
|
'--heroui-secondary-700',
|
||||||
|
'--heroui-secondary-800',
|
||||||
|
'--heroui-secondary-900',
|
||||||
|
'--heroui-secondary-foreground',
|
||||||
|
'--heroui-secondary',
|
||||||
|
|
||||||
|
'--heroui-success-50',
|
||||||
|
'--heroui-success-100',
|
||||||
|
'--heroui-success-200',
|
||||||
|
'--heroui-success-300',
|
||||||
|
'--heroui-success-400',
|
||||||
|
'--heroui-success-500',
|
||||||
|
'--heroui-success-600',
|
||||||
|
'--heroui-success-700',
|
||||||
|
'--heroui-success-800',
|
||||||
|
'--heroui-success-900',
|
||||||
|
'--heroui-success-foreground',
|
||||||
|
'--heroui-success',
|
||||||
|
|
||||||
|
'--heroui-warning-50',
|
||||||
|
'--heroui-warning-100',
|
||||||
|
'--heroui-warning-200',
|
||||||
|
'--heroui-warning-300',
|
||||||
|
'--heroui-warning-400',
|
||||||
|
'--heroui-warning-500',
|
||||||
|
'--heroui-warning-600',
|
||||||
|
'--heroui-warning-700',
|
||||||
|
'--heroui-warning-800',
|
||||||
|
'--heroui-warning-900',
|
||||||
|
'--heroui-warning-foreground',
|
||||||
|
'--heroui-warning',
|
||||||
|
|
||||||
|
'--heroui-focus',
|
||||||
|
'--heroui-overlay',
|
||||||
|
'--heroui-divider',
|
||||||
|
'--heroui-code-background',
|
||||||
|
'--heroui-strong',
|
||||||
|
'--heroui-code-mdx'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const generateTheme = (theme: ThemeConfig, validField?: string) => {
|
||||||
|
let css = `:root ${validField ? `.${validField}` : ''}, .light ${validField ? `.${validField}` : ''}, [data-theme="light"] ${validField ? `.${validField}` : ''} {`
|
||||||
|
for (const key in theme.light) {
|
||||||
|
const _key = key as keyof ThemeConfigItem
|
||||||
|
css += `${_key}: ${theme.light[_key]};`
|
||||||
|
}
|
||||||
|
css += `}`
|
||||||
|
css += `.dark ${validField ? `.${validField}` : ''}, [data-theme="dark"] ${validField ? `.${validField}` : ''} {`
|
||||||
|
for (const key in theme.dark) {
|
||||||
|
const _key = key as keyof ThemeConfigItem
|
||||||
|
css += `${_key}: ${theme.dark[_key]};`
|
||||||
|
}
|
||||||
|
css += `}`
|
||||||
|
return css
|
||||||
|
}
|
||||||
|
@@ -9,6 +9,12 @@ export default {
|
|||||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||||
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
'./node_modules/@heroui/theme/dist/**/*.{js,ts,jsx,tsx}'
|
||||||
],
|
],
|
||||||
|
safelist: [
|
||||||
|
{
|
||||||
|
pattern:
|
||||||
|
/bg-(primary|secondary|success|danger|warning)-(50|100|200|300|400|500|600|700|800|900)/
|
||||||
|
}
|
||||||
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {}
|
extend: {}
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user