import { BOOK_FONT_SLOTS, BOOK_PALETTE_KEYS, BOOK_THEME_DECORATIONS, BOOK_THEME_MOTIFS, BOOK_THEME_PRESETS, bookThemeOf, type BookFontSlot, type BookPaletteKey, type BookThemeFont, type BookThemePreset, } from '@trek/shared'; import { Check, ImageIcon, RotateCcw } from 'lucide-react'; import { useMemo } from 'react'; import '../../../styles/studio-theme.css'; import { useToast } from '../../shared/Toast'; import { Choice, Line, Picks, Section, Switch } from '../StudioControls'; import { StudioFontPicker } from '../StudioFontPicker'; import { PanelHead, Shelf } from '../StudioPanelHead'; import type { StudioT } from '../StudioSidebar'; import { ThemeSheet } from './ThemeSheet'; import { useStudioTheme } from './useStudioTheme'; /** * The Theme section of the rail: seventeen built-in looks, the palette, the * typefaces and the motifs of the one being put together, and applying it to * the book or to the page that is open. * * Nothing changes on the page until it is applied: picking a theme and * trying colours is a draft, so the panel can be played with freely, and an * apply is one undo step however many pages it recoloured. */ /** One built-in theme as a card: its paper, its display face in its ink, its three accents. */ function ThemeCard({ preset, on, inUse, onPick, t, }: { preset: BookThemePreset; on: boolean; inUse: boolean; onPick: () => void; t: StudioT; }) { const display = useMemo(() => bookThemeOf(preset.id).fonts.display, [preset.id]); return ( ); } /** * One palette entry: its colour, which is the picker, and its name. The three * accents share one name under them, so each shows its colour alone and says * its own name to a screen reader and on hover. */ function PaletteEntry({ name, hex, onPick, t, }: { name: BookPaletteKey; hex: string; onPick: (hex: string) => void; t: StudioT; }) { const accent = name.startsWith('accent'); const label = t(`studio.theme.token.${name}`); return ( ); } /** The script a theme falls back to when one is switched on: the handwriting most themes pair with. */ const DEFAULT_SCRIPT = 'caveat'; /** * One typeface slot, in the Studio's own font picker. The script can be * switched off, since a calm theme sets no handwriting at all. */ function FontSlot({ slot, face, onPick, t, }: { slot: BookFontSlot; face: BookThemeFont | null; onPick: (font: string | null, weight?: number) => void; t: StudioT; }) { const label = t(`studio.theme.slot.${slot}`); const picker = face && ( onPick(p.font, p.weight)} t={t} /> ); if (slot !== 'script') return ( {picker} ); return (
onPick(face ? null : DEFAULT_SCRIPT)} /> {picker ??

{t('studio.theme.noScript')}

}
); } export function StudioThemePanel({ t }: { t: StudioT }) { const theme = useStudioTheme(); const toast = useToast(); const { draft } = theme; const apply = (scope: 'book' | 'page') => { const done = theme.apply(scope); if (!done) return; if (done.pages > 0) toast.success(t('studio.theme.applied')); else toast.info(t('studio.theme.nothing')); }; const fromPhotos = async () => { if (!(await theme.fromPhotos())) toast.info(t('studio.theme.noPhotoColours')); }; return ( <>

{t('studio.theme.intro')}

{BOOK_THEME_PRESETS.map((preset) => ( theme.pick(preset.id)} t={t} /> ))}
{theme.edited && ( )}
{BOOK_PALETTE_KEYS.map((key) => ( theme.setColour(key, hex)} t={t} /> ))} {t('studio.theme.accents')}
{BOOK_FONT_SLOTS.map((slot) => ( theme.setFont(slot, font, weight)} t={t} /> ))}
({ value: m, label: t(`studio.theme.motif.${m}`) }))} onToggle={theme.toggleMotif} />
({ value: d, label: t(`studio.theme.decoration.${d}`) }))} onPick={theme.setDecoration} ariaLabel={t('studio.theme.decoration')} />
theme.setAdopt(!theme.adopt)} />

{t('studio.theme.adoptHint')}

); }