feat(studio): un solo set de iconos en vez de emojis mezclados con símbolos
En la misma pantalla convivían dos lenguajes: emojis a color (🌙 📊 🧠 ✍️ 📄 🌐 🔁) y símbolos tipográficos monocromos (✎ ✕ ← ↻ ⚠ ⬇ ✓). Eso se lee como descuido, y encima los emojis los dibuja el sistema operativo — el mismo panel se ve distinto en Mac, en Windows y en Android, sin que podamos hacer nada. Ahora hay un set propio de iconos SVG de trazo: mismo grosor de línea, misma caja, y heredan currentColor, así que toman el color del texto que los rodea y funcionan igual en tema claro y oscuro. Al lado uno de otro tienen el mismo peso visual, que es lo que hacía falta. El emoji suelto de la pantalla de inicio pasa a ser Umi, que ya es el personaje del producto. Un detalle de implementación que costó dos intentos: los iconos NO pueden declararse como cadenas de HTML para v-html. Dentro de un <svg>, v-html parsea como HTML y los <path> quedan sin el namespace de SVG: aparecen en el DOM pero no dibujan nada, así que la primera versión se veía perfecta en el código y en blanco en pantalla. Van declarados como pares [etiqueta, atributos] para que Vue los cree con el namespace correcto. Y AgenteDetail usaba cinco iconos sin importar el componente. Verificado ahora sobre todos los archivos: cada componente usado está importado donde se usa. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
002b75e71b
commit
ee9bb2cfb7
@@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import UiIcono from './ui/UiIcono.vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { api } from '../lib/api.js'
|
||||
@@ -191,14 +192,14 @@ onMounted(() => {
|
||||
title="Editar"
|
||||
@click="editarTenant(t)"
|
||||
>
|
||||
✎
|
||||
<UiIcono nombre="lapiz" :tam="14" />
|
||||
</button>
|
||||
<button
|
||||
class="p-1 text-tenue hover:text-red-600"
|
||||
title="Eliminar"
|
||||
@click="eliminar(t)"
|
||||
>
|
||||
✕
|
||||
<UiIcono nombre="cerrar" :tam="14" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<script setup>
|
||||
// Un solo set de iconos para todo uMind.
|
||||
//
|
||||
// Antes convivían emojis a color (🌙 📊 🧠 ✍️) con símbolos tipográficos
|
||||
// monocromos (✎ ✕ ← ↻). Dos lenguajes distintos en la misma pantalla se leen
|
||||
// como descuido, y encima los emojis los dibuja el sistema operativo: el mismo
|
||||
// panel se ve distinto en Mac, en Windows y en Android, sin que podamos hacer
|
||||
// nada.
|
||||
//
|
||||
// Estos son SVG de trazo que heredan currentColor, así que toman el color del
|
||||
// texto que los rodea y funcionan igual en tema claro y oscuro. Mismo grosor de
|
||||
// línea y misma caja en todos: al lado uno de otro tienen el mismo peso visual.
|
||||
defineProps({
|
||||
nombre: { type: String, required: true },
|
||||
tam: { type: [Number, String], default: 16 },
|
||||
})
|
||||
|
||||
// Cada icono es una lista de [etiqueta, atributos]. No es una cadena de HTML a
|
||||
// propósito: v-html dentro de un <svg> parsea como HTML y los <path> quedan sin
|
||||
// el namespace de SVG — se insertan en el DOM pero no dibujan nada. Declarados
|
||||
// así, Vue los crea con el namespace correcto porque ve el <svg> padre.
|
||||
const ICONOS = {
|
||||
luna: [['path', { d: 'M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5Z' }]],
|
||||
sol: [
|
||||
['circle', { cx: 12, cy: 12, r: 4 }],
|
||||
['path', { d: 'M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4' }],
|
||||
],
|
||||
lapiz: [
|
||||
['path', { d: 'M12 20h9' }],
|
||||
['path', { d: 'M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z' }],
|
||||
],
|
||||
cerrar: [['path', { d: 'M18 6 6 18M6 6l12 12' }]],
|
||||
atras: [['path', { d: 'M19 12H5M12 19l-7-7 7-7' }]],
|
||||
grafico: [
|
||||
['path', { d: 'M3 3v18h18' }],
|
||||
['path', { d: 'M7 15v3M12 10v8M17 6v12' }],
|
||||
],
|
||||
cerebro: [
|
||||
['path', { d: 'M9.5 3A2.5 2.5 0 0 0 7 5.5v.2A2.7 2.7 0 0 0 5 8.3c0 .8.3 1.5.9 2A2.8 2.8 0 0 0 5 12.5c0 1 .5 1.9 1.3 2.4-.2.4-.3.8-.3 1.2A2.9 2.9 0 0 0 9 19a2.5 2.5 0 0 0 3 2V3.8A2.5 2.5 0 0 0 9.5 3Z' }],
|
||||
['path', { d: 'M14.5 3A2.5 2.5 0 0 1 17 5.5v.2a2.7 2.7 0 0 1 2 2.6c0 .8-.3 1.5-.9 2A2.8 2.8 0 0 1 19 12.5c0 1-.5 1.9-1.3 2.4.2.4.3.8.3 1.2A2.9 2.9 0 0 1 15 19a2.5 2.5 0 0 1-3 2' }],
|
||||
],
|
||||
imagen: [
|
||||
['rect', { x: 3, y: 3, width: 18, height: 18, rx: 2 }],
|
||||
['circle', { cx: 8.5, cy: 8.5, r: 1.5 }],
|
||||
['path', { d: 'm21 15-5-5L5 21' }],
|
||||
],
|
||||
microfono: [
|
||||
['rect', { x: 9, y: 2, width: 6, height: 12, rx: 3 }],
|
||||
['path', { d: 'M5 10a7 7 0 0 0 14 0M12 17v5' }],
|
||||
],
|
||||
escribir: [
|
||||
['path', { d: 'M3 21h18' }],
|
||||
['path', { d: 'M5 17 17.5 4.5a2.1 2.1 0 0 1 3 3L8 20l-4 1Z' }],
|
||||
],
|
||||
archivo: [
|
||||
['path', { d: 'M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7Z' }],
|
||||
['path', { d: 'M14 2v5h5' }],
|
||||
],
|
||||
sitio: [
|
||||
['circle', { cx: 12, cy: 12, r: 9 }],
|
||||
['path', { d: 'M3 12h18M12 3a15 15 0 0 1 0 18 15 15 0 0 1 0-18Z' }],
|
||||
],
|
||||
refrescar: [
|
||||
['path', { d: 'M21 12a9 9 0 1 1-3-6.7' }],
|
||||
['path', { d: 'M21 4v5h-5' }],
|
||||
],
|
||||
descargar: [
|
||||
['path', { d: 'M12 3v12' }],
|
||||
['path', { d: 'm7 11 5 5 5-5' }],
|
||||
['path', { d: 'M4 21h16' }],
|
||||
],
|
||||
alerta: [
|
||||
['path', { d: 'M12 9v4M12 17h.01' }],
|
||||
['path', { d: 'M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z' }],
|
||||
],
|
||||
ok: [['path', { d: 'M20 6 9 17l-5-5' }]],
|
||||
copiar: [
|
||||
['rect', { x: 9, y: 9, width: 12, height: 12, rx: 2 }],
|
||||
['path', { d: 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' }],
|
||||
],
|
||||
mensaje: [['path', { d: 'M21 11.5a8.4 8.4 0 0 1-9 8.4 8.5 8.5 0 0 1-3.9-.9L3 21l1.9-5.1A8.4 8.4 0 0 1 4 11.5a8.5 8.5 0 0 1 8.5-8.5A8.4 8.4 0 0 1 21 11.5Z' }]],
|
||||
auto: [
|
||||
['path', { d: 'M17 2.1 21 6l-4 3.9' }],
|
||||
['path', { d: 'M3 11V9a4 4 0 0 1 4-4h14' }],
|
||||
['path', { d: 'M7 21.9 3 18l4-3.9' }],
|
||||
['path', { d: 'M21 13v2a4 4 0 0 1-4 4H3' }],
|
||||
],
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg
|
||||
:width="tam"
|
||||
:height="tam"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.75"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="inline-block shrink-0 align-[-0.15em]"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<component
|
||||
v-for="(el, i) in ICONOS[nombre] || []"
|
||||
:key="i"
|
||||
:is="el[0]"
|
||||
v-bind="el[1]"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup>
|
||||
import UiIcono from './UiIcono.vue'
|
||||
defineProps({ titulo: { type: String, default: '' }, ancho: { type: String, default: 'max-w-lg' } })
|
||||
const emit = defineEmits(['cerrar'])
|
||||
</script>
|
||||
@@ -12,7 +13,7 @@ const emit = defineEmits(['cerrar'])
|
||||
<div :class="['card w-full p-6 max-h-[90vh] overflow-y-auto animate-escalar shadow-2xl', ancho]">
|
||||
<div v-if="titulo" class="flex items-center justify-between mb-4">
|
||||
<h2 class="font-semibold text-texto">{{ titulo }}</h2>
|
||||
<button class="btn-ghost !px-2 !py-1" type="button" @click="emit('cerrar')">✕</button>
|
||||
<button class="btn-ghost !px-2 !py-1" type="button" @click="emit('cerrar')"><UiIcono nombre="cerrar" :tam="15" /></button>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user