Skip to content

Latest commit

 

History

History
1580 lines (1529 loc) · 79.7 KB

File metadata and controls

1580 lines (1529 loc) · 79.7 KB
layout Blank
<script setup> import { ref, computed } from 'vue'; import ExampleTabs from '@exampleComponents/ExampleTabs.vue'; import { DtTabGroup, DtTab, DtTabPanel } from '@dialpad/dialtone-vue'; import { useThemeManager } from '@composables/useThemeManager'; import ExampleProfileCard from '@exampleComponents/ExampleProfileCard.vue'; const { currentMode, currentContrast, currentModeIconName, setMode, setContrast, } = useThemeManager(); const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); const borderless = ref(false); const outlined = ref(false); const muted = ref(false); const showIcon = ref(false); const showTabEndIcon = ref(false); const showLeading = ref(false); const showTrailing = ref(false); const size = ref('md'); const selectOnFocus = ref(false); const isDisabled = ref(false); const labelSizeSelection = ref('default'); const resolvedLabelSize = computed(() => labelSizeSelection.value === 'default' ? undefined : labelSizeSelection.value); const labelStrengthSelection = ref('default'); const resolvedLabelStrength = computed(() => labelStrengthSelection.value === 'default' ? undefined : labelStrengthSelection.value); const showLabelClass = ref(false); const resolvedLabelClass = computed(() => showLabelClass.value ? 'd-bgc-warning' : undefined); const checkRadioLabelSize = ref('default'); const resolvedCheckRadioLabelSize = computed(() => checkRadioLabelSize.value === 'default' ? undefined : checkRadioLabelSize.value); const checkRadioLabelStrength = ref('default'); const resolvedCheckRadioLabelStrength = computed(() => checkRadioLabelStrength.value === 'default' ? undefined : checkRadioLabelStrength.value); const showBtnLeading = ref(false); const showBtnTrailing = ref(false); const showBtnStartIcon = ref(false); const showBtnEndIcon = ref(false); const removeBtnSlotClass = ref(false); const highlightBtnSlotClass = ref(false); const showBtnLabelClass = ref(false); const resolvedBtnLabelClass = computed(() => showBtnLabelClass.value ? 'd-bgc-warning' : undefined); const showTabLabelClass = ref(false); const resolvedTabLabelClass = computed(() => showTabLabelClass.value ? 'd-bgc-warning' : undefined); const showInputDescription = ref(false); const showInputMessages = ref(false); const inputMessages = computed(() => showInputMessages.value ? [{ message: 'Critical validation message', type: 'critical' }] : []); const showInputMessagesClass = ref(false); const resolvedInputMessagesClass = computed(() => showInputMessagesClass.value ? 'd-bgc-critical' : undefined); const showInputDescriptionClass = ref(false); const resolvedInputDescriptionClass = computed(() => showInputDescriptionClass.value ? 'd-bgc-success' : undefined); const showDescription = ref(false); const showCheckRadioMessages = ref(false); const checkRadioMessages = computed(() => showCheckRadioMessages.value ? [{ message: 'Critical validation message', type: 'critical' }] : []); const showCheckRadioMessagesClass = ref(false); const resolvedCheckRadioMessagesClass = computed(() => showCheckRadioMessagesClass.value ? 'd-bgc-critical' : undefined); const showCheckRadioDescriptionClass = ref(false); const resolvedCheckRadioDescriptionClass = computed(() => showCheckRadioDescriptionClass.value ? 'd-bgc-success' : undefined); const checkRadioDisabled = ref(false); </script> Scratchpad System Light Dark Default High

Focusgroup directive

Declarative roving tabindex for composite widgets. Manages arrow-key navigation, tabindex management, looping, focus memory, and disabled-item skipping — following the Open UI focusgroup proposal.

The directive handles focus movement only. Activation and selection (toggling aria-selected, aria-checked, etc.) remain the consumer's responsibility.

Usage

Import and install the directive:

import { DtFocusgroupDirective } from "@dialpad/dialtone-vue";
app.use(DtFocusgroupDirective);

Basic usage

Just add v-dt-focusgroup and any focusable child will be managed by the focusgroup.

