fix(frontend): let a tag be created/edited without a colour

A native <input type="color"> always holds a value, so the form always
sent the input's default colour and a tag could never be colourless. Add
a "Color" checkbox gating the swatch: off by default on the new-tag form
(so tags are colourless unless opted in) and initialised from the tag on
the edit form, which can now clear a colour. Sends color: null when off.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:31:59 +03:00
parent 21c7aa31ea
commit da867406e2
2 changed files with 68 additions and 6 deletions
+34 -3
View File
@@ -15,6 +15,9 @@
let name = $state(''); let name = $state('');
let notes = $state(''); let notes = $state('');
// A native <input type="color"> always holds a value, so a separate flag tracks
// whether the tag has a colour at all — letting it be cleared back to none.
let hasColor = $state(false);
let color = $state('#444455'); let color = $state('#444455');
let categoryId = $state(''); let categoryId = $state('');
let isPublic = $state(false); let isPublic = $state(false);
@@ -43,6 +46,7 @@
name = t.name ?? ''; name = t.name ?? '';
notes = t.notes ?? ''; notes = t.notes ?? '';
hasColor = !!t.color;
color = t.color ? `#${t.color}` : '#444455'; color = t.color ? `#${t.color}` : '#444455';
categoryId = t.category_id ?? ''; categoryId = t.category_id ?? '';
isPublic = t.is_public ?? false; isPublic = t.is_public ?? false;
@@ -61,7 +65,7 @@
await api.patch(`/tags/${tagId}`, { await api.patch(`/tags/${tagId}`, {
name: name.trim(), name: name.trim(),
notes: notes.trim() || null, notes: notes.trim() || null,
color: color.slice(1), color: hasColor ? color.slice(1) : null,
category_id: categoryId || null, category_id: categoryId || null,
is_public: isPublic is_public: isPublic
}); });
@@ -139,8 +143,18 @@
/> />
</div> </div>
<div class="field color-field"> <div class="field color-field">
<label class="label" for="color">Color</label> <label class="label color-label">
<input id="color" class="color-input" type="color" bind:value={color} /> <input type="checkbox" class="color-check" bind:checked={hasColor} />
Color
</label>
<input
id="color"
class="color-input"
type="color"
bind:value={color}
disabled={!hasColor}
aria-label="Tag color"
/>
</div> </div>
</div> </div>
@@ -339,6 +353,18 @@
border-color: var(--color-accent); border-color: var(--color-accent);
} }
.color-label {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
}
.color-check {
cursor: pointer;
accent-color: var(--color-accent);
}
.color-input { .color-input {
width: 50px; width: 50px;
height: 36px; height: 36px;
@@ -349,6 +375,11 @@
cursor: pointer; cursor: pointer;
} }
.color-input:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.textarea { .textarea {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
+34 -3
View File
@@ -6,6 +6,10 @@
let name = $state(''); let name = $state('');
let notes = $state(''); let notes = $state('');
// A native <input type="color"> always holds a value, so a separate flag tracks
// whether the tag should have a color at all. Off by default → no colour unless
// the user opts in (otherwise the tag falls back to its category / the default).
let hasColor = $state(false);
let color = $state('#444455'); let color = $state('#444455');
let categoryId = $state(''); let categoryId = $state('');
let isPublic = $state(false); let isPublic = $state(false);
@@ -27,7 +31,7 @@
await api.post('/tags', { await api.post('/tags', {
name: name.trim(), name: name.trim(),
notes: notes.trim() || null, notes: notes.trim() || null,
color: color.slice(1), // strip # color: hasColor ? color.slice(1) : null, // strip #; null = no colour
category_id: categoryId || null, category_id: categoryId || null,
is_public: isPublic is_public: isPublic
}); });
@@ -86,8 +90,18 @@
/> />
</div> </div>
<div class="field color-field"> <div class="field color-field">
<label class="label" for="color">Color</label> <label class="label color-label">
<input id="color" class="color-input" type="color" bind:value={color} /> <input type="checkbox" class="color-check" bind:checked={hasColor} />
Color
</label>
<input
id="color"
class="color-input"
type="color"
bind:value={color}
disabled={!hasColor}
aria-label="Tag color"
/>
</div> </div>
</div> </div>
@@ -235,6 +249,18 @@
border-color: var(--color-accent); border-color: var(--color-accent);
} }
.color-label {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
}
.color-check {
cursor: pointer;
accent-color: var(--color-accent);
}
.color-input { .color-input {
width: 50px; width: 50px;
height: 36px; height: 36px;
@@ -245,6 +271,11 @@
cursor: pointer; cursor: pointer;
} }
.color-input:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.textarea { .textarea {
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;