refactor: extract reusable UI components and composables from TextSettings

Major refactoring to improve code quality and reduce duplication:

TextSettings.vue: 1127 → 269 lines (-76%)

New composables:
- useCssUpdater.js: generic CSS update/remove functions
- useCssSync.js: CSS parsing to form fields

New UI components:
- UnitToggle.vue: reusable unit selector buttons
- InputWithUnit.vue: number input with unit toggle
- MarginEditor.vue: simple/detailed margin editor with sync

Benefits:
- Reusable components for other settings panels
- Centralized CSS manipulation logic
- Better separation of concerns
- Easier to test and maintain

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
isUnknown 2025-12-05 16:30:44 +01:00
parent 94112ab1a8
commit c4d2015a69
6 changed files with 647 additions and 1006 deletions

View file

@ -0,0 +1,70 @@
<template>
<div class="input-with-unit">
<input
v-if="showRange"
type="range"
:value="modelValue.value"
:min="min"
:max="max"
:step="step"
@input="updateValue(Number($event.target.value))"
/>
<input
type="number"
:value="modelValue.value"
:min="min"
:max="max"
:step="step"
class="size-input"
@input="updateValue(Number($event.target.value))"
/>
<UnitToggle
:modelValue="modelValue.unit"
:units="units"
@update:modelValue="updateUnit"
/>
<slot name="after" />
</div>
</template>
<script setup>
import UnitToggle from './UnitToggle.vue';
const props = defineProps({
modelValue: {
type: Object,
required: true,
validator: (v) => 'value' in v && 'unit' in v
},
units: {
type: Array,
default: () => ['mm', 'px']
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
step: {
type: Number,
default: 1
},
showRange: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:modelValue']);
const updateValue = (value) => {
emit('update:modelValue', { ...props.modelValue, value });
};
const updateUnit = (unit) => {
emit('update:modelValue', { ...props.modelValue, unit });
};
</script>

View file

@ -0,0 +1,162 @@
<template>
<div class="margin-editor">
<div class="field">
<label :for="id">{{ label }}</label>
<div class="input-with-unit">
<input
:id="id"
type="number"
:value="simple.value"
min="0"
@input="updateSimpleValue(Number($event.target.value))"
/>
<UnitToggle
:modelValue="simple.unit"
:units="units"
@update:modelValue="updateSimpleUnit"
/>
<button
type="button"
class="collapse-toggle"
:class="{ expanded }"
@click="toggleExpanded"
title="Réglages détaillés"
>
</button>
</div>
</div>
<div v-if="expanded" class="subsection collapsed-section">
<div v-for="side in sides" :key="side.key" class="field">
<label :for="`${id}-${side.key}`">{{ side.label }}</label>
<div class="input-with-unit">
<input
:id="`${id}-${side.key}`"
type="number"
:value="detailed[side.key].value"
min="0"
@input="updateDetailedValue(side.key, Number($event.target.value))"
/>
<UnitToggle
:modelValue="detailed[side.key].unit"
:units="units"
@update:modelValue="(unit) => updateDetailedUnit(side.key, unit)"
/>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import UnitToggle from './UnitToggle.vue';
const props = defineProps({
id: {
type: String,
required: true
},
label: {
type: String,
required: true
},
simple: {
type: Object,
required: true,
validator: (v) => 'value' in v && 'unit' in v
},
detailed: {
type: Object,
required: true,
validator: (v) => ['top', 'right', 'bottom', 'left'].every(k => k in v)
},
units: {
type: Array,
default: () => ['mm', 'px']
}
});
const emit = defineEmits(['update:simple', 'update:detailed', 'change']);
const expanded = ref(false);
let isSyncing = false;
const sides = [
{ key: 'top', label: 'Haut' },
{ key: 'bottom', label: 'Bas' },
{ key: 'left', label: 'Gauche' },
{ key: 'right', label: 'Droite' }
];
const toggleExpanded = () => {
expanded.value = !expanded.value;
if (expanded.value) {
// Sync detailed values from simple when expanding
syncDetailedFromSimple();
}
};
const syncDetailedFromSimple = () => {
isSyncing = true;
const newDetailed = {};
for (const side of sides) {
newDetailed[side.key] = { value: props.simple.value, unit: props.simple.unit };
}
emit('update:detailed', newDetailed);
isSyncing = false;
};
const updateSimpleValue = (value) => {
const newSimple = { ...props.simple, value };
emit('update:simple', newSimple);
// Sync all detailed values
isSyncing = true;
const newDetailed = {};
for (const side of sides) {
newDetailed[side.key] = { value, unit: props.simple.unit };
}
emit('update:detailed', newDetailed);
isSyncing = false;
emit('change', { type: 'simple', simple: newSimple });
};
const updateSimpleUnit = (unit) => {
const newSimple = { ...props.simple, unit };
emit('update:simple', newSimple);
// Sync all detailed units
isSyncing = true;
const newDetailed = {};
for (const side of sides) {
newDetailed[side.key] = { ...props.detailed[side.key], unit };
}
emit('update:detailed', newDetailed);
isSyncing = false;
emit('change', { type: 'simple', simple: newSimple });
};
const updateDetailedValue = (key, value) => {
if (isSyncing) return;
const newDetailed = { ...props.detailed, [key]: { ...props.detailed[key], value } };
emit('update:detailed', newDetailed);
if (expanded.value) {
emit('change', { type: 'detailed', detailed: newDetailed });
}
};
const updateDetailedUnit = (key, unit) => {
if (isSyncing) return;
const newDetailed = { ...props.detailed, [key]: { ...props.detailed[key], unit } };
emit('update:detailed', newDetailed);
if (expanded.value) {
emit('change', { type: 'detailed', detailed: newDetailed });
}
};
defineExpose({ expanded });
</script>

View file

@ -0,0 +1,28 @@
<template>
<div class="unit-toggle">
<button
v-for="unit in units"
:key="unit"
type="button"
:class="{ active: modelValue === unit }"
@click="$emit('update:modelValue', unit)"
>
{{ unit }}
</button>
</div>
</template>
<script setup>
defineProps({
modelValue: {
type: String,
required: true
},
units: {
type: Array,
default: () => ['mm', 'px']
}
});
defineEmits(['update:modelValue']);
</script>