[!WARNING] Always pair with an ARIA role The directive manages keyboard focus but does not set any ARIA attributes. For screen readers to announce the widget correctly, you must provide a role (toolbar, tablist, listbox, radiogroup, menu), an accessible name (aria-label), and aria-orientation when the axis differs from the role's default. Without a role, the container is opaque to assistive technology — arrow-key cycling works, but the user has no context for what they're navigating.

<dt-stack v-dt-focusgroup direction="row" gap="100">
  <dt-button kind="muted" importance="outlined">Button</dt-button>
  <dt-button kind="muted" importance="outlined">Button</dt-button>
  <dt-button kind="muted" importance="outlined">Button</dt-button>
</dt-stack>

Token syntax

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal'" aria-label="Formatting">
  <dt-button kind="muted" importance="outlined">Bold</dt-button>
  <dt-button kind="muted" importance="outlined">Italic</dt-button>
  <dt-button kind="muted" importance="outlined">Underline</dt-button>
</dt-stack>

Object syntax

<dt-stack gap="100" role="listbox" v-dt-focusgroup="{ axis: 'vertical', loop: false }" aria-label="Fruits">
  <dt-button role="option" kind="muted" importance="outlined">Apple</dt-button>
  <dt-button role="option" kind="muted" importance="outlined">Banana</dt-button>
</dt-stack>
<dt-stack direction="row" gap="100" role="listbox" aria-orientation="horizontal" v-dt-focusgroup="{ axis: 'horizontal', loop: false }" aria-label="Fruits">
  <dt-button role="option" kind="muted" importance="outlined">Apple</dt-button>
  <dt-button role="option" kind="muted" importance="outlined">Banana</dt-button>
</dt-stack>

Vertical toolbar

<dt-stack gap="100" role="toolbar" aria-orientation="vertical" v-dt-focusgroup="'vertical'" aria-label="Formatting">
  <dt-button kind="muted" importance="outlined">Bold</dt-button>
  <dt-button kind="muted" importance="outlined">Italic</dt-button>
  <dt-button kind="muted" importance="outlined">Underline</dt-button>
</dt-stack>

noloop — focus stops at boundaries

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal noloop'" aria-label="Pagination">
  <dt-button kind="muted" importance="outlined">First</dt-button>
  <dt-button kind="muted" importance="outlined">Previous</dt-button>
  <dt-button kind="muted" importance="outlined">Next</dt-button>
  <dt-button kind="muted" importance="outlined">Last</dt-button>
</dt-stack>

nomemory — re-entry always starts at first item

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal nomemory'" aria-label="Actions">
  <dt-button kind="muted" importance="outlined">Cut</dt-button>
  <dt-button kind="muted" importance="outlined">Copy</dt-button>
  <dt-button kind="muted" importance="outlined">Paste</dt-button>
</dt-stack>

Disabled items — skipped by default

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal'" aria-label="Tools">
  <dt-button kind="muted" importance="outlined">Pen</dt-button>
  <dt-button kind="muted" importance="outlined" disabled>Eraser (disabled)</dt-button>
  <dt-button kind="muted" importance="outlined">Highlighter</dt-button>
</dt-stack>

noskipdisabled — disabled items remain focusable

<dt-stack direction="row" gap="100" role="tablist" v-dt-focusgroup="'horizontal nomemory'" aria-label="Platforms">
  <dt-button role="tab" kind="muted" importance="outlined">Mac</dt-button>
  <dt-button role="tab" kind="muted" importance="outlined" class="d-btn--disabled" aria-disabled="true">Windows (disabled)</dt-button>
  <dt-button role="tab" kind="muted" importance="outlined">Linux</dt-button>
</dt-stack>

dt-focusgroup-move event — selection follows focus

<dt-stack direction="row" gap="100" role="tablist" v-dt-focusgroup="'horizontal nomemory'" aria-label="Tabs" @dt-focusgroup-move="$event.detail.item.setAttribute('aria-selected', 'true'); $event.detail.previousItem.setAttribute('aria-selected', 'false')">
  <dt-button role="tab" kind="muted" importance="clear" aria-selected="true">One</dt-button>
  <dt-button role="tab" kind="muted" importance="clear" aria-selected="false">Two</dt-button>
  <dt-button role="tab" kind="muted" importance="clear" aria-selected="false">Three</dt-button>
