neurapress/src/components/editor/StyleConfigDialog.tsx
2025-01-30 14:51:23 +08:00

230 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useState } from 'react'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { Label } from '@/components/ui/label'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Settings } from 'lucide-react'
import { type RendererOptions } from '@/lib/markdown'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
const themeColors = [
{ name: '经典黑', value: '#1a1a1a' },
{ name: '深蓝', value: '#1e40af' },
{ name: '墨绿', value: '#065f46' },
{ name: '深紫', value: '#5b21b6' },
{ name: '酒红', value: '#991b1b' },
{ name: '海蓝', value: '#0369a1' },
{ name: '森绿', value: '#166534' },
{ name: '靛蓝', value: '#1e3a8a' },
{ name: '玫红', value: '#9d174d' },
{ name: '橙色', value: '#c2410c' },
{ name: '棕褐', value: '#713f12' },
{ name: '石墨', value: '#374151' },
]
interface StyleConfigDialogProps {
value: RendererOptions
onChangeAction: (options: RendererOptions) => void
}
export function StyleConfigDialog({ value, onChangeAction }: StyleConfigDialogProps) {
const [currentOptions, setCurrentOptions] = useState<RendererOptions>(value)
const [customizedFields, setCustomizedFields] = useState<Set<string>>(new Set())
const handleOptionChange = (
category: keyof RendererOptions,
subcategory: string,
value: string | null
) => {
setCustomizedFields(prev => {
const next = new Set(prev)
if (value === null) {
next.delete(`${category}.${subcategory}`)
} else {
next.add(`${category}.${subcategory}`)
}
return next
})
const newOptions = {
...currentOptions,
[category]: {
...currentOptions[category],
[subcategory]: value === null ? undefined : value
}
}
// 如果是主题颜色变更,同时更新标题颜色
if (category === 'base' && subcategory === 'themeColor') {
if (value === null) {
// 重置为模板默认值
newOptions.block = {
...newOptions.block,
h1: { ...(newOptions.block?.h1 || {}), color: undefined },
h2: { ...(newOptions.block?.h2 || {}), color: undefined },
h3: { ...(newOptions.block?.h3 || {}), color: undefined }
}
} else {
newOptions.block = {
...newOptions.block,
h1: { ...(newOptions.block?.h1 || {}), color: value },
h2: { ...(newOptions.block?.h2 || {}), color: value },
h3: { ...(newOptions.block?.h3 || {}), color: value }
}
}
}
setCurrentOptions(newOptions)
onChangeAction(newOptions)
}
const resetToDefault = (field: string) => {
const [category, subcategory] = field.split('.')
handleOptionChange(category as keyof RendererOptions, subcategory, null)
}
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline" size="sm">
<Settings className="h-4 w-4 mr-2" />
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="space-y-6">
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label></Label>
{customizedFields.has('base.themeColor') && (
<Button
variant="ghost"
size="sm"
onClick={() => resetToDefault('base.themeColor')}
>
</Button>
)}
</div>
<div className="grid grid-cols-6 gap-2 mb-2">
{themeColors.map((color) => (
<button
key={color.value}
className={`w-8 h-8 rounded-full border-2 transition-all ${
currentOptions.base?.themeColor === color.value
? 'border-primary scale-110'
: 'border-transparent hover:scale-105'
}`}
style={{ backgroundColor: color.value }}
onClick={() => handleOptionChange('base', 'themeColor', color.value)}
title={color.name}
/>
))}
</div>
<div className="flex gap-2">
<Input
type="color"
value={currentOptions.base?.themeColor || '#1a1a1a'}
className="w-16 h-8 p-1"
onChange={(e) => handleOptionChange('base', 'themeColor', e.target.value)}
/>
<Input
value={currentOptions.base?.themeColor || '#1a1a1a'}
onChange={(e) => handleOptionChange('base', 'themeColor', e.target.value)}
/>
</div>
<p className="text-sm text-muted-foreground">
</p>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label></Label>
{customizedFields.has('base.fontSize') && (
<Button
variant="ghost"
size="sm"
onClick={() => resetToDefault('base.fontSize')}
>
</Button>
)}
</div>
<div className="flex gap-2">
<Input
type="number"
min="12"
max="24"
value={parseInt(currentOptions.base?.fontSize || '15')}
className="w-24"
onChange={(e) => handleOptionChange('base', 'fontSize', `${e.target.value}px`)}
/>
<span className="flex items-center">px</span>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label></Label>
{customizedFields.has('base.textAlign') && (
<Button
variant="ghost"
size="sm"
onClick={() => resetToDefault('base.textAlign')}
>
</Button>
)}
</div>
<Select
value={currentOptions.base?.textAlign || 'left'}
onValueChange={(value: string) => handleOptionChange('base', 'textAlign', value)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="left"></SelectItem>
<SelectItem value="center"></SelectItem>
<SelectItem value="right"></SelectItem>
<SelectItem value="justify"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label></Label>
{customizedFields.has('base.lineHeight') && (
<Button
variant="ghost"
size="sm"
onClick={() => resetToDefault('base.lineHeight')}
>
</Button>
)}
</div>
<Input
type="number"
min="1"
max="3"
step="0.1"
value={parseFloat(String(currentOptions.base?.lineHeight || '1.75'))}
onChange={(e) => handleOptionChange('base', 'lineHeight', e.target.value)}
/>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}