feat: custom accent color via color wheel in settings

Add ColorWheel component (hue/saturation wheel + lightness strip + hex input).
themeStore gains accentIdx=-1 for custom mode and setCustom action.
ThemeApplier uses getActiveAccent to support both presets and custom hex.
Settings page shows "Свой" button; clicking reveals the color wheel inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 20:49:29 +03:00
parent 292117cf56
commit 7f2f6f7e44
4 changed files with 261 additions and 13 deletions

View File

@@ -17,22 +17,50 @@ export const ACCENT_PRESETS: AccentPreset[] = [
{ name: 'Минт', accent: '#00FFB2', rgb: '0,255,178' },
]
const STORAGE_KEY = 'pm_accent'
function hexToRgbStr(hex: string): string {
const h = hex.replace('#', '')
const r = parseInt(h.substring(0, 2), 16)
const g = parseInt(h.substring(2, 4), 16)
const b = parseInt(h.substring(4, 6), 16)
return `${r},${g},${b}`
}
const KEY_IDX = 'pm_accent'
const KEY_CUSTOM = 'pm_accent_custom'
interface ThemeStore {
accentIdx: number
accentIdx: number // -1 = custom color
customHex: string
setAccent: (idx: number) => void
setCustom: (hex: string) => void
}
export const useThemeStore = create<ThemeStore>((set) => ({
accentIdx: (() => {
if (typeof window === 'undefined') return 0
const saved = localStorage.getItem(STORAGE_KEY)
const saved = localStorage.getItem(KEY_IDX)
const idx = saved !== null ? parseInt(saved, 10) : 0
return idx >= 0 && idx < ACCENT_PRESETS.length ? idx : 0
return idx >= -1 && idx < ACCENT_PRESETS.length ? idx : 0
})(),
customHex: (() => {
if (typeof window === 'undefined') return '#ff00ff'
return localStorage.getItem(KEY_CUSTOM) || '#ff00ff'
})(),
setAccent: (idx) => {
if (typeof window !== 'undefined') localStorage.setItem(STORAGE_KEY, String(idx))
if (typeof window !== 'undefined') localStorage.setItem(KEY_IDX, String(idx))
set({ accentIdx: idx })
},
setCustom: (hex) => {
if (typeof window !== 'undefined') {
localStorage.setItem(KEY_CUSTOM, hex)
localStorage.setItem(KEY_IDX, '-1')
}
set({ accentIdx: -1, customHex: hex })
},
}))
export function getActiveAccent(accentIdx: number, customHex: string): { accent: string; rgb: string } {
if (accentIdx === -1) return { accent: customHex, rgb: hexToRgbStr(customHex) }
const preset = ACCENT_PRESETS[accentIdx] ?? ACCENT_PRESETS[0]
return { accent: preset.accent, rgb: preset.rgb }
}