</dt-stack>

Item opt-out

Add data-dt-focusgroup-skip to exclude an element from arrow-key navigation (e.g., text inputs that need their own arrow keys):

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal'" aria-label="Formatting with opt-out">
  <dt-button kind="muted" importance="outlined">Bold</dt-button>
  <dt-input data-dt-focusgroup-skip placeholder="This will be skipped" />
  <dt-button kind="muted" importance="outlined">Code</dt-button>
  <dt-link data-dt-focusgroup-skip>Skipped Text link</dt-link>
  <dt-button kind="muted" importance="outlined">Code</dt-button>
</dt-stack>

Mixed focusable elements

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal'" aria-label="Mixed elements">
  <dt-button kind="muted" importance="outlined">Button</dt-button>
  <dt-link>Link</dt-link>
  <dt-select-menu
    :options="[
          { value: ``, label: `Please select one` },
          { value: `1`, label: `Option 1` },
          { value: `2`, label: `Option 2` },
          { value: `3`, label: `Option 3` },
        ]"
    label="Default"
    :model-value="modelValue"
    :show-label="false"
    @input="onInput"
    @change="onChange"
  />
</dt-stack>

Nesting depth

Items do not need to be direct children. The directive uses querySelectorAll on the container, finding items at any nesting depth in DOM order:

<dt-stack direction="row" gap="100" role="toolbar" v-dt-focusgroup="'horizontal'" aria-label="Nested groups">
  <dt-stack direction="row" gap="100" class="d-bgc-moderate-opaque d-p-100">
    <dt-button kind="muted" importance="outlined">btn</dt-button>
    <dt-button kind="muted" importance="outlined">btn</dt-button>
    <dt-button kind="muted" importance="outlined">btn</dt-button>
  </dt-stack>
  <dt-stack direction="row" gap="100" class="d-bgc-moderate-opaque d-p-100">
    <dt-button kind="muted" importance="outlined">btn</dt-button>
    <dt-button kind="muted" importance="outlined">btn</dt-button>
  </dt-stack>
  <dt-stack direction="row" gap="100" class="d-bgc-moderate-opaque d-p-100">
    <dt-link>text link a</dt-link>
    <dt-link>text link b</dt-link>
  </dt-stack>
</dt-stack>

Recipes

Real-world patterns showing how v-dt-focusgroup composes with Dialtone components.

Table with row navigation

<table class="d-table dialtone-doc-table" v-dt-focusgroup="{ axis: 'vertical', selector: 'tbody tr' }" aria-label="Office List">
  <caption class="d-table__caption">Office List</caption>
  <thead>
    <tr>
      <th scope="col">Office</th>
      <th scope="col">Country</th>
      <th scope="col" width="10%">Employees</th>
      <th scope="col" colspan="2">Contact</th>
    </tr>
  </thead>
  <tbody>
    <tr class="h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-c-pointer" tabindex="0">
      <th scope="row">Austin, TX</th>
      <td>United States</td>
      <td>48</td>
      <td>Henna Ferry</td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 1</dt-button></td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 2</dt-button></td>
    </tr>
    <tr class="h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-c-pointer" tabindex="-1">
      <th scope="row">Bangalore</th>
      <td>India</td>
      <td>13</td>
      <td>Arun Chadda</td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 1</dt-button></td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 2</dt-button></td>
    </tr>
    <tr class="h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-c-pointer" tabindex="-1">
      <th scope="row">San Francisco, CA</th>
      <td>United States</td>
      <td>108</td>
      <td>Shane Holmes</td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 1</dt-button></td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 2</dt-button></td>
    </tr>
    <tr class="h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-c-pointer" tabindex="-1">
      <th scope="row">Vancouver, BC</th>
      <td>Canada</td>
      <td>76</td>
      <td>Kendal Lewis</td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 1</dt-button></td>
      <td class="d-ta-right"><dt-button kind="muted" importance="outlined" size="200">Button 2</dt-button></td>
    </tr>
  </tbody>
</table>

Inbox

