fix(tabs): leave the vertical pan to the page

touch-pan-x on the tab strip refused a vertical pan that began on it, so
a swipe up or down over a tab strip scrolled nothing. The strip already
pans sideways through its own overflow.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 18:23:49 +02:00
1 parent 1f4c1bd2f0
commit 22976337cb
2 files changed
+25 -3

No files matched your search

+17
View File
@@ -30,4 +30,21 @@ describe("<TabsList>", () => {
// Horizontal scroll behaviour is still intact for long strips.
expect(list![0]).toMatch(/\boverflow-x-auto\b/);
});
it("leaves the vertical pan to the page", () => {
// `touch-pan-x` (or `touch-none`) on the strip refuses a vertical pan
// that starts on it, so a swipe up or down over a tab strip scrolled
// nothing. The strip pans sideways through its own overflow.
const html = renderToStaticMarkup(
<Tabs defaultValue="a">
<TabsList>
<TabsTrigger value="a">A</TabsTrigger>
</TabsList>
</Tabs>,
);
const list = html.match(/<div\b[^>]*data-slot="tabs-list"[^>]*>/)![0];
expect(list).not.toMatch(
/\btouch-(?:pan-x|pan-left|pan-right|none|pinch-zoom)\b/,
);
});
});
+8 -3
View File
@@ -29,8 +29,8 @@ function Tabs({
// mobile when more than ~3 tabs render at typical label widths
// (`/insights`, `/admin`, `/settings/*`). Tab labels are i18n-driven
// so we cannot truncate them deterministically. Instead, the list
// becomes horizontally scrollable on mobile via `overflow-x-auto`
// plus `touch-pan-x` for the iOS PWA, and the wrapping tabs preserve
// becomes horizontally scrollable on mobile via `overflow-x-auto`, and the
// wrapping tabs preserve
// `whitespace-nowrap` so labels never word-wrap mid-tab. Desktop
// looks identical because the inline-flex shrink-wraps inside the
// wider container.
@@ -47,10 +47,15 @@ function Tabs({
// Padding sits at `p-1` (4 px) so the 3 px focus-visible ring
// (`focus-visible:ring-[3px]`) on triggers stays inside the
// `overflow-y-hidden` clamp without clipping the bottom of the ring.
// No `touch-action` on the strip: `overflow-x-auto` already pans it sideways
// under a finger, and the `touch-pan-x` it once carried also refused the
// vertical pan, so a swipe up or down that began on a tab strip scrolled
// nothing at all (on a tablet the strip is 52 px of the page under the 44 px
// touch floor).
// Under a coarse pointer the strip sizes to its triggers, which take the
// 44 px touch floor; a mouse keeps the compact 36 px strip.
const tabsListVariants = cva(
"rounded-lg p-1 group-data-[orientation=horizontal]/tabs:h-9 pointer-coarse:group-data-[orientation=horizontal]/tabs:h-auto data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit max-w-full items-center justify-center overflow-x-auto overflow-y-hidden touch-pan-x scrollbar-thin group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col group-data-[orientation=vertical]/tabs:overflow-x-visible group-data-[orientation=vertical]/tabs:overflow-y-visible",
"rounded-lg p-1 group-data-[orientation=horizontal]/tabs:h-9 pointer-coarse:group-data-[orientation=horizontal]/tabs:h-auto data-[variant=line]:rounded-none group/tabs-list text-muted-foreground inline-flex w-fit max-w-full items-center justify-center overflow-x-auto overflow-y-hidden scrollbar-thin group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col group-data-[orientation=vertical]/tabs:overflow-x-visible group-data-[orientation=vertical]/tabs:overflow-y-visible",
{
variants: {
variant: {