0.5.1: replace properties checkboxes with icon toggles

All checkboxes and radio buttons in the Properties panel are now
icon-toggle buttons:

- Polygon "Closed path" → PhPolygon button (fill weight when closed)
- Fill/Stroke transparent → PhEye / PhEyeSlash toggle next to the swatch
- Mask use → PhMaskHappy toggle
- Mask mode → segmented control with PhPlus / PhMinus (negative is red)

Shared .icon-toggle style: 26×26, panel background, accent when active,
optional .danger variant. Tests updated to click buttons instead of
checkboxes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-20 12:18:47 +02:00
1 parent ddea7d348f
commit cbc309bc8c
4 files changed
+149 -92

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "animatr",
"private": true,
"version": "0.5.0",
"version": "0.5.1",
"type": "module",
"scripts": {
"dev": "vite",
+146 -89
View File
@@ -1,6 +1,18 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { PhCaretDown, PhCaretRight, PhDiamond, PhTrash, PhX } from '@phosphor-icons/vue'
import {
PhCaretDown,
PhCaretRight,
PhDiamond,
PhEye,
PhEyeSlash,
PhMaskHappy,
PhMinus,
PhPolygon,
PhPlus,
PhTrash,
PhX,
} from '@phosphor-icons/vue'
import { useSceneStore } from '@/stores/scene'
import { sampleLayer } from '@/lib/interpolate'
import type { AnimatableProps } from '@/types/scene'
@@ -405,10 +417,18 @@ function removeKeyframeHere() {
<span>{{ t('properties.sections.polygon') }}</span>
</button>
<div v-if="sectionOpen.polygon" class="group-body">
<label class="inline-toggle">
<input type="checkbox" v-model="polygonClosed" />
<span>{{ t('properties.polygonClosed') }}</span>
</label>
<div class="toggle-row">
<span class="toggle-label">{{ t('properties.polygonClosed') }}</span>
<button
type="button"
class="icon-toggle"
:class="{ active: polygonClosed }"
:title="t('properties.polygonClosed')"
@click="polygonClosed = !polygonClosed"
>
<PhPolygon :size="14" :weight="polygonClosed ? 'fill' : 'regular'" />
</button>
</div>
<label>
{{ t('properties.polygonPoints') }}
<input type="number" min="3" max="64" v-model.number="polygonPointCount" />
@@ -434,10 +454,15 @@ function removeKeyframeHere() {
@input="fillColor = ($event.target as HTMLInputElement).value"
/>
<span class="color-hex">{{ fillTransparent ? '—' : fillColor.toUpperCase() }}</span>
<label class="alpha-toggle">
<input type="checkbox" v-model="fillTransparent" />
<span>{{ t('properties.transparent') }}</span>
</label>
<button
type="button"
class="icon-toggle alpha-toggle-btn"
:class="{ active: fillTransparent }"
:title="t('properties.transparent')"
@click="fillTransparent = !fillTransparent"
>
<component :is="fillTransparent ? PhEyeSlash : PhEye" :size="13" />
</button>
</div>
</label>
<label v-if="canStroke" class="color-label">
@@ -451,10 +476,15 @@ function removeKeyframeHere() {
@input="strokeColor = ($event.target as HTMLInputElement).value"
/>
<span class="color-hex">{{ strokeTransparent ? '—' : strokeColor.toUpperCase() }}</span>
<label class="alpha-toggle">
<input type="checkbox" v-model="strokeTransparent" />
<span>{{ t('properties.transparent') }}</span>
</label>
<button
type="button"
class="icon-toggle alpha-toggle-btn"
:class="{ active: strokeTransparent }"
:title="t('properties.transparent')"
@click="strokeTransparent = !strokeTransparent"
>
<component :is="strokeTransparent ? PhEyeSlash : PhEye" :size="13" />
</button>
</div>
</label>
<label v-if="canStroke" class="slider-label">
@@ -545,31 +575,40 @@ function removeKeyframeHere() {
<span>{{ t('properties.mask') }}</span>
</button>
<div v-if="sectionOpen.mask" class="group-body">
<label class="mask-toggle">
<input
type="checkbox"
:checked="!!selected.isMask"
@change="store.setLayerMask(selected.id, ($event.target as HTMLInputElement).checked)"
/>
<span>{{ t('properties.maskUse') }}</span>
</label>
<div v-if="selected.isMask" class="mask-mode-row">
<label class="mask-radio">
<input
type="radio"
:checked="(selected.maskMode ?? 'positive') === 'positive'"
@change="store.setLayerMaskMode(selected.id, 'positive')"
/>
<span>{{ t('properties.maskPositive') }}</span>
</label>
<label class="mask-radio">
<input
type="radio"
:checked="selected.maskMode === 'negative'"
@change="store.setLayerMaskMode(selected.id, 'negative')"
/>
<span>{{ t('properties.maskNegative') }}</span>
</label>
<div class="toggle-row">
<span class="toggle-label">{{ t('properties.maskUse') }}</span>
<button
type="button"
class="icon-toggle"
:class="{ active: !!selected.isMask }"
:title="t('properties.maskUse')"
@click="store.setLayerMask(selected.id, !selected.isMask)"
>
<PhMaskHappy :size="14" />
</button>
</div>
<div v-if="selected.isMask" class="toggle-row">
<span class="toggle-label">{{ t('properties.maskMode') }}</span>
<div class="segmented">
<button
type="button"
class="icon-toggle"
:class="{ active: (selected.maskMode ?? 'positive') === 'positive' }"
:title="t('properties.maskPositive')"
@click="store.setLayerMaskMode(selected.id, 'positive')"
>
<PhPlus :size="13" weight="bold" />
</button>
<button
type="button"
class="icon-toggle"
:class="{ active: selected.maskMode === 'negative', danger: selected.maskMode === 'negative' }"
:title="t('properties.maskNegative')"
@click="store.setLayerMaskMode(selected.id, 'negative')"
>
<PhMinus :size="13" weight="bold" />
</button>
</div>
</div>
<p v-if="selected.isMask && maskInGroup" class="status-active">
{{ t('properties.maskActive') }}
@@ -681,45 +720,77 @@ input {
padding: 0 2px 10px;
}
.inline-toggle {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text);
text-transform: none;
letter-spacing: 0;
cursor: pointer;
flex: 0 0 auto;
}
.inline-toggle input {
width: auto;
margin: 0;
}
.mask-toggle,
.mask-radio {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text);
text-transform: none;
letter-spacing: 0;
cursor: pointer;
flex: 0 0 auto;
}
.mask-mode-row {
.toggle-row {
display: flex;
gap: 16px;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.mask-toggle input,
.mask-radio input {
width: auto;
margin: 0;
.toggle-label {
font-size: 12px;
color: var(--text);
text-transform: none;
letter-spacing: 0;
flex: 1 1 auto;
}
.segmented {
display: inline-flex;
gap: 2px;
background: var(--panel-alt);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 2px;
}
.icon-toggle {
width: 26px;
height: 26px;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--panel-alt);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-dim);
cursor: pointer;
transition: background 140ms var(--ease), border-color 140ms var(--ease),
color 140ms var(--ease);
flex-shrink: 0;
}
.icon-toggle:hover {
border-color: var(--border-strong);
color: var(--text);
}
.icon-toggle.active {
background: var(--accent-soft);
border-color: var(--accent);
color: var(--accent);
}
.icon-toggle.active.danger {
background: color-mix(in srgb, #ff6b6b 16%, transparent);
border-color: #ff6b6b;
color: #ff6b6b;
}
.segmented .icon-toggle {
border: none;
background: transparent;
}
.segmented .icon-toggle.active {
background: var(--accent-soft);
color: var(--accent);
}
.segmented .icon-toggle.active.danger {
background: color-mix(in srgb, #ff6b6b 16%, transparent);
color: #ff6b6b;
}
.status-active,
@@ -843,22 +914,8 @@ input {
letter-spacing: 0.03em;
}
.alpha-toggle {
display: inline-flex;
align-items: center;
gap: 4px;
.alpha-toggle-btn {
margin-left: auto;
font-size: 10px;
color: var(--text-dim);
text-transform: uppercase;
letter-spacing: 0.05em;
cursor: pointer;
flex: 0 0 auto;
}
.alpha-toggle input {
width: auto;
margin: 0;
}
.color-swatch:disabled {
+1 -1
View File
@@ -2399,7 +2399,7 @@ export function registerAllTests(): void {
suite('properties — fill/stroke transparent toggle', () => {
function findAlphaToggles(host: HTMLElement) {
return Array.from(
host.querySelectorAll<HTMLInputElement>('.alpha-toggle input[type="checkbox"]'),
host.querySelectorAll<HTMLButtonElement>('button.alpha-toggle-btn'),
)
}
+1 -1
View File
@@ -1,2 +1,2 @@
export const APP_VERSION = '0.5.0'
export const APP_VERSION = '0.5.1'
export const APP_NAME = 'RUWIO'