<dt-stack role="list" v-dt-focusgroup="'vertical'" aria-label="Contacts">
  <dt-stack role="listitem" tabindex="0" gap="100" class="d-p-100 d-w-800 h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-bar8">
    <dt-stack direction="row" gap="100" class="d-w100p">
      <dt-avatar full-name="Ashanti Trevor" />
      <dt-stack class="d-fl1">
        <dt-text kind="body" :size="200" strength="bold">Ashanti Trevor</dt-text>
        <dt-stack direction="row" gap="50">
          <dt-stack direction="row" gap="100">
            <dt-icon name="phone-outgoing" size="200" class="d-fc-tertiary" />
            <dt-text kind="body" :size="100" tone="tertiary">Outgoing call</dt-text>
          </dt-stack>
          <dt-text kind="body" :size="100" tone="tertiary">&bull;</dt-text>
          <dt-text kind="body" :size="100" tone="tertiary">2 minutes 10 seconds</dt-text>
        </dt-stack>
      </dt-stack>
      <dt-text kind="body" :size="200" tone="tertiary" numeric>3:23 pm</dt-text>
      <dt-badge kind="count" type="bulletin" text="6" />
    </dt-stack>
  </dt-stack>
  <dt-stack role="listitem" tabindex="0" gap="100" class="d-p-100 d-w-800 h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-bar8">
    <dt-stack direction="row" gap="100" class="d-w100p">
      <dt-avatar full-name="Marcus Chen" />
      <dt-stack class="d-fl1">
        <dt-text kind="body" :size="200" strength="bold">Marcus Chen</dt-text>
        <dt-stack direction="row" gap="50">
          <dt-stack direction="row" gap="100">
            <dt-icon name="phone-incoming" size="200" class="d-fc-tertiary" />
            <dt-text kind="body" :size="100" tone="tertiary">Incoming call</dt-text>
          </dt-stack>
          <dt-text kind="body" :size="100" tone="tertiary">&bull;</dt-text>
          <dt-text kind="body" :size="100" tone="tertiary">14 minutes 32 seconds</dt-text>
        </dt-stack>
      </dt-stack>
      <dt-text kind="body" :size="200" tone="tertiary" numeric>1:47 pm</dt-text>
    </dt-stack>
  </dt-stack>
  <dt-stack role="listitem" tabindex="0" gap="100" class="d-p-100 d-w-800 h:d-bgc-moderate-opaque fv:d-bgc-moderate-opaque d-bar8">
    <dt-stack direction="row" gap="100" class="d-w100p">
      <dt-avatar full-name="Priya Sharma" />
      <dt-stack class="d-fl1">
        <dt-text kind="body" :size="200" strength="bold">Priya Sharma</dt-text>
        <dt-stack direction="row" gap="50">
          <dt-stack direction="row" gap="100">
            <dt-icon name="phone-missed" size="200" class="d-fc-critical" />
            <dt-text kind="body" :size="100" tone="tertiary">Missed call</dt-text>
          </dt-stack>
          <dt-text kind="body" :size="100" tone="tertiary">&bull;</dt-text>
          <dt-text kind="body" :size="100" tone="tertiary">0 seconds</dt-text>
        </dt-stack>
      </dt-stack>
      <dt-text kind="body" :size="200" tone="tertiary" numeric>11:05 am</dt-text>
      <dt-badge kind="count" type="bulletin" text="3" />
    </dt-stack>
  </dt-stack>
</dt-stack>

Contact List, with custom selector

<dt-stack role="list" v-dt-focusgroup="{ axis: 'vertical', loop: false, selector: '[data-custom-attribute-name]' }" aria-label="Contacts" class="d-w-400">
  <dt-hovercard placement="right">
    <template #anchor>
      <dt-recipe-contact-row data-custom-attribute-name role="listitem" name="Ashanti Trevor" avatar-presence="active" user-status="Good morning!" has-call-button />
    </template>
    <template #content>
      <ExampleProfileCard />
    </template>
  </dt-hovercard>
  <dt-hovercard placement="right">
    <template #anchor>
      <dt-recipe-contact-row data-custom-attribute-name role="listitem" name="Marcus Chen" avatar-presence="away" presence-text="Away" user-status="Out for a bit" has-call-button />
    </template>
    <template #content>
      <ExampleProfileCard />
    </template>
  </dt-hovercard>
  <dt-hovercard placement="right">
    <template #anchor>
      <dt-recipe-contact-row data-custom-attribute-name role="listitem" name="Priya Sharma" avatar-presence="busy" presence-text="In a meeting" user-status="Meetings all day" has-call-button />
    </template>
    <template #content>
      <ExampleProfileCard />
    </template>
  </dt-hovercard>
  <dt-hovercard placement="right">
    <template #anchor>
      <dt-recipe-contact-row data-custom-attribute-name role="listitem" name="Jordan Kim" unread-count="3" :has-unreads="true" has-call-button />
    </template>
    <template #content>
      <ExampleProfileCard />
    </template>
  </dt-hovercard>
