tag colors
This commit is contained in:
@@ -380,6 +380,13 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
[activeTagIds],
|
[activeTagIds],
|
||||||
);
|
);
|
||||||
const untaggedActive = untaggedFilterId ? activeTagSet.has(untaggedFilterId) : false;
|
const untaggedActive = untaggedFilterId ? activeTagSet.has(untaggedFilterId) : false;
|
||||||
|
const untaggedButtonClassName = ['badge', 'tag-chip', 'tag-chip--untagged', untaggedActive ? 'active' : null]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
const getTagChipClassName = (isActive: boolean) =>
|
||||||
|
['badge', 'tag-chip', isActive ? 'active' : null]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
const handleManageTags = useCallback(() => {
|
const handleManageTags = useCallback(() => {
|
||||||
onManageTags?.();
|
onManageTags?.();
|
||||||
}, [onManageTags]);
|
}, [onManageTags]);
|
||||||
@@ -933,9 +940,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="listitem"
|
role="listitem"
|
||||||
className={`sidebar-tag-pill sidebar-tag-pill--untagged${
|
className={untaggedButtonClassName}
|
||||||
untaggedActive ? ' active' : ''
|
|
||||||
}`}
|
|
||||||
onClick={() => handleToggleTag(untaggedFilterId)}
|
onClick={() => handleToggleTag(untaggedFilterId)}
|
||||||
aria-pressed={untaggedActive}
|
aria-pressed={untaggedActive}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
@@ -946,7 +951,7 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
{tags.map((tag) => {
|
{tags.map((tag) => {
|
||||||
const isActive = activeTagSet.has(tag.id);
|
const isActive = activeTagSet.has(tag.id);
|
||||||
const style = getTagColorStyle(tag.color);
|
const style = getTagColorStyle(tag.color);
|
||||||
const className = `sidebar-tag-pill${isActive ? ' active' : ''}`;
|
const className = getTagChipClassName(isActive);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
|
|||||||
@@ -20,6 +20,12 @@
|
|||||||
--neutral-contrast-baseline: 1.125;
|
--neutral-contrast-baseline: 1.125;
|
||||||
--neutral-contrast-delta: calc(var(--neutral-contrast-amount) - var(--neutral-contrast-baseline));
|
--neutral-contrast-delta: calc(var(--neutral-contrast-amount) - var(--neutral-contrast-baseline));
|
||||||
--neutral-chroma-scale: calc(clamp(0.0001, var(--neutral-chroma-effective), 1) / 0.1);
|
--neutral-chroma-scale: calc(clamp(0.0001, var(--neutral-chroma-effective), 1) / 0.1);
|
||||||
|
--tag-chip-bg-lighten: 0.4;
|
||||||
|
--tag-chip-bg-chroma-scale: 0.75;
|
||||||
|
--tag-chip-outline-lighten: 0.12;
|
||||||
|
--tag-chip-outline-chroma-scale: 0.75;
|
||||||
|
--tag-chip-text-lighten: -0.5;
|
||||||
|
--tag-chip-text-chroma-scale: 0.4;
|
||||||
|
|
||||||
/* --- Neutrals --- */
|
/* --- Neutrals --- */
|
||||||
--bg: oklch(calc(0.97 + var(--neutral-contrast-delta) * 0.12) calc(var(--neutral-chroma-effective) * 0.001) calc(var(--neutral-hue) + var(--surface-hue-shift)));
|
--bg: oklch(calc(0.97 + var(--neutral-contrast-delta) * 0.12) calc(var(--neutral-chroma-effective) * 0.001) calc(var(--neutral-hue) + var(--surface-hue-shift)));
|
||||||
@@ -110,6 +116,11 @@
|
|||||||
--detail-panel-width: calc(100vw / 3);
|
--detail-panel-width: calc(100vw / 3);
|
||||||
--sidebar-width: 20em;
|
--sidebar-width: 20em;
|
||||||
--documents-grid-title-size: 0.8rem;
|
--documents-grid-title-size: 0.8rem;
|
||||||
|
|
||||||
|
--tag-chip-bg-lighten: 0.4;
|
||||||
|
--tag-chip-bg-chroma-scale: 0.75;
|
||||||
|
--tag-chip-outline-lighten: 0.12;
|
||||||
|
--tag-chip-outline-chroma-scale: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme='light'] {
|
:root[data-theme='light'] {
|
||||||
@@ -167,6 +178,13 @@
|
|||||||
--overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
|
--overlay-backdrop: color-mix(in oklch, oklch(0.1 0.015 var(--dark-neutral-hue)) 70%, transparent);
|
||||||
--overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
|
--overlay-shadow: color-mix(in oklch, oklch(0.12 0.015 var(--dark-neutral-hue)) 55%, transparent);
|
||||||
|
|
||||||
|
--tag-chip-bg-lighten: 0.2;
|
||||||
|
--tag-chip-bg-chroma-scale: 0.65;
|
||||||
|
--tag-chip-outline-lighten: 0.08;
|
||||||
|
--tag-chip-outline-chroma-scale: 0.85;
|
||||||
|
--tag-chip-text-lighten: 0.35;
|
||||||
|
--tag-chip-text-chroma-scale: 0.6;
|
||||||
|
|
||||||
--success: oklch(0.62 0.16 var(--success-hue));
|
--success: oklch(0.62 0.16 var(--success-hue));
|
||||||
--warning: oklch(0.68 0.17 var(--warning-hue));
|
--warning: oklch(0.68 0.17 var(--warning-hue));
|
||||||
--danger: oklch(0.60 0.2 var(--danger-hue));
|
--danger: oklch(0.60 0.2 var(--danger-hue));
|
||||||
@@ -190,6 +208,11 @@
|
|||||||
--preview-nav-fg: var(--fg);
|
--preview-nav-fg: var(--fg);
|
||||||
--text-on-dark: color-mix(in oklch, white 92%, transparent);
|
--text-on-dark: color-mix(in oklch, white 92%, transparent);
|
||||||
--surface-ink-soft: color-mix(in oklch, white 12%, transparent);
|
--surface-ink-soft: color-mix(in oklch, white 12%, transparent);
|
||||||
|
|
||||||
|
--tag-chip-bg-lighten: -1.2;
|
||||||
|
--tag-chip-bg-chroma-scale: 1.5;
|
||||||
|
--tag-chip-outline-lighten: 0;
|
||||||
|
--tag-chip-outline-chroma-scale: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.documents-panel tr.folder.is-drop-target {
|
.documents-panel tr.folder.is-drop-target {
|
||||||
outline: 2px dashed var(--accent-strong, var(--accent));
|
outline: 2px dashed var(--accent-strong, var(--accent));
|
||||||
outline-offset: -2px;
|
outline-offset: -2px;
|
||||||
@@ -760,15 +758,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tag-chip {
|
.tag-chip {
|
||||||
|
--tag-chip-resolved-l: var(--tag-chip-base-l, 0.9);
|
||||||
|
--tag-chip-resolved-c: var(--tag-chip-base-c, 0);
|
||||||
|
--tag-chip-resolved-h: var(--tag-chip-base-h, 0deg);
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
padding: 0.2rem 0.4rem;
|
padding: 0.2rem 0.4rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
border: none;
|
||||||
|
background: oklch(
|
||||||
|
min(1, calc(var(--tag-chip-resolved-l) + (1 - var(--tag-chip-resolved-l)) * var(--tag-chip-bg-lighten)))
|
||||||
|
calc(var(--tag-chip-resolved-c) * var(--tag-chip-bg-chroma-scale))
|
||||||
|
var(--tag-chip-resolved-h)
|
||||||
|
/ 0.9
|
||||||
|
);
|
||||||
|
--tag-chip-outline-color: var(
|
||||||
|
--tag-chip-outline,
|
||||||
|
oklch(
|
||||||
|
min(1, calc(var(--tag-chip-resolved-l) + (1 - var(--tag-chip-resolved-l)) * var(--tag-chip-outline-lighten)))
|
||||||
|
min(1, calc(var(--tag-chip-resolved-c) * var(--tag-chip-outline-chroma-scale)))
|
||||||
|
var(--tag-chip-resolved-h)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
--tag-chip-text-color: var(
|
||||||
|
--tag-chip-text,
|
||||||
|
oklch(
|
||||||
|
clamp(0, calc(var(--tag-chip-resolved-l) + var(--tag-chip-text-lighten)), 1)
|
||||||
|
clamp(0, calc(var(--tag-chip-resolved-c) * var(--tag-chip-text-chroma-scale)), 1)
|
||||||
|
var(--tag-chip-resolved-h)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
color: var(--tag-chip-text-color, currentColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag-chip--more {
|
.tag-chip--more {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-color: var(--border-strong);
|
border: 1px dashed var(--border-strong);
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -676,49 +676,45 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
padding: 0.25rem 0 0.1rem;
|
padding: 0.25rem 0 0.1rem;
|
||||||
|
--tag-chip-base-default: var(--surface-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tag-pill {
|
.sidebar-tag-cloud .tag-chip {
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 0.25rem 0.6rem;
|
padding: 0.25rem 0.6rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--surface-soft);
|
transition: transform 0.15s ease, opacity 0.12s ease;
|
||||||
color: var(--fg);
|
|
||||||
transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease,
|
|
||||||
opacity 0.12s ease;
|
|
||||||
box-shadow: 0 1px 2px var(--shadow-faint);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tag-pill:hover {
|
.sidebar-tag-cloud .tag-chip:focus-visible {
|
||||||
transform: none;
|
outline: 2px solid var(--tag-chip-outline-color, var(--accent));
|
||||||
box-shadow: 0 2px 6px var(--shadow-pop);
|
|
||||||
border-color: var(--accent-soft);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-tag-pill:focus-visible {
|
|
||||||
outline: 2px solid var(--accent);
|
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tag-pill.active {
|
.sidebar-tag-cloud .tag-chip.active {
|
||||||
border-color: var(--sidebar-active-pill-border);
|
outline: 2px solid var(--tag-chip-outline-color, var(--accent));
|
||||||
box-shadow: 0 0 0 1.5px var(--sidebar-active-pill-border);
|
outline-offset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tag-pill--untagged {
|
.sidebar-tag-cloud .tag-chip:hover {
|
||||||
border: 1px dashed var(--border);
|
transform: none;
|
||||||
background: var(--surface-subtle);
|
outline: 3px solid var(--tag-chip-outline-color, var(--accent));
|
||||||
|
outline-offset: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tag-cloud .tag-chip--untagged {
|
||||||
|
--tag-chip-base: var(--surface-subtle);
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tag-pill--untagged.active {
|
.sidebar-tag-cloud .tag-chip--untagged.active {
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-tag-cloud--has-active .sidebar-tag-pill:not(.active) {
|
.sidebar-tag-cloud--has-active .tag-chip:not(.active) {
|
||||||
opacity: 0.45;
|
opacity: 0.45;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,71 @@ const rgbToHsl = ({ r, g, b }: RgbColor) => {
|
|||||||
return { h: hue, s: clamp01(saturation), l: clamp01(lightness) };
|
return { h: hue, s: clamp01(saturation), l: clamp01(lightness) };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const srgbChannelToLinear = (value: number) => {
|
||||||
|
const normalized = value / 255;
|
||||||
|
if (normalized <= 0.04045) {
|
||||||
|
return normalized / 12.92;
|
||||||
|
}
|
||||||
|
return ((normalized + 0.055) / 1.055) ** 2.4;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hexToOklch = (input?: string) => {
|
||||||
|
const rgb = hexToRgb(input);
|
||||||
|
if (!rgb) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const r = srgbChannelToLinear(rgb.r);
|
||||||
|
const g = srgbChannelToLinear(rgb.g);
|
||||||
|
const b = srgbChannelToLinear(rgb.b);
|
||||||
|
|
||||||
|
const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
||||||
|
const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
||||||
|
const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
||||||
|
|
||||||
|
const lRoot = Math.cbrt(l);
|
||||||
|
const mRoot = Math.cbrt(m);
|
||||||
|
const sRoot = Math.cbrt(s);
|
||||||
|
|
||||||
|
const L = 0.2104542553 * lRoot + 0.793617785 * mRoot - 0.0040720468 * sRoot;
|
||||||
|
const a = 1.9779984951 * lRoot - 2.428592205 * mRoot + 0.4505937099 * sRoot;
|
||||||
|
const bLab = 0.0259040371 * lRoot + 0.7827717662 * mRoot - 0.808675766 * sRoot;
|
||||||
|
|
||||||
|
const chroma = Math.sqrt(a * a + bLab * bLab);
|
||||||
|
let hue = Math.atan2(bLab, a) * (180 / Math.PI);
|
||||||
|
if (hue < 0) {
|
||||||
|
hue += 360;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
l: clamp01(L),
|
||||||
|
c: chroma,
|
||||||
|
h: hue,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatCssNumber = (value: number) => {
|
||||||
|
if (!Number.isFinite(value)) {
|
||||||
|
return `${value}`;
|
||||||
|
}
|
||||||
|
const rounded = Number(value.toFixed(6));
|
||||||
|
return `${rounded}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildTagStyle = (backgroundHex: string) => {
|
||||||
|
const oklch = hexToOklch(backgroundHex);
|
||||||
|
if (!oklch) {
|
||||||
|
return {
|
||||||
|
'--tag-chip-base': backgroundHex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'--tag-chip-base': backgroundHex,
|
||||||
|
'--tag-chip-base-l': formatCssNumber(oklch.l),
|
||||||
|
'--tag-chip-base-c': formatCssNumber(oklch.c),
|
||||||
|
'--tag-chip-base-h': formatCssNumber(oklch.h),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const hexToRgb = (input?: string): (RgbColor & { hex: string }) | null => {
|
export const hexToRgb = (input?: string): (RgbColor & { hex: string }) | null => {
|
||||||
if (!input) return null;
|
if (!input) return null;
|
||||||
const match = HEX_COLOR_PATTERN.exec(input.trim());
|
const match = HEX_COLOR_PATTERN.exec(input.trim());
|
||||||
@@ -234,11 +299,7 @@ export const getTagColorStyle = (hex) => {
|
|||||||
let bestRatio = baseRatio;
|
let bestRatio = baseRatio;
|
||||||
|
|
||||||
if (bestRatio >= TARGET_RATIO) {
|
if (bestRatio >= TARGET_RATIO) {
|
||||||
return {
|
return buildTagStyle(bestBackground);
|
||||||
backgroundColor: bestBackground,
|
|
||||||
borderColor: bestBackground,
|
|
||||||
color: bestText,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let compliantBackground = null;
|
let compliantBackground = null;
|
||||||
@@ -277,18 +338,10 @@ export const getTagColorStyle = (hex) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (compliantBackground) {
|
if (compliantBackground) {
|
||||||
return {
|
return buildTagStyle(compliantBackground);
|
||||||
backgroundColor: compliantBackground,
|
|
||||||
borderColor: compliantBackground,
|
|
||||||
color: compliantText,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return buildTagStyle(bestBackground);
|
||||||
backgroundColor: bestBackground,
|
|
||||||
borderColor: bestBackground,
|
|
||||||
color: bestText,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const generateRandomTagColor = () => {
|
export const generateRandomTagColor = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user