Files
TREK/client/src/components/Collab/CollabNotesCategorySettingsModal.tsx
T
Maurice 741639edf0 feat(appearance): granular per-size text scaling with live preview
The text-size control now adjusts each size class (Large / Medium / Normal / Small) independently as well as all-at-once. Inline px sizes are mapped to a class by their value, so the per-class sliders reach real content; each class variable = global factor x its per-class factor (no double-scaling with the root font-size that handles rem text). The settings UI gains a live preview that resizes as you drag, and the four size sliders sit behind a clear toggle.
2026-06-29 13:59:00 +02:00

146 lines
7.0 KiB
TypeScript

import ReactDOM from 'react-dom'
import { useState } from 'react'
import { Plus, Trash2, X } from 'lucide-react'
import { FONT, NOTE_COLORS } from './CollabNotes.constants'
import { EditableCatName } from './CollabNotesEditableCatName'
// ── Category Settings Modal ──────────────────────────────────────────────────
interface CategorySettingsModalProps {
onClose: () => void
categories: string[]
categoryColors: Record<string, string>
onSave: (colors: Record<string, string>) => void
onRenameCategory: (oldName: string, newName: string) => Promise<void>
t: (key: string) => string
}
export function CategorySettingsModal({ onClose, categories, categoryColors, onSave, onRenameCategory, t }: CategorySettingsModalProps) {
const [localColors, setLocalColors] = useState({ ...categoryColors })
const [renames, setRenames] = useState<Record<string, string>>({}) // { oldName: newName }
const [newCatName, setNewCatName] = useState('')
const handleColorChange = (cat, color) => {
setLocalColors(prev => ({ ...prev, [cat]: color }))
}
const handleAddCategory = () => {
if (!newCatName.trim() || localColors[newCatName.trim()]) return
setLocalColors(prev => ({ ...prev, [newCatName.trim()]: NOTE_COLORS[Object.keys(prev).length % NOTE_COLORS.length].value }))
setNewCatName('')
}
const handleRemoveCategory = (cat) => {
setLocalColors(prev => { const n = { ...prev }; delete n[cat]; return n })
}
const handleRenameCategory = (oldName, newName) => {
if (!newName.trim() || newName.trim() === oldName || localColors[newName.trim()]) return
// Track rename for saving to DB later
const originalName = Object.entries(renames).find(([, v]) => v === oldName)?.[0] || oldName
setRenames(prev => ({ ...prev, [originalName]: newName.trim() }))
setLocalColors(prev => {
const n = {}
for (const [k, v] of Object.entries(prev)) {
n[k === oldName ? newName.trim() : k] = v
}
return n
})
}
const handleSave = async () => {
// Apply renames to notes in DB
for (const [oldName, newName] of Object.entries(renames)) {
if (oldName !== newName) await onRenameCategory(oldName, newName)
}
await onSave(localColors)
onClose()
}
// Merge existing categories from notes with saved colors
const allCats = [...new Set([...categories, ...Object.keys(localColors)])]
return ReactDOM.createPortal(
<div style={{
position: 'fixed', inset: 0, background: 'var(--overlay-bg, rgba(0,0,0,0.35))',
backdropFilter: 'blur(6px)', WebkitBackdropFilter: 'blur(6px)',
display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 9999, padding: 16, fontFamily: FONT,
}} onClick={onClose}>
<div style={{
background: 'var(--bg-card)', borderRadius: 16, width: '100%', maxWidth: 420,
maxHeight: '80vh', overflow: 'auto', border: '1px solid var(--border-faint)',
}} onClick={e => e.stopPropagation()}>
{/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 16px 12px', borderBottom: '1px solid var(--border-faint)' }}>
<h3 style={{ fontSize: 'calc(14px * var(--fs-scale-body, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: 0 }}>
{t('collab.notes.categorySettings') || 'Category Settings'}
</h3>
<button onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 2, display: 'flex' }}>
<X size={16} />
</button>
</div>
{/* Categories list */}
<div style={{ padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
{allCats.length === 0 && (
<p style={{ fontSize: 'calc(12px * var(--fs-scale-body, 1))', color: 'var(--text-faint)', textAlign: 'center', padding: 16 }}>
{t('collab.notes.noCategoriesYet') || 'No categories yet'}
</p>
)}
{allCats.map(cat => (
<div key={cat} style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
{/* Color swatches */}
<div style={{ display: 'flex', gap: 4 }}>
{NOTE_COLORS.map(c => (
<button key={c.value} onClick={() => handleColorChange(cat, c.value)} style={{
width: 20, height: 20, borderRadius: 6, background: c.value, border: 'none', cursor: 'pointer', padding: 0,
outline: (localColors[cat] || NOTE_COLORS[0].value) === c.value ? '2px solid var(--text-primary)' : '2px solid transparent',
outlineOffset: 1, transition: 'transform 0.1s',
transform: (localColors[cat] || NOTE_COLORS[0].value) === c.value ? 'scale(1.1)' : 'scale(1)',
}} />
))}
</div>
{/* Category name — editable */}
<EditableCatName name={cat} onRename={(newName) => handleRenameCategory(cat, newName)} />
{/* Delete */}
<button onClick={() => handleRemoveCategory(cat)} style={{
background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-faint)', padding: 3, display: 'flex',
}}
onMouseEnter={e => e.currentTarget.style.color = '#ef4444'}
onMouseLeave={e => e.currentTarget.style.color = 'var(--text-faint)'}>
<Trash2 size={13} />
</button>
</div>
))}
{/* Add new */}
<div style={{ display: 'flex', gap: 6, marginTop: 4 }}>
<input value={newCatName} onChange={e => setNewCatName(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleAddCategory()}
placeholder={t('collab.notes.newCategory')}
style={{
flex: 1, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '8px 12px',
fontSize: 'calc(13px * var(--fs-scale-body, 1))', background: 'var(--bg-input)', color: 'var(--text-primary)', fontFamily: 'inherit', outline: 'none',
}} />
<button onClick={handleAddCategory} disabled={!newCatName.trim()} style={{
background: newCatName.trim() ? 'var(--accent)' : 'var(--border-primary)', color: 'var(--accent-text)',
border: 'none', borderRadius: 10, padding: '8px 14px', cursor: newCatName.trim() ? 'pointer' : 'default',
display: 'flex', alignItems: 'center', flexShrink: 0,
}}>
<Plus size={14} />
</button>
</div>
{/* Save */}
<button onClick={handleSave} style={{
width: '100%', borderRadius: 99, padding: '9px 14px', background: 'var(--accent)', color: 'var(--accent-text)',
fontSize: 'calc(13px * var(--fs-scale-body, 1))', fontWeight: 600, border: 'none', cursor: 'pointer', marginTop: 8,
}}>
{t('collab.notes.save')}
</button>
</div>
</div>
</div>,
document.body
)
}