</dt-stack>
Disabled Button Not just a matter of applying opacity to whole button, but w/ combination of `color-mix()` and tweaking existing DtButton css variables via `oklch()` of specific properties – separate opacity and saturation for border, bgc, fc, etc. Disabled Place Call Place Call Place Call Place Call Place Call Place Call Place Call Place Call Place Call Place Call Place Call Button: Leading/Trailing Freeform elements that are rendered before/after the button content. Leading Trailing Start Icon End Icon `labelClass` Remove leading/trailing class Highlight leading/trailing Place Call Place Call Place Call Place Call Place Call Sizing update: Button/Input/Select Button Button Button Button Button Input / Select `labelClass` Description Messages `messagesClass` `descriptionClass` Tabs Just straight up refactor to use DtButton instead of custom markup/style. Use mix of DtButton variants depending on `active`. Uses all DtButton sizes (currently at least). Borderless Outlined Muted Start Icon End Icon Leading Trailing Select on focus `labelClass` Backwards-compatible old tabs html

First tab

Second tab

Third tab

Argentina United States United Kingdom India Canada
The Argentina stretches from subtropical forests in the north to glacial landscapes in the south, encompassing the towering Andes mountains and the vast Pampas grasslands in between. Its cities blend European architectural influences with a vibrant local character, while rural traditions of horsemanship and cattle ranching continue to shape the national identity. The country is celebrated for its contributions to tango, wine production, and a culinary culture built around shared meals and regional flavors. The United States spans a broad continental range, from Atlantic coastlines and Appalachian ridges to Great Plains, Rocky Mountain summits, and Pacific shores beyond. Major metropolitan areas serve as centers for finance, technology, and the arts, while smaller communities maintain distinct regional customs, dialects, and culinary traditions. The nation's history of immigration has produced a diverse cultural fabric, with influences from virtually every corner of the globe woven into daily life. The United Kingdom comprises England, Scotland, Wales, and Northern Ireland, each with distinct landscapes ranging from chalk cliffs and moors to highland lochs and green valleys. Its cities layer centuries of history alongside modern architecture, with institutions in education, finance, and governance that have influenced systems around the world. A strong tradition in literature, theater, and music continues to thrive, supported by public institutions and a widespread culture of creative expression. The India extends from the Himalayan ranges in the north through fertile river plains to tropical coastlines in the south, supporting an extraordinary range of ecosystems and climates. Hundreds of languages and traditions coexist across its states and territories, producing one of the most culturally varied societies on earth with deep historical roots. A growing technology sector and expanding urban centers complement longstanding agricultural and artisan economies that continue to sustain millions of people. The Canada stretches from the Atlantic to the Pacific and northward into the Arctic, encompassing boreal forests, prairies, mountain ranges, and thousands of lakes and waterways. Its cities are known for cultural diversity and livability, while vast rural and wilderness areas support forestry, mining, and agriculture across multiple climate zones. Official bilingualism in English and French reflects a history shaped by Indigenous peoples, European settlement, and ongoing immigration from around the world.
Argentina United States United Kingdom England, Scotland, Wales, Northern Ireland India Canada
The Argentina stretches from subtropical forests in the north to glacial landscapes in the south, encompassing the towering Andes mountains and the vast Pampas grasslands in between. Its cities blend European architectural influences with a vibrant local character, while rural traditions of horsemanship and cattle ranching continue to shape the national identity. The country is celebrated for its contributions to tango, wine production, and a culinary culture built around shared meals and regional flavors. The United States spans a broad continental range, from Atlantic coastlines and Appalachian ridges to Great Plains, Rocky Mountain summits, and Pacific shores beyond. Major metropolitan areas serve as centers for finance, technology, and the arts, while smaller communities maintain distinct regional customs, dialects, and culinary traditions. The nation's history of immigration has produced a diverse cultural fabric, with influences from virtually every corner of the globe woven into daily life. The United Kingdom comprises England, Scotland, Wales, and Northern Ireland, each with distinct landscapes ranging from chalk cliffs and moors to highland lochs and green valleys. Its cities layer centuries of history alongside modern architecture, with institutions in education, finance, and governance that have influenced systems around the world. A strong tradition in literature, theater, and music continues to thrive, supported by public institutions and a widespread culture of creative expression. The India extends from the Himalayan ranges in the north through fertile river plains to tropical coastlines in the south, supporting an extraordinary range of ecosystems and climates. Hundreds of languages and traditions coexist across its states and territories, producing one of the most culturally varied societies on earth with deep historical roots. A growing technology sector and expanding urban centers complement longstanding agricultural and artisan economies that continue to sustain millions of people. A growing technology sector and expanding urban centers complement longstanding agricultural and artisan economies that continue to sustain millions of people. A growing technology sector and expanding urban centers complement longstanding agricultural and artisan economies that continue to sustain millions of people. Canada stretches from the Atlantic to the Pacific and northward into the Arctic, encompassing boreal forests, prairies, mountain ranges, and thousands of lakes and waterways. Its cities are known for cultural diversity and livability, while vast rural and wilderness areas support forestry, mining, and agriculture across multiple climate zones. Official bilingualism in English and French reflects a history shaped by Indigenous peoples, European settlement, and ongoing immigration from around the world.
Notice / Banner / Toast Updated typography sizing and intelligent icon alignment. Icon margin adjusts based on content layout: title-only, message-only, or title+message. Notice
Default Action completed successfully. Please review before proceeding. Something went wrong. Please try again. A neutral notice for general information. Important Visually prominent variant with filled background. Visually prominent variant with filled background. Visually prominent variant with filled background. Visually prominent variant with filled background. Alignment per internal parts Message only — icon aligns to center when there is a single line of content. When both title and message are present, the icon aligns to the top of the content stack.
Banner Banners are more prominent than notices. Action completed successfully. Please review before proceeding. Something went wrong. Important Banners are more prominent than notices. Action completed successfully. Please review before proceeding. Something went wrong. Toast
Default Important Alignment per internal parts
Radio / Checkbox Description Disabled `labelClass` Messages `messagesClass` `descriptionClass` Checkbox Radio Traveling Indicator Stress Test 1. All four variants Default (::after underline) Outlined Muted + Outlined Muted Active (background) 2. All sizes (width variance stress) Size 100 (xs) Size 200 (sm) Size 300 (md) — default Size 400 (lg) Size 500 (xl) 3. Spread modes (indicator width morphing) spread="none" (default) spread="grow" spread="equal" 4. Vertical orientation Default vertical Outlined vertical Muted vertical 5. Borderless Borderless default Borderless outlined 6. Auto activation mode (arrow keys should NOT animate) Auto mode — click should animate, arrows should snap Auto mode + outlined 7. Disabled (should do nothing) Whole group disabled 8. showIndicatorTransition=false (animation suppressed) Should switch instantly, no slide First Second Third Panel 1 Panel 2 Panel 3 9. Multiple instances on same page (no conflicts) Click tabs in one group while another is mid-animation. They should not interfere. Group A Group B Group C 10. Extreme tab width differences (scale morphing stress) Short vs very long labels — watch the scale animation Medium label Extremely long tab label for stressy stressful testing Shrt Panel A Panel Long Panel B Medium label Extremely long tab label for stressy stressful testing Shrt Panel A Panel Long Panel B 11. Many tabs (potential wrapping) Does the animation break when tabs wrap to a second row? Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliet Panel Alpha Panel Bravo Panel Charlie Panel Delta Panel Echo Panel Foxtrot Panel Golf Panel Hotel Panel India Panel Juliet 12. RTL direction Does the indicator slide the correct direction in RTL?
13. Inverted 14. Rapid click test Click tabs as fast as possible — animation should cancel cleanly, no stuck states 15. Combined extremes Outlined + spread=equal + size 500 Muted + vertical + borderless Muted + outlined + spread=grow + size 100