TagChip — small pill rendering a tag's name with the colour from the 8-preset
palette. Three usage shapes (mutually exclusive):
* display-only (default)
* with onRemove → renders an inline X (used by picker-selected chips)
* with onSelect → renders the whole chip as a button (used by the lateral
filter bar and by item-row chips that filter the list on click)
Nested <button> was the SSR warning from the first cut — fixed by making
onSelect / onRemove mutually exclusive.
TagPicker — combobox-style picker driven by filterTagOptions (pure helper
in tag-picker-filter.ts so it can be unit-tested without rendering). Lists
matches by substring, surfaces a "Create `{query}`" affordance only when no
existing tag matches the trimmed query exactly. Enter key prefers toggling
the first match, falls back to creating. Re-usable from the item modal,
the list-filter bar, and the settings tag-management section.
tag-picker-filter.test.ts — 7 unit tests (TP-01..07) covering case-
insensitive substring match, empty query → full list, create affordance
gating on exact-match absence, exclusion of already-selected ids, and
createName preserving verbatim user input (no lowercasing).
tailwind.config.ts safelists the runtime-built bg-/text-/ring-/dot
combinations for all 8 preset colours so JIT doesn't strip them.
Messages: 9 new keys in en + es (tag picker, filter bar, settings section).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
48 lines
1.8 KiB
TypeScript
48 lines
1.8 KiB
TypeScript
import type { Config } from 'tailwindcss';
|
||
|
||
// Fase 11: tag colours are picked at runtime from an 8-preset palette
|
||
// (see public.item_tags.color CHECK constraint). Tailwind cannot statically
|
||
// see the class names (they're built from the colour string), so safelist
|
||
// every (variant × intensity) combination the chip + dot use.
|
||
const TAG_COLORS = ['slate', 'red', 'amber', 'green', 'sky', 'indigo', 'pink', 'stone'];
|
||
const tagSafelist = TAG_COLORS.flatMap((c) => [
|
||
`bg-${c}-100`,
|
||
`text-${c}-700`,
|
||
`ring-${c}-200`,
|
||
`dark:bg-${c}-900/40`,
|
||
`dark:text-${c}-200`,
|
||
`dark:ring-${c}-700/50`,
|
||
`bg-${c}-500`
|
||
]);
|
||
|
||
export default {
|
||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||
safelist: tagSafelist,
|
||
// Fase 9.1: drive dark mode from <html data-theme="dark"> so the inline
|
||
// anti-FOUC script in app.html can set the attribute synchronously before
|
||
// the first paint. Existing `dark:` utilities keep working as-is.
|
||
darkMode: ['selector', '[data-theme="dark"]'],
|
||
theme: {
|
||
extend: {
|
||
fontFamily: {
|
||
sans: ['Inter', 'system-ui', 'sans-serif']
|
||
},
|
||
colors: {
|
||
// Semantic aliases — "The Monolith Editorial" surface hierarchy.
|
||
// Use background shifts (never 1px borders) to separate major UI areas.
|
||
background: 'rgb(var(--background) / <alpha-value>)',
|
||
'surface-container-low': 'rgb(var(--surface-container-low) / <alpha-value>)',
|
||
surface: 'rgb(var(--surface) / <alpha-value>)',
|
||
'surface-raised': 'rgb(var(--surface-raised) / <alpha-value>)',
|
||
// Text hierarchy
|
||
'text-primary': 'rgb(var(--text-primary) / <alpha-value>)',
|
||
'text-secondary': 'rgb(var(--text-secondary) / <alpha-value>)',
|
||
'text-muted': 'rgb(var(--text-muted) / <alpha-value>)',
|
||
// Only non-slate color allowed in the system
|
||
destructive: 'rgb(var(--destructive) / <alpha-value>)'
|
||
}
|
||
}
|
||
},
|
||
plugins: []
|
||
} satisfies Config;
|