feat: improve visualization of tags
Previously tags where very much focused on color coding email and less about adding additional information. They were also visualized in different ways in different locations. This commit gets rid of all "Color-coding" references, aligns visualization of the tags across the whole project and tries to improve user experience of using tags in general. A search box is shown in the tagging control so the user can quickly search for a tag if they have a huge (more than 10) amount of tags.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { useSettingsStore, DEFAULT_KEYWORDS, KEYWORD_PALETTE, getKeywordVisibility } from '../settings-store';
|
||||
import { useSettingsStore, DEFAULT_KEYWORDS, KEYWORD_PALETTE, KEYWORD_PALETTE_ROWS, getKeywordVisibility } from '../settings-store';
|
||||
import type { KeywordDefinition } from '../settings-store';
|
||||
|
||||
describe('settings-store keywords', () => {
|
||||
@@ -16,7 +16,7 @@ describe('settings-store keywords', () => {
|
||||
DEFAULT_KEYWORDS.forEach((kw) => {
|
||||
expect(KEYWORD_PALETTE[kw.color]).toBeDefined();
|
||||
expect(KEYWORD_PALETTE[kw.color].dot).toBeTruthy();
|
||||
expect(KEYWORD_PALETTE[kw.color].bg).toBeTruthy();
|
||||
expect(KEYWORD_PALETTE[kw.color].fill).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,14 +27,39 @@ describe('settings-store keywords', () => {
|
||||
});
|
||||
|
||||
describe('KEYWORD_PALETTE', () => {
|
||||
it('has 13 colors', () => {
|
||||
expect(Object.keys(KEYWORD_PALETTE)).toHaveLength(13);
|
||||
it('has a lighter, base and darker shade of every hue', () => {
|
||||
expect(KEYWORD_PALETTE_ROWS).toHaveLength(3);
|
||||
KEYWORD_PALETTE_ROWS.forEach((row) => expect(row).toHaveLength(13));
|
||||
expect(Object.keys(KEYWORD_PALETTE)).toHaveLength(39);
|
||||
});
|
||||
|
||||
it('each color has dot and bg classes', () => {
|
||||
it('lays every row out in the same hue order', () => {
|
||||
const [light, base, dark] = KEYWORD_PALETTE_ROWS;
|
||||
expect(light).toEqual(base.map((key) => `${key}-light`));
|
||||
expect(dark).toEqual(base.map((key) => `${key}-dark`));
|
||||
});
|
||||
|
||||
it('keeps the bare hue name on the base row, so saved tags still resolve', () => {
|
||||
// A tag stored as `red` predates the lighter and darker rows.
|
||||
expect(KEYWORD_PALETTE_ROWS[1]).toContain('red');
|
||||
expect(KEYWORD_PALETTE.red).toBeDefined();
|
||||
});
|
||||
|
||||
it('spells every class out so Tailwind can find it', () => {
|
||||
// A composed class name would compile to nothing, so none may be built
|
||||
// at runtime and each has to carry its own utility prefix.
|
||||
Object.values(KEYWORD_PALETTE).forEach((entry) => {
|
||||
expect(entry.dot).toMatch(/^bg-/);
|
||||
expect(entry.bg).toMatch(/^bg-/);
|
||||
expect(entry.fill).toMatch(/^bg-/);
|
||||
expect(entry.border).toMatch(/^border-/);
|
||||
expect(entry.text).toMatch(/^text-.* dark:text-/);
|
||||
expect(entry.rowTint).toMatch(/^bg-.* dark:bg-/);
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves every row key', () => {
|
||||
KEYWORD_PALETTE_ROWS.flat().forEach((key) => {
|
||||
expect(KEYWORD_PALETTE[key]).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+78
-15
@@ -125,23 +125,86 @@ export interface SidebarApp {
|
||||
showOnMobile: boolean;
|
||||
}
|
||||
|
||||
// Available color palette for keywords
|
||||
export const KEYWORD_PALETTE: Record<string, { dot: string; bg: string }> = {
|
||||
red: { dot: 'bg-red-500', bg: 'bg-red-50 dark:bg-red-950/30' },
|
||||
orange: { dot: 'bg-orange-500', bg: 'bg-orange-50 dark:bg-orange-950/30' },
|
||||
yellow: { dot: 'bg-yellow-500', bg: 'bg-yellow-50 dark:bg-yellow-950/30' },
|
||||
green: { dot: 'bg-green-500', bg: 'bg-green-50 dark:bg-green-950/30' },
|
||||
blue: { dot: 'bg-blue-500', bg: 'bg-blue-50 dark:bg-blue-950/30' },
|
||||
purple: { dot: 'bg-purple-500', bg: 'bg-purple-50 dark:bg-purple-950/30' },
|
||||
pink: { dot: 'bg-pink-500', bg: 'bg-pink-50 dark:bg-pink-950/30' },
|
||||
teal: { dot: 'bg-teal-500', bg: 'bg-teal-50 dark:bg-teal-950/30' },
|
||||
cyan: { dot: 'bg-cyan-500', bg: 'bg-cyan-50 dark:bg-cyan-950/30' },
|
||||
indigo: { dot: 'bg-indigo-500', bg: 'bg-indigo-50 dark:bg-indigo-950/30' },
|
||||
amber: { dot: 'bg-amber-500', bg: 'bg-amber-50 dark:bg-amber-950/30' },
|
||||
lime: { dot: 'bg-lime-500', bg: 'bg-lime-50 dark:bg-lime-950/30' },
|
||||
gray: { dot: 'bg-gray-500', bg: 'bg-gray-50 dark:bg-gray-950/30' },
|
||||
export interface KeywordColor {
|
||||
/** Solid swatch: the dot form and the settings swatches. */
|
||||
dot: string;
|
||||
/** The same solid colour as `dot`, for glyphs that take a text colour. */
|
||||
icon: string;
|
||||
/** Lozenge background. */
|
||||
fill: string;
|
||||
/** Lozenge border. */
|
||||
border: string;
|
||||
/** Lozenge text. */
|
||||
text: string;
|
||||
/** Full-row wash when `tintListRowsByTag` is on. */
|
||||
rowTint: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag colours, written out literally.
|
||||
*
|
||||
* Tailwind v4 scans this file, but only for classes that appear verbatim -
|
||||
* a composed `bg-${hue}-500` would compile to nothing. Every shade a tag can
|
||||
* take therefore has to be spelled out, which is why this map is long.
|
||||
*
|
||||
* Three shades per hue: the middle one keeps the bare hue name, so a tag
|
||||
* saved before the lighter and darker rows existed still resolves.
|
||||
*/
|
||||
export const KEYWORD_PALETTE: Record<string, KeywordColor> = {
|
||||
// light
|
||||
'red-light': { dot: 'bg-red-300', icon: 'text-red-300', fill: 'bg-red-300/10', border: 'border-red-300/30', text: 'text-red-600 dark:text-red-200', rowTint: 'bg-red-50/60 dark:bg-red-950/20' },
|
||||
'orange-light': { dot: 'bg-orange-300', icon: 'text-orange-300', fill: 'bg-orange-300/10', border: 'border-orange-300/30', text: 'text-orange-600 dark:text-orange-200', rowTint: 'bg-orange-50/60 dark:bg-orange-950/20' },
|
||||
'amber-light': { dot: 'bg-amber-300', icon: 'text-amber-300', fill: 'bg-amber-300/10', border: 'border-amber-300/30', text: 'text-amber-600 dark:text-amber-200', rowTint: 'bg-amber-50/60 dark:bg-amber-950/20' },
|
||||
'yellow-light': { dot: 'bg-yellow-300', icon: 'text-yellow-300', fill: 'bg-yellow-300/10', border: 'border-yellow-300/30', text: 'text-yellow-600 dark:text-yellow-200', rowTint: 'bg-yellow-50/60 dark:bg-yellow-950/20' },
|
||||
'lime-light': { dot: 'bg-lime-300', icon: 'text-lime-300', fill: 'bg-lime-300/10', border: 'border-lime-300/30', text: 'text-lime-600 dark:text-lime-200', rowTint: 'bg-lime-50/60 dark:bg-lime-950/20' },
|
||||
'green-light': { dot: 'bg-green-300', icon: 'text-green-300', fill: 'bg-green-300/10', border: 'border-green-300/30', text: 'text-green-600 dark:text-green-200', rowTint: 'bg-green-50/60 dark:bg-green-950/20' },
|
||||
'teal-light': { dot: 'bg-teal-300', icon: 'text-teal-300', fill: 'bg-teal-300/10', border: 'border-teal-300/30', text: 'text-teal-600 dark:text-teal-200', rowTint: 'bg-teal-50/60 dark:bg-teal-950/20' },
|
||||
'cyan-light': { dot: 'bg-cyan-300', icon: 'text-cyan-300', fill: 'bg-cyan-300/10', border: 'border-cyan-300/30', text: 'text-cyan-600 dark:text-cyan-200', rowTint: 'bg-cyan-50/60 dark:bg-cyan-950/20' },
|
||||
'blue-light': { dot: 'bg-blue-300', icon: 'text-blue-300', fill: 'bg-blue-300/10', border: 'border-blue-300/30', text: 'text-blue-600 dark:text-blue-200', rowTint: 'bg-blue-50/60 dark:bg-blue-950/20' },
|
||||
'indigo-light': { dot: 'bg-indigo-300', icon: 'text-indigo-300', fill: 'bg-indigo-300/10', border: 'border-indigo-300/30', text: 'text-indigo-600 dark:text-indigo-200', rowTint: 'bg-indigo-50/60 dark:bg-indigo-950/20' },
|
||||
'purple-light': { dot: 'bg-purple-300', icon: 'text-purple-300', fill: 'bg-purple-300/10', border: 'border-purple-300/30', text: 'text-purple-600 dark:text-purple-200', rowTint: 'bg-purple-50/60 dark:bg-purple-950/20' },
|
||||
'pink-light': { dot: 'bg-pink-300', icon: 'text-pink-300', fill: 'bg-pink-300/10', border: 'border-pink-300/30', text: 'text-pink-600 dark:text-pink-200', rowTint: 'bg-pink-50/60 dark:bg-pink-950/20' },
|
||||
'gray-light': { dot: 'bg-gray-300', icon: 'text-gray-300', fill: 'bg-gray-300/10', border: 'border-gray-300/30', text: 'text-gray-600 dark:text-gray-200', rowTint: 'bg-gray-50/60 dark:bg-gray-950/20' },
|
||||
// base
|
||||
red: { dot: 'bg-red-500', icon: 'text-red-500', fill: 'bg-red-500/10', border: 'border-red-500/30', text: 'text-red-700 dark:text-red-300', rowTint: 'bg-red-50 dark:bg-red-950/30' },
|
||||
orange: { dot: 'bg-orange-500', icon: 'text-orange-500', fill: 'bg-orange-500/10', border: 'border-orange-500/30', text: 'text-orange-700 dark:text-orange-300', rowTint: 'bg-orange-50 dark:bg-orange-950/30' },
|
||||
amber: { dot: 'bg-amber-500', icon: 'text-amber-500', fill: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-700 dark:text-amber-300', rowTint: 'bg-amber-50 dark:bg-amber-950/30' },
|
||||
yellow: { dot: 'bg-yellow-500', icon: 'text-yellow-500', fill: 'bg-yellow-500/10', border: 'border-yellow-500/30', text: 'text-yellow-700 dark:text-yellow-300', rowTint: 'bg-yellow-50 dark:bg-yellow-950/30' },
|
||||
lime: { dot: 'bg-lime-500', icon: 'text-lime-500', fill: 'bg-lime-500/10', border: 'border-lime-500/30', text: 'text-lime-700 dark:text-lime-300', rowTint: 'bg-lime-50 dark:bg-lime-950/30' },
|
||||
green: { dot: 'bg-green-500', icon: 'text-green-500', fill: 'bg-green-500/10', border: 'border-green-500/30', text: 'text-green-700 dark:text-green-300', rowTint: 'bg-green-50 dark:bg-green-950/30' },
|
||||
teal: { dot: 'bg-teal-500', icon: 'text-teal-500', fill: 'bg-teal-500/10', border: 'border-teal-500/30', text: 'text-teal-700 dark:text-teal-300', rowTint: 'bg-teal-50 dark:bg-teal-950/30' },
|
||||
cyan: { dot: 'bg-cyan-500', icon: 'text-cyan-500', fill: 'bg-cyan-500/10', border: 'border-cyan-500/30', text: 'text-cyan-700 dark:text-cyan-300', rowTint: 'bg-cyan-50 dark:bg-cyan-950/30' },
|
||||
blue: { dot: 'bg-blue-500', icon: 'text-blue-500', fill: 'bg-blue-500/10', border: 'border-blue-500/30', text: 'text-blue-700 dark:text-blue-300', rowTint: 'bg-blue-50 dark:bg-blue-950/30' },
|
||||
indigo: { dot: 'bg-indigo-500', icon: 'text-indigo-500', fill: 'bg-indigo-500/10', border: 'border-indigo-500/30', text: 'text-indigo-700 dark:text-indigo-300', rowTint: 'bg-indigo-50 dark:bg-indigo-950/30' },
|
||||
purple: { dot: 'bg-purple-500', icon: 'text-purple-500', fill: 'bg-purple-500/10', border: 'border-purple-500/30', text: 'text-purple-700 dark:text-purple-300', rowTint: 'bg-purple-50 dark:bg-purple-950/30' },
|
||||
pink: { dot: 'bg-pink-500', icon: 'text-pink-500', fill: 'bg-pink-500/10', border: 'border-pink-500/30', text: 'text-pink-700 dark:text-pink-300', rowTint: 'bg-pink-50 dark:bg-pink-950/30' },
|
||||
gray: { dot: 'bg-gray-500', icon: 'text-gray-500', fill: 'bg-gray-500/10', border: 'border-gray-500/30', text: 'text-gray-700 dark:text-gray-300', rowTint: 'bg-gray-50 dark:bg-gray-950/30' },
|
||||
// dark
|
||||
'red-dark': { dot: 'bg-red-700', icon: 'text-red-700', fill: 'bg-red-700/10', border: 'border-red-700/30', text: 'text-red-800 dark:text-red-400', rowTint: 'bg-red-100 dark:bg-red-950/50' },
|
||||
'orange-dark': { dot: 'bg-orange-700', icon: 'text-orange-700', fill: 'bg-orange-700/10', border: 'border-orange-700/30', text: 'text-orange-800 dark:text-orange-400', rowTint: 'bg-orange-100 dark:bg-orange-950/50' },
|
||||
'amber-dark': { dot: 'bg-amber-700', icon: 'text-amber-700', fill: 'bg-amber-700/10', border: 'border-amber-700/30', text: 'text-amber-800 dark:text-amber-400', rowTint: 'bg-amber-100 dark:bg-amber-950/50' },
|
||||
'yellow-dark': { dot: 'bg-yellow-700', icon: 'text-yellow-700', fill: 'bg-yellow-700/10', border: 'border-yellow-700/30', text: 'text-yellow-800 dark:text-yellow-400', rowTint: 'bg-yellow-100 dark:bg-yellow-950/50' },
|
||||
'lime-dark': { dot: 'bg-lime-700', icon: 'text-lime-700', fill: 'bg-lime-700/10', border: 'border-lime-700/30', text: 'text-lime-800 dark:text-lime-400', rowTint: 'bg-lime-100 dark:bg-lime-950/50' },
|
||||
'green-dark': { dot: 'bg-green-700', icon: 'text-green-700', fill: 'bg-green-700/10', border: 'border-green-700/30', text: 'text-green-800 dark:text-green-400', rowTint: 'bg-green-100 dark:bg-green-950/50' },
|
||||
'teal-dark': { dot: 'bg-teal-700', icon: 'text-teal-700', fill: 'bg-teal-700/10', border: 'border-teal-700/30', text: 'text-teal-800 dark:text-teal-400', rowTint: 'bg-teal-100 dark:bg-teal-950/50' },
|
||||
'cyan-dark': { dot: 'bg-cyan-700', icon: 'text-cyan-700', fill: 'bg-cyan-700/10', border: 'border-cyan-700/30', text: 'text-cyan-800 dark:text-cyan-400', rowTint: 'bg-cyan-100 dark:bg-cyan-950/50' },
|
||||
'blue-dark': { dot: 'bg-blue-700', icon: 'text-blue-700', fill: 'bg-blue-700/10', border: 'border-blue-700/30', text: 'text-blue-800 dark:text-blue-400', rowTint: 'bg-blue-100 dark:bg-blue-950/50' },
|
||||
'indigo-dark': { dot: 'bg-indigo-700', icon: 'text-indigo-700', fill: 'bg-indigo-700/10', border: 'border-indigo-700/30', text: 'text-indigo-800 dark:text-indigo-400', rowTint: 'bg-indigo-100 dark:bg-indigo-950/50' },
|
||||
'purple-dark': { dot: 'bg-purple-700', icon: 'text-purple-700', fill: 'bg-purple-700/10', border: 'border-purple-700/30', text: 'text-purple-800 dark:text-purple-400', rowTint: 'bg-purple-100 dark:bg-purple-950/50' },
|
||||
'pink-dark': { dot: 'bg-pink-700', icon: 'text-pink-700', fill: 'bg-pink-700/10', border: 'border-pink-700/30', text: 'text-pink-800 dark:text-pink-400', rowTint: 'bg-pink-100 dark:bg-pink-950/50' },
|
||||
'gray-dark': { dot: 'bg-gray-700', icon: 'text-gray-700', fill: 'bg-gray-700/10', border: 'border-gray-700/30', text: 'text-gray-800 dark:text-gray-400', rowTint: 'bg-gray-100 dark:bg-gray-950/50' },
|
||||
} as const;
|
||||
|
||||
/** Palette laid out as the settings picker shows it: lighter, base, darker. */
|
||||
export const KEYWORD_PALETTE_ROWS: string[][] = [
|
||||
['red-light', 'orange-light', 'amber-light', 'yellow-light', 'lime-light', 'green-light', 'teal-light', 'cyan-light', 'blue-light', 'indigo-light', 'purple-light', 'pink-light', 'gray-light'],
|
||||
['red', 'orange', 'amber', 'yellow', 'lime', 'green', 'teal', 'cyan', 'blue', 'indigo', 'purple', 'pink', 'gray'],
|
||||
['red-dark', 'orange-dark', 'amber-dark', 'yellow-dark', 'lime-dark', 'green-dark', 'teal-dark', 'cyan-dark', 'blue-dark', 'indigo-dark', 'purple-dark', 'pink-dark', 'gray-dark'],
|
||||
];
|
||||
|
||||
/** The colour a tag falls back to when its definition is gone. */
|
||||
export const FALLBACK_KEYWORD_COLOR = 'gray';
|
||||
|
||||
export const DEFAULT_KEYWORDS: KeywordDefinition[] = [
|
||||
{ id: 'red', label: 'Red', color: 'red' },
|
||||
{ id: 'orange', label: 'Orange', color: 'orange' },
|
||||
|
||||
Reference in New Issue
Block a user