feat(todo): color picker popup on column dot instead of inline swatches

In edit mode, the color dot stays in its normal position but gets a
purple ring to indicate it's clickable. Clicking opens a popup with:
- 16 preset color swatches in a 4x4 grid
- Native color picker for custom colors
- Click outside to dismiss

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-31 13:49:16 +02:00
parent 1926c6b1f2
commit dc4ba0a39c

View file

@ -30,67 +30,106 @@
let editMode = $derived(editModeCtx?.active ?? false);
let editable = $derived(editMode && !!onRename);
const COLORS = [
let showColorPicker = $state(false);
const PRESET_COLORS = [
'#EF4444',
'#F59E0B',
'#22C55E',
'#3B82F6',
'#8B5CF6',
'#EC4899',
'#14B8A6',
'#F97316',
'#F59E0B',
'#EAB308',
'#22C55E',
'#14B8A6',
'#06B6D4',
'#3B82F6',
'#6366F1',
'#8B5CF6',
'#A855F7',
'#EC4899',
'#F43F5E',
'#78716C',
'#6B7280',
'#334155',
];
</script>
<!-- svelte-ignore a11y_no_static_element_interactions -->
{#if showColorPicker}
<div class="picker-backdrop" onclick={() => (showColorPicker = false)}></div>
{/if}
<div class="column-header" class:editing={editable}>
{#if editable}
<!-- Edit mode: color dots + name input + actions -->
<div class="edit-header">
<div class="color-row">
{#each COLORS as c}
<button
class="color-pick"
class:active={color === c}
style="background-color: {c}"
onclick={() => onColorChange?.(c)}
></button>
{/each}
</div>
<div class="edit-row">
<input
class="name-input"
type="text"
value={name}
oninput={(e) => onRename?.(e.currentTarget.value)}
/>
<div class="edit-actions">
<button
class="act-btn"
onclick={() => onMove?.(-1)}
disabled={columnIndex === 0}
title="Nach links"
>
<ArrowLeft size={12} />
</button>
<button
class="act-btn"
onclick={() => onMove?.(1)}
disabled={columnIndex >= totalColumns - 1}
title="Nach rechts"
>
<ArrowRight size={12} />
</button>
<button
class="act-btn del-btn"
onclick={() => onDelete?.()}
disabled={totalColumns <= 1}
title="Spalte löschen"
>
<Trash size={12} />
</button>
</div>
<!-- Edit mode: same layout, color dot is clickable, name is input -->
<div class="header-left">
<div class="color-dot-wrapper">
<button
class="color-dot editable"
style="background-color: {color}"
onclick={() => (showColorPicker = !showColorPicker)}
title="Farbe ändern"
></button>
{#if showColorPicker}
<div class="color-picker" onclick={(e) => e.stopPropagation()}>
<div class="picker-grid">
{#each PRESET_COLORS as c}
<button
class="picker-swatch"
class:active={color === c}
style="background-color: {c}"
onclick={() => {
onColorChange?.(c);
showColorPicker = false;
}}
></button>
{/each}
</div>
<label class="custom-color-row">
<span class="custom-label">Eigene:</span>
<input
type="color"
value={color}
oninput={(e) => onColorChange?.(e.currentTarget.value)}
class="custom-color-input"
/>
</label>
</div>
{/if}
</div>
<input
class="name-input"
type="text"
value={name}
oninput={(e) => onRename?.(e.currentTarget.value)}
/>
</div>
<div class="edit-actions">
<button
class="act-btn"
onclick={() => onMove?.(-1)}
disabled={columnIndex === 0}
title="Nach links"
>
<ArrowLeft size={12} />
</button>
<button
class="act-btn"
onclick={() => onMove?.(1)}
disabled={columnIndex >= totalColumns - 1}
title="Nach rechts"
>
<ArrowRight size={12} />
</button>
<button
class="act-btn del-btn"
onclick={() => onDelete?.()}
disabled={totalColumns <= 1}
title="Spalte löschen"
>
<Trash size={12} />
</button>
</div>
{:else}
<!-- Normal mode -->
@ -110,15 +149,12 @@
padding: 0.75rem 1rem;
}
.column-header.editing {
padding: 0.5rem 0.75rem;
}
.header-left {
display: flex;
align-items: center;
gap: 0.5rem;
min-width: 0;
flex: 1;
}
.color-dot {
@ -157,45 +193,29 @@
}
/* ── Edit mode ────────────────────────────────────────── */
.edit-header {
display: flex;
flex-direction: column;
gap: 0.375rem;
width: 100%;
.color-dot-wrapper {
position: relative;
flex-shrink: 0;
}
.color-row {
display: flex;
gap: 0.2rem;
}
.color-pick {
width: 12px;
height: 12px;
border-radius: 50%;
border: 2px solid transparent;
.color-dot.editable {
width: 0.875rem;
height: 0.875rem;
cursor: pointer;
transition: all 0.15s;
border: none;
padding: 0;
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.5);
transition: all 0.15s;
}
.color-pick:hover {
transform: scale(1.25);
}
.color-pick.active {
border-color: white;
box-shadow: 0 0 0 1.5px currentColor;
.color-dot.editable:hover {
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.7);
transform: scale(1.15);
}
.edit-row {
display: flex;
align-items: center;
gap: 0.375rem;
}
.name-input {
flex: 1;
font-size: 0.8125rem;
font-size: 0.875rem;
font-weight: 600;
color: #374151;
background: transparent;
@ -216,6 +236,7 @@
display: flex;
gap: 0.125rem;
flex-shrink: 0;
margin-left: 0.375rem;
}
.act-btn {
@ -244,4 +265,92 @@
color: #ef4444 !important;
background: rgba(239, 68, 68, 0.1) !important;
}
/* ── Color Picker Popup ──────────────────────────────── */
.picker-backdrop {
position: fixed;
inset: 0;
z-index: 50;
}
.color-picker {
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
z-index: 51;
background: rgba(255, 255, 255, 0.97);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 0.75rem;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.15);
padding: 0.625rem;
min-width: 170px;
}
:global(.dark) .color-picker {
background: rgba(30, 30, 30, 0.97);
border-color: rgba(255, 255, 255, 0.12);
}
.picker-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 0.375rem;
margin-bottom: 0.5rem;
}
.picker-swatch {
width: 28px;
height: 28px;
border-radius: 50%;
border: 2px solid transparent;
cursor: pointer;
transition: all 0.15s;
padding: 0;
}
.picker-swatch:hover {
transform: scale(1.15);
}
.picker-swatch.active {
border-color: white;
box-shadow: 0 0 0 2px currentColor;
}
.custom-color-row {
display: flex;
align-items: center;
gap: 0.5rem;
padding-top: 0.375rem;
border-top: 1px solid rgba(0, 0, 0, 0.06);
}
:global(.dark) .custom-color-row {
border-top-color: rgba(255, 255, 255, 0.08);
}
.custom-label {
font-size: 0.6875rem;
font-weight: 500;
color: #6b7280;
}
:global(.dark) .custom-label {
color: #9ca3af;
}
.custom-color-input {
width: 28px;
height: 28px;
padding: 0;
border: none;
border-radius: 50%;
cursor: pointer;
background: transparent;
}
.custom-color-input::-webkit-color-swatch-wrapper {
padding: 0;
}
.custom-color-input::-webkit-color-swatch {
border: 2px solid rgba(0, 0, 0, 0.1);
border-radius: 50%;
}
</style>