hlassiege/darylpublic Fork
⑂ 0606563
Commits
⬇ Clone ▾
git clone https://git.rickub.com/hlassiege/daryl.git
git clone ssh://git@rickub.com/hlassiege/daryl.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

refactor(web): extract the design system swatch into a shared component

hlassiege committed 2026-10-13T14:02:18+02:00 Browse files
0606563 parent: 46136b1
added web/src/components/ThemeSwatch.vue +18 -0
new file mode 100644
@@ -0,0 +1,18 @@
1+<script setup lang="ts">
2+/** L'aperçu d'un design system : « Aa » sur le fond des cartes, dans la couleur du texte, et une pastille d'accent. */
3+defineProps<{ theme: Record<string, string | number> }>();
4+</script>
5+
6+<template>
7+ <span class="swatch" :style="{
8+ background: String(theme.card), borderColor: String(theme.line), color: String(theme.text),
9+ borderRadius: theme.radius === undefined ? undefined : `${Math.min(Number(theme.radius), 12)}px`,
10+ }">
11+ Aa<i :style="{ background: String(theme.accent) }" />
12+ </span>
13+</template>
14+
15+<style scoped>
16+.swatch { position: relative; display: grid; place-items: center; flex: none; width: 44px; height: 30px; border: 1px solid; font-size: 13px; font-weight: 600; }
17+.swatch i { position: absolute; right: 4px; bottom: 4px; width: 8px; height: 8px; border-radius: 50%; }
18+</style>
new file mode 100644
@@ -0,0 +1,18 @@
1+<script setup lang="ts">
2+/** L'aperçu d'un design system : « Aa » sur le fond des cartes, dans la couleur du texte, et une pastille d'accent. */
3+defineProps<{ theme: Record<string, string | number> }>();
4+</script>
5+
6+<template>
7+ <span class="swatch" :style="{
8+ background: String(theme.card), borderColor: String(theme.line), color: String(theme.text),
9+ borderRadius: theme.radius === undefined ? undefined : `${Math.min(Number(theme.radius), 12)}px`,
10+ }">
11+ Aa<i :style="{ background: String(theme.accent) }" />
12+ </span>
13+</template>
14+
15+<style scoped>
16+.swatch { position: relative; display: grid; place-items: center; flex: none; width: 44px; height: 30px; border: 1px solid; font-size: 13px; font-weight: 600; }
17+.swatch i { position: absolute; right: 4px; bottom: 4px; width: 8px; height: 8px; border-radius: 50%; }
18+</style>
modified web/src/pages/library/KitDesign.vue +4 -13
@@ -8,6 +8,7 @@ import { computed, onMounted, ref } from "vue";
88 import { useI18n } from "vue-i18n";
99 import { api, putJson } from "../../lib/api";
1010 import { themesFromImage } from "../../lib/palette";
11+import ThemeSwatch from "../../components/ThemeSwatch.vue";
1112 import { toast } from "../../lib/toast";
1213
1314 type Design = Record<string, string | number>;
@@ -35,10 +36,6 @@ const changed = (key: string) => view.value && view.value.design[key] !== view.v
3536
3637 // la charte de base en tête des ambiances ; une ambiance est active si tous ses réglages le sont
3738 const presets = computed<Design[]>(() => view.value ? [{ ...view.value.defaults, id: "base" }, ...view.value.presets] : []);
38-const swatch = (p: Design) => ({
39- background: String(p.card), borderColor: String(p.line), color: String(p.text),
40- borderRadius: p.radius === undefined ? undefined : `${Math.min(Number(p.radius), 12)}px`,
41-});
4239 const active = (values: Design) => !!view.value && Object.entries(values)
4340 .every(([k, v]) => k === "id" || view.value!.design[k] === v);
4441
@@ -67,9 +64,7 @@ async function readPhoto(e: Event) {
6764 </div>
6865 <div class="themes">
6966 <button v-for="p in presets" :key="p.id" class="theme" :class="{ active: active(p) }" @click="save(p)">
70- <span class="swatch" :style="swatch(p)">
71- Aa<i :style="{ background: String(p.accent) }" />
72- </span>
67+ <ThemeSwatch :theme="p" />
7368 <span>{{ t(`library.design.presets.${p.id}`) }}</span>
7469 </button>
7570 <button class="theme photo" @click="photo?.click()">
@@ -81,9 +76,7 @@ async function readPhoto(e: Event) {
8176 <div v-if="fromPhoto.length" class="themes">
8277 <span class="muted hint">{{ t("library.design.photoHint") }}</span>
8378 <button v-for="(p, i) in fromPhoto" :key="i" class="theme" :class="{ active: active(p) }" @click="save(p)">
84- <span class="swatch" :style="swatch(p)">
85- Aa<i :style="{ background: String(p.accent) }" />
86- </span>
79+ <ThemeSwatch :theme="p" />
8780 <span>{{ t("library.design.photoTheme", { n: i + 1 }) }}</span>
8881 </button>
8982 </div>
@@ -122,9 +115,7 @@ async function readPhoto(e: Event) {
122115 .theme { display: flex; align-items: center; gap: 8px; padding: 4px 10px 4px 4px; border: 1px solid var(--line); border-radius: 6px; background: none; font-size: 13px; color: var(--muted); cursor: pointer; }
123116 .theme:hover { border-color: var(--line-strong); }
124117 .theme.active { border-color: var(--ink); color: var(--ink); font-weight: 500; }
125-.swatch { position: relative; display: grid; place-items: center; width: 44px; height: 30px; border: 1px solid; font-size: 13px; font-weight: 600; }
126-.swatch i { position: absolute; right: 4px; bottom: 4px; width: 8px; height: 8px; border-radius: 50%; }
127-.swatch.add { border: 1px dashed var(--line-strong); border-radius: 4px; color: var(--muted); font-size: 16px; font-weight: 400; }
118+.swatch.add { display: grid; place-items: center; width: 44px; height: 30px; border: 1px dashed var(--line-strong); border-radius: 4px; color: var(--muted); font-size: 16px; font-weight: 400; }
128119 .fields { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center; }
129120 .field { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); cursor: pointer; }
130121 .field.changed span { color: var(--ink); font-weight: 500; }
@@ -8,6 +8,7 @@ import { computed, onMounted, ref } from "vue";
8 import { useI18n } from "vue-i18n";8 import { useI18n } from "vue-i18n";
9 import { api, putJson } from "../../lib/api";9 import { api, putJson } from "../../lib/api";
10 import { themesFromImage } from "../../lib/palette";10 import { themesFromImage } from "../../lib/palette";
11+import ThemeSwatch from "../../components/ThemeSwatch.vue";
11 import { toast } from "../../lib/toast";12 import { toast } from "../../lib/toast";
12 13
13 type Design = Record<string, string | number>;14 type Design = Record<string, string | number>;
@@ -35,10 +36,6 @@ const changed = (key: string) => view.value && view.value.design[key] !== view.v
35 36
36 // la charte de base en tête des ambiances ; une ambiance est active si tous ses réglages le sont37 // la charte de base en tête des ambiances ; une ambiance est active si tous ses réglages le sont
37 const presets = computed<Design[]>(() => view.value ? [{ ...view.value.defaults, id: "base" }, ...view.value.presets] : []);38 const presets = computed<Design[]>(() => view.value ? [{ ...view.value.defaults, id: "base" }, ...view.value.presets] : []);
38-const swatch = (p: Design) => ({
39- background: String(p.card), borderColor: String(p.line), color: String(p.text),
40- borderRadius: p.radius === undefined ? undefined : `${Math.min(Number(p.radius), 12)}px`,
41-});
42 const active = (values: Design) => !!view.value && Object.entries(values)39 const active = (values: Design) => !!view.value && Object.entries(values)
43 .every(([k, v]) => k === "id" || view.value!.design[k] === v);40 .every(([k, v]) => k === "id" || view.value!.design[k] === v);
44 41
@@ -67,9 +64,7 @@ async function readPhoto(e: Event) {
67 </div>64 </div>
68 <div class="themes">65 <div class="themes">
69 <button v-for="p in presets" :key="p.id" class="theme" :class="{ active: active(p) }" @click="save(p)">66 <button v-for="p in presets" :key="p.id" class="theme" :class="{ active: active(p) }" @click="save(p)">
70- <span class="swatch" :style="swatch(p)">67+ <ThemeSwatch :theme="p" />
71- Aa<i :style="{ background: String(p.accent) }" />
72- </span>
73 <span>{{ t(`library.design.presets.${p.id}`) }}</span>68 <span>{{ t(`library.design.presets.${p.id}`) }}</span>
74 </button>69 </button>
75 <button class="theme photo" @click="photo?.click()">70 <button class="theme photo" @click="photo?.click()">
@@ -81,9 +76,7 @@ async function readPhoto(e: Event) {
81 <div v-if="fromPhoto.length" class="themes">76 <div v-if="fromPhoto.length" class="themes">
82 <span class="muted hint">{{ t("library.design.photoHint") }}</span>77 <span class="muted hint">{{ t("library.design.photoHint") }}</span>
83 <button v-for="(p, i) in fromPhoto" :key="i" class="theme" :class="{ active: active(p) }" @click="save(p)">78 <button v-for="(p, i) in fromPhoto" :key="i" class="theme" :class="{ active: active(p) }" @click="save(p)">
84- <span class="swatch" :style="swatch(p)">79+ <ThemeSwatch :theme="p" />
85- Aa<i :style="{ background: String(p.accent) }" />
86- </span>
87 <span>{{ t("library.design.photoTheme", { n: i + 1 }) }}</span>80 <span>{{ t("library.design.photoTheme", { n: i + 1 }) }}</span>
88 </button>81 </button>
89 </div>82 </div>
@@ -122,9 +115,7 @@ async function readPhoto(e: Event) {
122 .theme { display: flex; align-items: center; gap: 8px; padding: 4px 10px 4px 4px; border: 1px solid var(--line); border-radius: 6px; background: none; font-size: 13px; color: var(--muted); cursor: pointer; }115 .theme { display: flex; align-items: center; gap: 8px; padding: 4px 10px 4px 4px; border: 1px solid var(--line); border-radius: 6px; background: none; font-size: 13px; color: var(--muted); cursor: pointer; }
123 .theme:hover { border-color: var(--line-strong); }116 .theme:hover { border-color: var(--line-strong); }
124 .theme.active { border-color: var(--ink); color: var(--ink); font-weight: 500; }117 .theme.active { border-color: var(--ink); color: var(--ink); font-weight: 500; }
125-.swatch { position: relative; display: grid; place-items: center; width: 44px; height: 30px; border: 1px solid; font-size: 13px; font-weight: 600; }118+.swatch.add { display: grid; place-items: center; width: 44px; height: 30px; border: 1px dashed var(--line-strong); border-radius: 4px; color: var(--muted); font-size: 16px; font-weight: 400; }
126-.swatch i { position: absolute; right: 4px; bottom: 4px; width: 8px; height: 8px; border-radius: 50%; }
127-.swatch.add { border: 1px dashed var(--line-strong); border-radius: 4px; color: var(--muted); font-size: 16px; font-weight: 400; }
128 .fields { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center; }119 .fields { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center; }
129 .field { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); cursor: pointer; }120 .field { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); cursor: pointer; }
130 .field.changed span { color: var(--ink); font-weight: 500; }121 .field.changed span { color: var(--ink); font-weight: 500; }