diff --git a/frontend/src/detail/DetailPanel.jsx b/frontend/src/detail/DetailPanel.jsx index 4500be1..3e257b4 100644 --- a/frontend/src/detail/DetailPanel.jsx +++ b/frontend/src/detail/DetailPanel.jsx @@ -105,11 +105,18 @@ const TagSection = ({ tags.map((tag) => { const key = tag.id ?? tag.label; const style = getTagColorStyle(tag.color); + const removable = Boolean(onRemove); + const className = removable ? 'badge tag-chip tag-chip--removable' : 'badge tag-chip'; return ( - - {tag.label}{' '} - {onRemove ? ( - ) : null} diff --git a/frontend/src/skeuomorphic_ws.css b/frontend/src/skeuomorphic_ws.css index b2b9921..f2cab9f 100644 --- a/frontend/src/skeuomorphic_ws.css +++ b/frontend/src/skeuomorphic_ws.css @@ -98,39 +98,28 @@ transition: transform 0.28s ease; } -.skeuo-tag { - display: inline-flex; - align-items: center; - justify-content: flex-start; - padding: 0.28rem 0.7rem; - border-radius: 1rem; - background: linear-gradient(180deg, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0) 70%), var(--surface); - color: var(--fg); - font-size: 1rem; - font-weight: 600; - box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.18); - text-align: left; - white-space: nowrap; - user-select: none; -} -.skeuo-item__tags .skeuo-tag { - font-size: 0.75rem; - padding: 0.18rem 0.55rem; +.tag-chip--draggable { + user-select: none; pointer-events: auto; cursor: grab; - transition: transform 0.16s ease, opacity 0.2s ease; + transition: transform 0.16s ease, opacity 0.2s ease, box-shadow 0.2s ease; + box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.18); } -.skeuo-tag.is-drag-hidden { +.tag-chip--draggable:active { + cursor: grabbing; +} + +.tag-chip--draggable.is-drag-hidden { opacity: 0.4; pointer-events: none; } -.skeuo-tag span { - display: block; - pointer-events: none; - user-select: none; +.skeuo-item__tags .tag-chip { + font-size: 0.85rem; + padding: 0.18rem 0.55rem; + gap: 0.3rem; } @@ -186,8 +175,8 @@ height: 100%; } -.skeuo-item__tags .skeuo-tag.is-tear-pending { - opacity: 0.4; +.skeuo-item__tags .tag-chip--tear-pending { + opacity: 0.35; } body.skeuo-cursor-remove, diff --git a/frontend/src/skeuomorphic_ws.jsx b/frontend/src/skeuomorphic_ws.jsx index a5ed2c4..eca756c 100644 --- a/frontend/src/skeuomorphic_ws.jsx +++ b/frontend/src/skeuomorphic_ws.jsx @@ -10,7 +10,7 @@ import { resolveDocumentAssetUrl, createAssetView } from './asset_manager'; import { useAssetNavigator } from './hooks/useAssetNavigator'; import { ArrowLeftIcon, ArrowRightIcon } from './ui/icons'; import PreviewZoomOverlay from './detail/PreviewZoomOverlay'; -import { getReadableTextColor } from './utils/colors'; +import { getTagColorStyle } from './utils/colors'; import './skeuomorphic_ws.css'; const ITEM_WIDTH = 220; @@ -598,23 +598,6 @@ const clampCardDimensions = (width, height) => { const formatTransform = (x, y, rotation = 0, scale = 1) => `translate3d(${x}px, ${y}px, 0) rotate(${rotation}deg) scale(${scale})`; -const normalizeColor = (input) => { - if (!input) return null; - const value = String(input).trim(); - if (!value) return null; - if (/^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value)) { - return value.length === 4 - ? `#${value[1]}${value[1]}${value[2]}${value[2]}${value[3]}${value[3]}` - : value; - } - if (/^[0-9a-fA-F]{6}$/.test(value)) { - return `#${value}`; - } - return null; -}; - -const getContrastingTextColor = (hex) => getReadableTextColor(hex, { light: '#1f2125' }); - const SkeuomorphicWorkspace = ({ documents = [], searchResults = null, @@ -1758,21 +1741,18 @@ const SkeuomorphicWorkspace = ({ ) { return null; } - const colorValue = normalizeColor(tag.color); - const foreground = getContrastingTextColor(colorValue || '#1b1f24'); + const colorStyle = getTagColorStyle(tag.color); const pendingRemoval = pendingRemovalTag && pendingRemovalTag.docId === doc.id && pendingRemovalTag.tagId === tag.id; - const tagClasses = ['skeuo-tag']; - if (pendingRemoval) tagClasses.push('is-tear-pending'); - const tagStyle = - colorValue ? { backgroundColor: colorValue, color: foreground } : undefined; + const tagClasses = ['badge', 'tag-chip', 'tag-chip--draggable']; + if (pendingRemoval) tagClasses.push('tag-chip--tear-pending'); return ( -
handleDocTagPointerDown(event, doc, tag)} @@ -1780,8 +1760,8 @@ const SkeuomorphicWorkspace = ({ onDrag={handleDocTagDrag} onDragEnd={(event) => handleDocTagDragEnd(event)} > - {tag.label || 'Tag'} -
+ {tag.label || 'Tag'} +
); })} diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 31420f9..b205cab 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1192,7 +1192,7 @@ button.danger:hover:not([disabled]) { border-radius: 999px; padding: 0.25rem 0.6rem; font-size: 0.75rem; - font-weight: 500; + font-weight: 600; line-height: 1; cursor: pointer; background: var(--surface-soft); @@ -1687,6 +1687,7 @@ button.danger:hover:not([disabled]) { gap: 0.25rem; border-radius: 1rem; padding: 0.2rem 0.4rem; + font-weight: 600; } .tag-chip--more { @@ -1695,6 +1696,39 @@ button.danger:hover:not([disabled]) { color: var(--muted); } +.tag-chip--removable { + gap: 0.35rem; +} + +.tag-chip__label { + line-height: 1; +} + +.tag-chip__remove { + background: none; + border: none; + color: inherit; + padding: 0; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 0.9em; + line-height: 1; + cursor: pointer; + opacity: 0.8; +} + +.tag-chip__remove:hover, +.tag-chip__remove:focus-visible { + opacity: 1; +} + +.tag-chip__remove:focus-visible { + outline: 2px solid currentColor; + outline-offset: 2px; + border-radius: 50%; +} + .empty-state { border: 1px dashed var(--border); border-radius: 0; @@ -2093,32 +2127,6 @@ button.danger:hover:not([disabled]) { line-height: 1.45; } -.tag-pill { - display: inline-flex; - align-items: center; - gap: 0.18rem; - background: var(--surface-subtle); - color: var(--fg); - padding: 0.2rem 0.45rem; - border-radius: 2px; - font-size: 0.78rem; - border: 1px solid var(--border); -} - -.tag-pill button { - background: none; - border: none; - color: inherit; - padding: 0; - cursor: pointer; - font-size: 0.85rem; - opacity: 0.8; -} - -.tag-pill button:hover { - opacity: 1; -} - input, textarea, select { diff --git a/frontend/src/tags/TagsPanel.jsx b/frontend/src/tags/TagsPanel.jsx index 867cfb6..0839e5b 100644 --- a/frontend/src/tags/TagsPanel.jsx +++ b/frontend/src/tags/TagsPanel.jsx @@ -1,5 +1,9 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { getTagColorStyle, HEX_COLOR_PATTERN } from '../utils/colors'; +import { + getTagColorStyle, + HEX_COLOR_PATTERN, + generateRandomTagColor, +} from '../utils/colors'; function TagsPanel({ tags, @@ -170,6 +174,14 @@ function TagsPanel({ disabled={creating} aria-label="Tag color (optional)" /> + {createColor && ( + {draftColor && ( +