You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/routes/test/air/tabs/+page.svelte

490 lines
17 KiB

<script lang="ts">
import { getContext } from 'svelte';
import { Tabs } from '$uix/air';
import type { AirTabsSize, AirTabsVariant } from '$uix/air';
import type { LingString } from '$lib/ling/lib/types';
import type { AirDemoContext } from '../demo-context';
import { AIR_DEMO_CONTEXT_KEY } from '../demo-context';
import CodeExample from '../_components/code-example.svelte';
const demoContext = getContext<AirDemoContext>(AIR_DEMO_CONTEXT_KEY);
const tv = (value: LingString) => demoContext?.tv(value) ?? String(value ?? '');
let playgroundValue = $state('overview');
let playgroundSize = $state<AirTabsSize>('md');
let playgroundVariant = $state<AirTabsVariant>('line');
let playgroundOrientation = $state<'horizontal' | 'vertical'>('horizontal');
let playgroundActivation = $state<'automatic' | 'manual'>('automatic');
let verticalValue = $state('metrics');
const sizes = ['xs', 'sm', 'md', 'lg'] as const;
const variants = ['line', 'surface', 'pills'] as const;
const orientations = ['horizontal', 'vertical'] as const;
const activationModes = ['automatic', 'manual'] as const;
const tabsCode = $derived(
`<Tabs.Root
value="${playgroundValue}"
size="${playgroundSize}"
variant="${playgroundVariant}"
orientation="${playgroundOrientation}"
activationMode="${playgroundActivation}"
>
<Tabs.List>
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
<Tabs.Trigger value="activity">Activity</Tabs.Trigger>
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">...</Tabs.Content>
<Tabs.Content value="activity">...</Tabs.Content>
<Tabs.Content value="settings">...</Tabs.Content>
</Tabs.Root>`
);
const verticalCode = `<Tabs.Root value="metrics" orientation="vertical" variant="surface" size="sm">
<Tabs.List>
<Tabs.Trigger value="metrics">Metrics</Tabs.Trigger>
<Tabs.Trigger value="members">Members</Tabs.Trigger>
<Tabs.Trigger value="logs">Logs</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="metrics">...</Tabs.Content>
<Tabs.Content value="members">...</Tabs.Content>
<Tabs.Content value="logs">...</Tabs.Content>
</Tabs.Root>`;
</script>
<svelte:head>
<title>Tabs · Air</title>
</svelte:head>
<div class="tabs-demo-page">
<header class="hero">
<div class="hero-copy">
<p class="eyebrow">Air / Tabs</p>
<h1>{tv({ es: 'Navegación tabulada sobre terra.Tabs', en: 'Tabbed navigation over terra.Tabs', ar: 'تنقل بعلامات تبويب فوق terra.Tabs' })}</h1>
<p class="lead">
{tv({
es: '`Tabs` reutiliza el wiring accesible de `terra` para roving focus, activación y relación trigger/panel, y `air` aterriza densidad, variantes y lectura visual del estado activo.',
en: '`Tabs` reuses `terra` accessible wiring for roving focus, activation and trigger/panel relationships, while `air` lands density, variants and the visual reading of active state.',
ar: 'يعيد `Tabs` استخدام البنية القابلة للوصول في `terra` للتركيز المتحرك والتفعيل وعلاقة الزر باللوحة، بينما يحدد `air` الكثافة والأنماط وقراءة الحالة النشطة.',
})}
</p>
<div class="hero-signals">
<span>{tv({ es: 'navigation sound', en: 'navigation sound', ar: 'صوت التنقل' })}</span>
<span>{tv({ es: 'roving focus', en: 'roving focus', ar: 'تركيز متحرك' })}</span>
<span>{tv({ es: 'horizontal + vertical', en: 'horizontal + vertical', ar: 'أفقي + عمودي' })}</span>
<span>{tv({ es: 'line · surface · pills', en: 'line · surface · pills', ar: 'line · surface · pills' })}</span>
</div>
</div>
<div class="hero-aside">
<div>
<span>{tv({ es: 'API', en: 'API', ar: 'الواجهة' })}</span>
<strong>Root · List · Trigger · Content</strong>
</div>
<div>
<span>DOM</span>
<strong>{tv({
es: 'div[data-tabs] + tablist + tab triggers + tabpanel',
en: 'div[data-tabs] + tablist + tab triggers + tabpanel',
ar: 'div[data-tabs] + tablist + tab triggers + tabpanel',
})}</strong>
</div>
<div>
<span>{tv({ es: 'Semántica', en: 'Semantic', ar: 'الدلالة' })}</span>
<strong>{tv({
es: 'navigation al cambiar de tab',
en: 'navigation on tab change',
ar: 'navigation عند تغيير التبويب',
})}</strong>
</div>
</div>
</header>
<section class="panel panel--feature">
<header class="section-header">
<h2>{tv({ es: 'Playground', en: 'Playground', ar: 'ساحة التجريب' })}</h2>
<p>
{tv({
es: 'La receta visual vive en `variant` y `size`, mientras `terra` sigue gobernando activación, teclado y relación ARIA.',
en: 'The visual recipe lives in `variant` and `size`, while `terra` still governs activation, keyboard handling and ARIA relationships.',
ar: 'تعيش الوصفة البصرية في `variant` و `size`، بينما يواصل `terra` إدارة التفعيل ولوحة المفاتيح وعلاقات ARIA.',
})}
</p>
</header>
<div class="playground-grid">
<div class="controls">
<div class="control">
<span>{tv({ es: 'Variant', en: 'Variant', ar: 'النمط' })}</span>
<div class="chips">
{#each variants as item}
<button
type="button"
class:chip={true}
class:chip--active={playgroundVariant === item}
onclick={() => (playgroundVariant = item)}
>{item}</button>
{/each}
</div>
</div>
<div class="control">
<span>{tv({ es: 'Size', en: 'Size', ar: 'الحجم' })}</span>
<div class="chips">
{#each sizes as item}
<button
type="button"
class:chip={true}
class:chip--active={playgroundSize === item}
onclick={() => (playgroundSize = item)}
>{item}</button>
{/each}
</div>
</div>
<div class="control">
<span>{tv({ es: 'Orientation', en: 'Orientation', ar: 'الاتجاه' })}</span>
<div class="chips">
{#each orientations as item}
<button
type="button"
class:chip={true}
class:chip--active={playgroundOrientation === item}
onclick={() => (playgroundOrientation = item)}
>{item}</button>
{/each}
</div>
</div>
<div class="control">
<span>{tv({ es: 'Activation', en: 'Activation', ar: 'التفعيل' })}</span>
<div class="chips">
{#each activationModes as item}
<button
type="button"
class:chip={true}
class:chip--active={playgroundActivation === item}
onclick={() => (playgroundActivation = item)}
>{item}</button>
{/each}
</div>
</div>
</div>
<CodeExample title={tv({ es: 'Tabs playground', en: 'Tabs playground', ar: 'ساحة Tabs' })} code={tabsCode}>
<div class="preview-stack">
<Tabs.Root
bind:value={playgroundValue}
size={playgroundSize}
variant={playgroundVariant}
orientation={playgroundOrientation}
activationMode={playgroundActivation}
>
<Tabs.List>
<Tabs.Trigger value="overview">{tv({ es: 'Resumen', en: 'Overview', ar: 'نظرة عامة' })}</Tabs.Trigger>
<Tabs.Trigger value="activity">{tv({ es: 'Actividad', en: 'Activity', ar: 'النشاط' })}</Tabs.Trigger>
<Tabs.Trigger value="settings">{tv({ es: 'Ajustes', en: 'Settings', ar: 'الإعدادات' })}</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="overview">
<div class="content-stack">
<h3>{tv({ es: 'Resumen del workspace', en: 'Workspace overview', ar: 'نظرة عامة على مساحة العمل' })}</h3>
<p>{tv({
es: 'El estado activo debe leerse rápido sin romper la jerarquía tipográfica del contenido.',
en: 'The active state should read quickly without breaking the content typography hierarchy.',
ar: 'يجب أن تُقرأ الحالة النشطة بسرعة من دون كسر الهرمية الطباعية للمحتوى.',
})}</p>
</div>
</Tabs.Content>
<Tabs.Content value="activity">
<div class="content-stack">
<h3>{tv({ es: 'Actividad reciente', en: 'Recent activity', ar: 'النشاط الأخير' })}</h3>
<p>{tv({
es: 'En modo automático el foco ya cambia la tab; en manual hace falta Enter o Space.',
en: 'In automatic mode focus already changes the tab; in manual mode Enter or Space is required.',
ar: 'في الوضع التلقائي يغيّر التركيز التبويب مباشرة؛ أما في الوضع اليدوي فيلزم Enter أو Space.',
})}</p>
</div>
</Tabs.Content>
<Tabs.Content value="settings">
<div class="content-stack">
<h3>{tv({ es: 'Preferencias', en: 'Preferences', ar: 'التفضيلات' })}</h3>
<p>{tv({
es: 'La receta de `air` toca densidad, fondo, borde y foco; `terra` mantiene el contrato accesible.',
en: '`air` recipe touches density, background, border and focus; `terra` keeps the accessible contract.',
ar: 'تضبط وصفة `air` الكثافة والخلفية والحدود والتركيز؛ بينما يحافظ `terra` على العقد القابل للوصول.',
})}</p>
</div>
</Tabs.Content>
</Tabs.Root>
</div>
</CodeExample>
</div>
</section>
<section class="panel">
<header class="section-header">
<h2>{tv({ es: 'Variantes', en: 'Variants', ar: 'الأنماط' })}</h2>
<p>{tv({
es: 'Las variantes no cambian el wiring; solo la lectura visual de la navegación.',
en: 'Variants do not change the wiring; they only change the visual reading of navigation.',
ar: 'لا تغيّر الأنماط البنية الأساسية؛ بل تغيّر فقط القراءة البصرية للتنقل.',
})}</p>
</header>
<div class="variant-grid">
{#each variants as variant}
<CodeExample title={`Tabs ${variant}`} code={`<Tabs.Root variant="${variant}">...</Tabs.Root>`}>
<Tabs.Root value="one" variant={variant} size="sm">
<Tabs.List>
<Tabs.Trigger value="one">{tv({ es: 'Uno', en: 'One', ar: 'واحد' })}</Tabs.Trigger>
<Tabs.Trigger value="two">{tv({ es: 'Dos', en: 'Two', ar: 'اثنان' })}</Tabs.Trigger>
<Tabs.Trigger value="three">{tv({ es: 'Tres', en: 'Three', ar: 'ثلاثة' })}</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="one">
<p class="mini-copy">{tv({
es: `Lectura visual de la variante ${variant}.`,
en: `${variant} variant visual reading.`,
ar: `القراءة البصرية للنمط ${variant}.`,
})}</p>
</Tabs.Content>
<Tabs.Content value="two"></Tabs.Content>
<Tabs.Content value="three"></Tabs.Content>
</Tabs.Root>
</CodeExample>
{/each}
</div>
</section>
<section class="panel">
<header class="section-header">
<h2>{tv({ es: 'Vertical', en: 'Vertical', ar: 'عمودي' })}</h2>
<p>{tv({
es: 'La orientación vertical debe seguir siendo legible en desktop y caer con dignidad a una columna en móvil.',
en: 'Vertical orientation should stay readable on desktop and gracefully collapse to one column on mobile.',
ar: 'يجب أن تبقى البنية العمودية مقروءة على سطح المكتب وأن تنهار بسلاسة إلى عمود واحد على الجوال.',
})}</p>
</header>
<CodeExample title={tv({ es: 'Tabs verticales', en: 'Vertical tabs', ar: 'علامات تبويب عمودية' })} code={verticalCode}>
<Tabs.Root bind:value={verticalValue} orientation="vertical" variant="surface" size="sm">
<Tabs.List>
<Tabs.Trigger value="metrics">{tv({ es: 'Métricas', en: 'Metrics', ar: 'المقاييس' })}</Tabs.Trigger>
<Tabs.Trigger value="members">{tv({ es: 'Equipo', en: 'Team', ar: 'الفريق' })}</Tabs.Trigger>
<Tabs.Trigger value="logs">{tv({ es: 'Logs', en: 'Logs', ar: 'السجلات' })}</Tabs.Trigger>
</Tabs.List>
<Tabs.Content value="metrics">
<div class="content-stack">
<h3>{tv({ es: 'Métricas', en: 'Metrics', ar: 'المقاييس' })}</h3>
<p>{tv({ es: 'Ideal para paneles con navegación local persistente.', en: 'Ideal for dashboards with persistent local navigation.', ar: 'مثالي للوحات التحكم ذات التنقل المحلي المستمر.' })}</p>
</div>
</Tabs.Content>
<Tabs.Content value="members">
<div class="content-stack">
<h3>{tv({ es: 'Equipo', en: 'Team', ar: 'الفريق' })}</h3>
<p>{tv({ es: 'El rail lateral mantiene jerarquía sin convertirse en un menú pesado.', en: 'The side rail keeps hierarchy without turning into a heavy menu.', ar: 'يحافظ الشريط الجانبي على الهرمية من دون أن يتحول إلى قائمة ثقيلة.' })}</p>
</div>
</Tabs.Content>
<Tabs.Content value="logs">
<div class="content-stack">
<h3>{tv({ es: 'Logs', en: 'Logs', ar: 'السجلات' })}</h3>
<p>{tv({ es: 'La receta usa las mismas escalas de spacing, borde y foco del resto de controles.', en: 'The recipe uses the same spacing, border and focus scales as the rest of the controls.', ar: 'تستخدم الوصفة مقاييس المسافات والحدود والتركيز نفسها مثل بقية عناصر التحكم.' })}</p>
</div>
</Tabs.Content>
</Tabs.Root>
</CodeExample>
</section>
</div>
<style>
.tabs-demo-page {
display: flex;
flex-direction: column;
gap: var(--air-space-6);
}
.hero {
display: grid;
grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.85fr);
gap: var(--air-space-6);
align-items: start;
}
.eyebrow {
margin: 0 0 var(--air-space-2);
font: 600 var(--air-font-size-xs) / 1 var(--air-font-mono);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--air-color-content-secondary);
}
.hero h1,
.section-header h2,
.content-stack h3 {
margin: 0;
}
.lead {
margin: var(--air-space-3) 0 0;
max-width: 64ch;
color: var(--air-color-content-secondary);
font-size: var(--air-font-size-md);
line-height: var(--air-leading-prose);
}
.hero-signals,
.controls,
.chips,
.preview-stack,
.content-stack,
.hero-aside {
display: flex;
}
.hero-signals,
.chips {
flex-wrap: wrap;
}
.hero-signals {
gap: var(--air-space-2);
margin-top: var(--air-space-4);
}
.hero-signals span,
.hero-aside span,
.control > span {
font-family: var(--air-font-mono);
font-size: var(--air-font-size-xs);
color: var(--air-color-content-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.hero-signals span,
.hero-aside > div,
.panel {
border: 1px solid color-mix(in srgb, var(--air-color-border-default) 82%, transparent);
background: color-mix(in srgb, var(--air-color-surface-raised) 92%, transparent);
}
.hero-signals span,
.chip,
.hero-aside > div,
.panel {
border-radius: var(--air-radius-lg);
}
.hero-signals span {
padding: 0.45rem 0.7rem;
}
.hero-aside {
flex-direction: column;
gap: var(--air-space-3);
}
.hero-aside > div {
display: grid;
gap: var(--air-space-2);
padding: var(--air-space-4);
}
.hero-aside strong {
font-size: var(--air-font-size-sm);
line-height: var(--air-leading-prose);
}
.panel {
display: grid;
gap: var(--air-space-5);
padding: var(--air-space-5);
}
.panel--feature {
background:
linear-gradient(180deg, color-mix(in srgb, var(--air-color-surface-overlay) 80%, transparent), transparent 55%),
color-mix(in srgb, var(--air-color-surface-raised) 94%, transparent);
}
.section-header {
display: grid;
gap: var(--air-space-2);
}
.section-header p,
.content-stack p,
.mini-copy {
margin: 0;
color: var(--air-color-content-secondary);
line-height: var(--air-leading-prose);
}
.playground-grid {
display: grid;
grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
gap: var(--air-space-5);
}
.controls,
.preview-stack,
.content-stack {
flex-direction: column;
}
.controls,
.preview-stack,
.content-stack {
gap: var(--air-space-3);
}
.control {
display: grid;
gap: var(--air-space-2);
}
.chips {
gap: var(--air-space-2);
}
.chip {
border: 1px solid color-mix(in srgb, var(--air-color-border-default) 82%, transparent);
background: transparent;
color: var(--air-color-content-secondary);
padding: 0.45rem 0.8rem;
font: inherit;
cursor: pointer;
transition:
background var(--air-duration-fast) var(--air-ease-default),
border-color var(--air-duration-fast) var(--air-ease-default),
color var(--air-duration-fast) var(--air-ease-default);
}
.chip--active {
background: color-mix(in srgb, var(--air-color-primary-track) 82%, transparent);
border-color: var(--air-color-primary-border);
color: var(--air-color-content-primary);
}
.variant-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--air-space-4);
}
@media (max-width: 960px) {
.hero,
.playground-grid,
.variant-grid {
grid-template-columns: 1fr;
}
}
</style>

Powered by TurnKey Linux.