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.
231 lines
6.9 KiB
231 lines
6.9 KiB
<script lang="ts">
|
|
import * as Tabs from '$soma/components/tabs';
|
|
import { Perms } from '../_perms.svelte';
|
|
|
|
// Perm-runner v2/v3 seed. `?perm.value=tab-2` preselects, `?perm.orientation=vertical`
|
|
// flips axis, `?perm.disabled=true` disables the first Tabs.Provider.
|
|
const perms = Perms.getOr({} as Record<string, string | number | boolean>);
|
|
let value1 = $state((perms.value as string) ?? 'tab-1');
|
|
let value2 = $state('tab-1');
|
|
let value3 = $state('tab-1');
|
|
let value4 = $state('tab-1');
|
|
let orientation = $state<'horizontal' | 'vertical'>(
|
|
(perms.orientation as 'horizontal' | 'vertical') ?? 'horizontal'
|
|
);
|
|
let disabled = $state((perms.disabled as boolean) ?? false);
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Tabs · Soma</title>
|
|
<!-- Axes the perm-runner v3 should generate URL variants for. -->
|
|
<meta name="perm-axes" content="dir,orientation,disabled" />
|
|
</svelte:head>
|
|
|
|
<div class="page">
|
|
<h1>Soma Tabs</h1>
|
|
<p>Headless tabs with roving tabindex, automatic/manual activation, orientation support.</p>
|
|
|
|
<!-- ── 1. Basic horizontal ────────────────────────────────────────────────── -->
|
|
<section>
|
|
<h2>1. Horizontal (automatic)</h2>
|
|
<p class="state">value: {value1}</p>
|
|
|
|
<Tabs.Provider bind:value={value1} {orientation} {disabled}>
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="tab-1" data-perm-step="0" data-perm-label="tab 1 active (initial)">Account</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-2" data-perm-step="1" data-perm-label="switch to tab 2">Password</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-3" data-perm-step="2" data-perm-label="switch to tab 3">Settings</Tabs.Trigger>
|
|
</Tabs.List>
|
|
|
|
<Tabs.Content value="tab-1">
|
|
<div class="panel">Manage your account details and profile information.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-2">
|
|
<div class="panel">Update your password and security preferences.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-3">
|
|
<div class="panel">Configure application settings and notifications.</div>
|
|
</Tabs.Content>
|
|
</Tabs.Provider>
|
|
</section>
|
|
|
|
<!-- ── 2. Manual activation ───────────────────────────────────────────────── -->
|
|
<section>
|
|
<h2>2. Manual activation</h2>
|
|
<p class="desc">Arrow keys move focus, Enter/Space activates.</p>
|
|
<p class="state">value: {value2}</p>
|
|
|
|
<Tabs.Provider bind:value={value2} activationMode="manual">
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="tab-1">Overview</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-2">Analytics</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-3">Reports</Tabs.Trigger>
|
|
</Tabs.List>
|
|
|
|
<Tabs.Content value="tab-1">
|
|
<div class="panel">Dashboard overview with key metrics.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-2">
|
|
<div class="panel">Detailed analytics and usage patterns.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-3">
|
|
<div class="panel">Generated reports and export options.</div>
|
|
</Tabs.Content>
|
|
</Tabs.Provider>
|
|
</section>
|
|
|
|
<!-- ── 3. Vertical ────────────────────────────────────────────────────────── -->
|
|
<section>
|
|
<h2>3. Vertical orientation</h2>
|
|
<p class="desc">Up/Down arrows navigate.</p>
|
|
<p class="state">value: {value3}</p>
|
|
|
|
<Tabs.Provider bind:value={value3} orientation="vertical">
|
|
<div class="vertical-layout">
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="tab-1">General</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-2">Appearance</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-3">Notifications</Tabs.Trigger>
|
|
</Tabs.List>
|
|
|
|
<div class="vertical-panels">
|
|
<Tabs.Content value="tab-1">
|
|
<div class="panel">General application settings.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-2">
|
|
<div class="panel">Theme, colors, and layout options.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-3">
|
|
<div class="panel">Email and push notification preferences.</div>
|
|
</Tabs.Content>
|
|
</div>
|
|
</div>
|
|
</Tabs.Provider>
|
|
</section>
|
|
|
|
<!-- ── 4. Disabled ────────────────────────────────────────────────────────── -->
|
|
<section>
|
|
<h2>4. Disabled tab</h2>
|
|
<p class="desc">Middle tab is disabled — skipped in keyboard nav.</p>
|
|
<p class="state">value: {value4}</p>
|
|
|
|
<Tabs.Provider bind:value={value4}>
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="tab-1">Active</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-2" disabled>Disabled</Tabs.Trigger>
|
|
<Tabs.Trigger value="tab-3">Also active</Tabs.Trigger>
|
|
</Tabs.List>
|
|
|
|
<Tabs.Content value="tab-1">
|
|
<div class="panel">This tab works normally.</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-2">
|
|
<div class="panel">You should not see this (disabled tab).</div>
|
|
</Tabs.Content>
|
|
<Tabs.Content value="tab-3">
|
|
<div class="panel">This tab also works normally.</div>
|
|
</Tabs.Content>
|
|
</Tabs.Provider>
|
|
</section>
|
|
</div>
|
|
|
|
<style>
|
|
.page {
|
|
max-width: 640px;
|
|
margin: 2rem auto;
|
|
font-family: system-ui, sans-serif;
|
|
}
|
|
h1 {
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
h2 {
|
|
margin-top: 2rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
p {
|
|
color: #555;
|
|
}
|
|
.state {
|
|
font-family: monospace;
|
|
font-size: 0.85rem;
|
|
color: #64748b;
|
|
}
|
|
.desc {
|
|
font-size: 0.9rem;
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
section {
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
/* Minimal tab styling for testing */
|
|
:global([data-tabs-list]) {
|
|
display: flex;
|
|
gap: 2px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
}
|
|
:global([data-tabs-trigger]) {
|
|
padding: 0.5rem 1rem;
|
|
border: none;
|
|
background: none;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
color: #64748b;
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
}
|
|
:global([data-tabs-trigger][data-state='active']) {
|
|
color: #1e293b;
|
|
border-bottom-color: #3b82f6;
|
|
}
|
|
:global([data-tabs-trigger]:hover:not([disabled])) {
|
|
color: #1e293b;
|
|
}
|
|
:global([data-tabs-trigger][disabled]) {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
:global([data-tabs-trigger]:focus-visible) {
|
|
outline: 2px solid #3b82f6;
|
|
outline-offset: -2px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.panel {
|
|
padding: 1rem;
|
|
border: 1px solid #e2e8f0;
|
|
border-top: none;
|
|
border-radius: 0 0 6px 6px;
|
|
color: #334155;
|
|
}
|
|
|
|
/* Vertical layout */
|
|
.vertical-layout {
|
|
display: flex;
|
|
gap: 0;
|
|
}
|
|
.vertical-layout :global([data-tabs-list]) {
|
|
flex-direction: column;
|
|
border-bottom: none;
|
|
border-right: 1px solid #e2e8f0;
|
|
min-width: 140px;
|
|
}
|
|
.vertical-layout :global([data-tabs-trigger]) {
|
|
border-bottom: none;
|
|
border-right: 2px solid transparent;
|
|
text-align: left;
|
|
}
|
|
.vertical-layout :global([data-tabs-trigger][data-state='active']) {
|
|
border-right-color: #3b82f6;
|
|
}
|
|
.vertical-panels {
|
|
flex: 1;
|
|
}
|
|
.vertical-panels .panel {
|
|
border: 1px solid #e2e8f0;
|
|
border-left: none;
|
|
border-radius: 0 6px 6px 0;
|
|
}
|
|
</style>
|