First commit

This commit is contained in:
2026-08-10 04:29:24 -06:00
commit 26851fa750
71 changed files with 8333 additions and 0 deletions
+16
View File
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Catálogo del sistema de diseño de Omaresquivel"
/>
<title>Design System · Playground</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+490
View File
@@ -0,0 +1,490 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import {
DsAlert,
DsAppShell,
DsButton,
DsButtonGroup,
DsCheckbox,
DsConfirmDialog,
DsDivider,
DsEmptyState,
DsErrorState,
DsFormActions,
DsIconButton,
DsInline,
DsInlineMessage,
DsLoadingState,
DsPageHeader,
DsPanel,
DsPasswordInput,
DsProgressBar,
DsRadioGroup,
DsSearchInput,
DsSectionHeader,
DsSelect,
DsSettingsSection,
DsSkeleton,
DsSpinner,
DsStack,
DsSwitch,
DsTabs,
DsTextarea,
DsTextInput,
type DsTabItem,
} from "@omaresquivel/design-system";
const sections: DsTabItem[] = [
{ id: "catalog", label: "Catálogo" },
{ id: "profile", label: "Perfil" },
{ id: "settings", label: "Configuración" },
{ id: "form", label: "Formulario largo" },
{ id: "states", label: "Estados de panel" },
];
const currentSection = ref("catalog");
const name = ref("Omar Esquivel");
const email = ref("omar@example.com");
const password = ref("correct horse battery staple");
const search = ref("");
const bio = ref("Diseño y desarrollo productos web mantenibles.");
const role = ref("designer");
const plan = ref("pro");
const notifications = ref(true);
const analytics = ref(false);
const terms = ref(false);
const dialogOpen = ref(false);
const confirmOpen = ref(false);
const panelState = ref<"loading" | "error" | "empty" | "success">("success");
const nameError = computed(() =>
name.value.length > 0 && name.value.length < 3
? "Escribe al menos tres caracteres."
: undefined,
);
const roleOptions = [
{ label: "Diseño", value: "designer" },
{ label: "Desarrollo", value: "developer" },
{ label: "Producto", value: "product" },
];
const planOptions = [
{
label: "Inicial",
value: "starter",
description: "Para proyectos personales.",
},
{ label: "Profesional", value: "pro", description: "Para equipos pequeños." },
{
label: "Empresa",
value: "enterprise",
description: "Controles y soporte avanzados.",
},
];
</script>
<template>
<DsAppShell>
<main class="playground-frame">
<DsPageHeader
eyebrow="Sistema de diseño"
title="Fundamentos y componentes"
description="Una fuente única de estilos sobrios, accesibles y reutilizables para productos Vue."
>
<template #actions>
<DsButton variant="secondary" @click="dialogOpen = true"
>Ver instalación</DsButton
>
</template>
</DsPageHeader>
<div class="playground-body">
<DsTabs
v-model="currentSection"
class="playground-nav"
:items="sections"
label="Secciones del catálogo"
>
<template #catalog>
<DsStack :gap="8">
<DsSectionHeader
title="Catálogo base"
description="Variantes, estados y controles que comparten el mismo lenguaje visual."
/>
<section class="playground-grid playground-grid--2">
<DsPanel>
<DsStack :gap="4">
<DsSectionHeader
title="Acciones"
description="Jerarquía explícita para cada intención."
/>
<DsButtonGroup label="Variantes de botones">
<DsButton>Guardar cambios</DsButton>
<DsButton variant="secondary">Cancelar</DsButton>
<DsButton variant="ghost">Más opciones</DsButton>
<DsButton variant="danger">Eliminar</DsButton>
</DsButtonGroup>
<DsButtonGroup label="Estados de botones">
<DsButton size="sm">Pequeño</DsButton>
<DsButton loading>Guardando</DsButton>
<DsButton disabled>Deshabilitado</DsButton>
<DsIconButton label="Añadir elemento" variant="secondary"
>+</DsIconButton
>
</DsButtonGroup>
</DsStack>
</DsPanel>
<DsPanel>
<DsStack :gap="4">
<DsSectionHeader
title="Tokens de color"
description="Valores semánticos sobrescribibles con CSS."
/>
<div class="playground-grid playground-grid--2">
<div class="playground-swatch playground-swatch--primary">
Primary
</div>
<div class="playground-swatch playground-swatch--inverse">
Surface inverse
</div>
<div class="playground-swatch playground-swatch--muted">
Surface muted
</div>
<div class="playground-swatch">Surface</div>
</div>
</DsStack>
</DsPanel>
</section>
<DsDivider />
<section class="playground-grid playground-grid--2">
<DsPanel>
<DsStack :gap="4">
<DsSectionHeader
title="Campos"
description="Label, ayuda y error comparten un contrato consistente."
/>
<DsTextInput
v-model="name"
label="Nombre completo"
required
:error="nameError"
autocomplete="name"
/>
<DsSearchInput
v-model="search"
label="Buscar proyecto"
description="Busca por nombre o identificador."
/>
<DsPasswordInput
v-model="password"
label="Contraseña"
required
autocomplete="current-password"
/>
<DsSelect
v-model="role"
label="Área principal"
:options="roleOptions"
required
/>
<DsTextarea
v-model="bio"
label="Biografía"
description="Máximo 280 caracteres."
:maxlength="280"
/>
</DsStack>
</DsPanel>
<DsPanel>
<DsStack :gap="4">
<DsSectionHeader
title="Selección"
description="Controles nativos y comportamiento predecible."
/>
<DsCheckbox
v-model="terms"
label="Acepto los términos"
description="Puedes consultar la política antes de continuar."
required
/>
<DsRadioGroup
v-model="plan"
legend="Plan"
name="catalog-plan"
:options="planOptions"
/>
<DsSwitch
v-model="notifications"
label="Notificaciones"
description="Recibe novedades importantes por correo."
/>
<DsSwitch
v-model="analytics"
label="Analítica de uso"
description="Ayúdanos a mejorar el producto."
/>
</DsStack>
</DsPanel>
</section>
<DsPanel>
<DsStack :gap="4">
<DsSectionHeader
title="Retroalimentación"
description="Mensajes visibles y anunciables solo cuando son dinámicos."
/>
<DsAlert variant="info" title="Información"
>El nuevo tema se aplicará a todos tus proyectos.</DsAlert
>
<DsAlert variant="success" title="Cambios guardados"
>Tu configuración ya está actualizada.</DsAlert
>
<DsAlert variant="warning" title="Revisa la información"
>Algunos campos todavía usan valores
predeterminados.</DsAlert
>
<DsAlert variant="error" title="No pudimos guardar"
>Comprueba tu conexión e intenta nuevamente.</DsAlert
>
<DsProgressBar :value="68" label="Configuración completada" />
<DsInline align="center"
><DsSpinner size="sm" /><DsInlineMessage
>Sincronizando preferencias</DsInlineMessage
></DsInline
>
<DsSkeleton height="2.75rem" />
</DsStack>
</DsPanel>
</DsStack>
</template>
<template #profile>
<DsStack :gap="6">
<DsSectionHeader
title="Perfil de usuario"
description="Ejemplo completo compuesto únicamente con la API pública."
/>
<div class="playground-profile">
<DsPanel>
<DsStack align="center" :gap="4">
<div class="playground-avatar" aria-hidden="true">OE</div>
<div style="text-align: center">
<strong>Omar Esquivel</strong>
<p class="playground-meta">Administrador</p>
</div>
<DsButton variant="secondary" size="sm"
>Cambiar fotografía</DsButton
>
</DsStack>
</DsPanel>
<form @submit.prevent>
<DsPanel>
<DsStack :gap="6">
<DsSectionHeader
title="Información personal"
description="Esta información aparecerá en tu cuenta y actividad."
/>
<div class="playground-grid playground-grid--2">
<DsTextInput
v-model="name"
label="Nombre completo"
required
autocomplete="name"
/>
<DsTextInput
v-model="email"
type="email"
label="Correo electrónico"
required
autocomplete="email"
/>
</div>
<DsTextarea v-model="bio" label="Acerca de ti" />
<DsFormActions
><DsButton variant="secondary">Cancelar</DsButton
><DsButton type="submit"
>Guardar perfil</DsButton
></DsFormActions
>
</DsStack>
</DsPanel>
</form>
</div>
</DsStack>
</template>
<template #settings>
<DsStack :gap="6">
<DsSectionHeader
title="Configuración"
description="Secciones independientes que pueden guardarse por separado."
/>
<DsSettingsSection
title="Comunicaciones"
description="Elige cómo quieres recibir información."
>
<DsStack :gap="4">
<DsSwitch
v-model="notifications"
label="Actualizaciones del producto"
description="Noticias sobre funciones y mantenimiento."
/>
<DsSwitch
v-model="analytics"
label="Resumen semanal"
description="Actividad relevante en un solo correo."
/>
</DsStack>
<template #actions
><DsButton size="sm">Guardar</DsButton></template
>
</DsSettingsSection>
<DsSettingsSection
title="Zona de riesgo"
description="Estas acciones pueden afectar el acceso de todo el equipo."
>
<DsAlert variant="warning" title="Antes de continuar"
>Exporta una copia de la información que necesites
conservar.</DsAlert
>
<template #actions
><DsButton
size="sm"
variant="danger"
@click="confirmOpen = true"
>Eliminar cuenta</DsButton
></template
>
</DsSettingsSection>
</DsStack>
</template>
<template #form>
<form @submit.prevent>
<DsStack :gap="6">
<DsSectionHeader
title="Solicitud de proyecto"
description="Formulario largo dividido en secciones claras."
/>
<DsSettingsSection
title="1. Contexto"
description="Cuéntanos qué necesitas resolver."
>
<DsTextarea
label="Objetivo y contexto de negocio"
required
placeholder="Agrega la información relevante para esta sección."
/>
</DsSettingsSection>
<DsSettingsSection
title="2. Responsables"
description="Personas que participarán en las decisiones."
>
<div class="playground-grid playground-grid--2">
<DsTextInput label="Responsable" required />
<DsTextInput label="Correo" type="email" required />
</div>
</DsSettingsSection>
<DsSettingsSection
title="3. Alcance"
description="Define prioridad y tipo de entrega."
>
<DsRadioGroup
v-model="plan"
legend="Prioridad"
name="project-priority"
:options="planOptions"
/>
</DsSettingsSection>
<DsFormActions
><DsButton variant="secondary">Guardar borrador</DsButton
><DsButton type="submit"
>Enviar solicitud</DsButton
></DsFormActions
>
</DsStack>
</form>
</template>
<template #states>
<DsStack :gap="6">
<DsSectionHeader
title="Estados de panel"
description="Prueba loading, error, vacío y contenido correcto."
>
<template #actions>
<DsButtonGroup label="Seleccionar estado">
<DsButton
v-for="state in [
'loading',
'error',
'empty',
'success',
] as const"
:key="state"
size="sm"
:variant="panelState === state ? 'primary' : 'secondary'"
@click="panelState = state"
>{{ state }}</DsButton
>
</DsButtonGroup>
</template>
</DsSectionHeader>
<DsPanel>
<DsLoadingState v-if="panelState === 'loading'" />
<DsErrorState
v-else-if="panelState === 'error'"
title="No pudimos cargar los proyectos"
description="Comprueba tu conexión e intenta nuevamente."
><template #actions
><DsButton>Reintentar</DsButton></template
></DsErrorState
>
<DsEmptyState
v-else-if="panelState === 'empty'"
title="Aún no hay proyectos"
description="Crea el primero para comenzar a organizar tu trabajo."
><template #actions
><DsButton>Crear proyecto</DsButton></template
></DsEmptyState
>
<DsStack v-else :gap="4"
><DsAlert variant="success" title="Todo está al día"
>Hay 12 proyectos activos y ninguno requiere
atención.</DsAlert
><DsProgressBar :value="100" label="Sincronización"
/></DsStack>
</DsPanel>
</DsStack>
</template>
</DsTabs>
</div>
</main>
<DsConfirmDialog
v-model="confirmOpen"
title="¿Eliminar la cuenta?"
description="Esta acción no se puede deshacer."
confirm-label="Sí, eliminar"
danger
@confirm="confirmOpen = false"
>
Se eliminarán los proyectos y preferencias vinculados a esta cuenta.
</DsConfirmDialog>
<DsConfirmDialog
v-model="dialogOpen"
title="Instalación"
confirm-label="Entendido"
@confirm="dialogOpen = false"
>
<pre
class="playground-code"
><code>import { DsButton } from '@omaresquivel/design-system'
import '@omaresquivel/design-system/style.css'</code></pre>
</DsConfirmDialog>
</DsAppShell>
</template>
+6
View File
@@ -0,0 +1,6 @@
import { createApp } from "vue";
import "@omaresquivel/design-system/style.css";
import "./playground.css";
import App from "./App.vue";
createApp(App).mount("#app");
+84
View File
@@ -0,0 +1,84 @@
.playground-frame {
overflow: hidden;
border: 1px solid var(--ds-color-border);
border-radius: var(--ds-radius-xl);
background: var(--ds-color-surface);
box-shadow: var(--ds-shadow-md);
}
.playground-body {
padding: var(--ds-space-5);
}
.playground-grid {
display: grid;
gap: var(--ds-space-5);
}
.playground-grid--2 {
grid-template-columns: minmax(0, 1fr);
}
.playground-nav {
margin-bottom: var(--ds-space-6);
}
.playground-swatch {
min-height: 5rem;
border: 1px solid var(--ds-color-border);
border-radius: var(--ds-radius-md);
padding: var(--ds-space-3);
font-size: var(--ds-font-size-xs);
}
.playground-swatch--primary {
color: white;
background: var(--ds-color-primary);
}
.playground-swatch--inverse {
color: white;
background: var(--ds-color-surface-inverse);
}
.playground-swatch--muted {
background: var(--ds-color-surface-muted);
}
.playground-icon {
width: 1.1rem;
height: 1.1rem;
}
.playground-profile {
display: grid;
grid-template-columns: 1fr;
gap: var(--ds-space-6);
}
.playground-avatar {
display: grid;
width: 5rem;
height: 5rem;
place-items: center;
border-radius: 50%;
color: white;
background: var(--ds-color-primary);
font-size: var(--ds-font-size-xl);
font-weight: 800;
}
.playground-meta {
margin: 0;
color: var(--ds-color-text-muted);
font-size: var(--ds-font-size-sm);
}
.playground-code {
overflow-x: auto;
border-radius: var(--ds-radius-md);
padding: var(--ds-space-4);
color: #e2e8f0;
background: var(--ds-color-surface-inverse);
font-size: var(--ds-font-size-sm);
}
@media (min-width: 48rem) {
.playground-body {
padding: var(--ds-space-8);
}
.playground-grid--2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.playground-profile {
grid-template-columns: 15rem minmax(0, 1fr);
}
}