From 5143289767806af7c321ba8e3b3cf545f4f93b0f Mon Sep 17 00:00:00 2001 From: Tucker McCoy Date: Wed, 15 Jul 2026 11:36:51 -0400 Subject: [PATCH] feat: forward optional per-item triggerProps onto Tabs triggers --- packages/demo/src/content/components/tabs.mdx | 47 ++++++++++++++++--- packages/ui/package.json | 2 +- packages/ui/src/components/tabs/tabs.tsx | 10 ++-- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/packages/demo/src/content/components/tabs.mdx b/packages/demo/src/content/components/tabs.mdx index 930e7861..0d04ce9d 100644 --- a/packages/demo/src/content/components/tabs.mdx +++ b/packages/demo/src/content/components/tabs.mdx @@ -205,6 +205,38 @@ Use the `suffix` property to render any React component after a tab's label. Thi /> ``` +### Forwarding trigger props + +Use the `triggerProps` field on an item to forward props onto that individual tab's trigger. This is useful for attaching event handlers to a specific tab — for example, warming a destination's data on hover or focus (prefetching) when the user shows intent toward that tab. + +Any props are spread onto the trigger before the component's own `value` and styling, so tab selection and internal classes are never overridden, and a custom `className` is merged with the internal ones. Event handlers compose with the component's built-in behavior, so tab activation and keyboard navigation are unchanged. + +```tsx +Declarations Content, + triggerProps: { + onPointerEnter: () => prefetchDeclarations(), + onFocus: () => prefetchDeclarations(), + }, + }, + { + label: "Reviews", + value: "reviews", + content:
Reviews Content
, + triggerProps: { + onPointerEnter: () => prefetchReviews(), + onFocus: () => prefetchReviews(), + }, + }, + ]} +/> +``` + ## Props --- @@ -221,10 +253,11 @@ Use the `suffix` property to render any React component after a tab's label. Thi Each item in the `items` array should have the following structure: -| Name | Description | Type | Default | Required | -| --------- | ------------------------------------------------------------ | ------------------------------ | ------- | -------- | -| `label` | Text label displayed on the tab trigger | `string` | - | ✅ | -| `value` | Unique value that identifies this tab | `string` | - | ✅ | -| `icon` | Icon to display alongside the label (Lucide name or element) | `React.ReactElement`, `string` | - | ❌ | -| `suffix` | Content rendered after the label (e.g. a badge or count) | `React.ReactNode` | - | ❌ | -| `content` | Content to display when this tab is active | `React.ReactNode` | - | ✅ | +| Name | Description | Type | Default | Required | +| -------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------- | -------- | +| `label` | Text label displayed on the tab trigger | `string` | - | ✅ | +| `value` | Unique value that identifies this tab | `string` | - | ✅ | +| `icon` | Icon to display alongside the label (Lucide name or element) | `React.ReactElement`, `string` | - | ❌ | +| `suffix` | Content rendered after the label (e.g. a badge or count) | `React.ReactNode` | - | ❌ | +| `content` | Content to display when this tab is active | `React.ReactNode` | - | ✅ | +| `triggerProps` | Props forwarded onto this tab's trigger (see [Forwarding trigger props](#forwarding-trigger-props)) | `Omit, "value">` | - | ❌ | diff --git a/packages/ui/package.json b/packages/ui/package.json index de5cde39..0e1d0602 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,7 +2,7 @@ "name": "@eqtylab/equality", "description": "EQTYLab's component and token-based design system", "homepage": "https://equality.eqtylab.io/", - "version": "2.3.1", + "version": "2.3.2", "license": "Apache-2.0", "keywords": [ "component library", diff --git a/packages/ui/src/components/tabs/tabs.tsx b/packages/ui/src/components/tabs/tabs.tsx index ec1ae724..9b45b2a0 100644 --- a/packages/ui/src/components/tabs/tabs.tsx +++ b/packages/ui/src/components/tabs/tabs.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, type ComponentPropsWithoutRef } from 'react'; import { motion } from 'motion/react'; import { Icon } from '@/components/icon/icon'; @@ -19,6 +19,7 @@ interface TabsProps { icon?: React.ReactElement | string; suffix?: React.ReactNode; content: React.ReactNode; + triggerProps?: Omit, 'value'>; }[]; className?: string; tabsListBackground?: 'transparent' | 'filled'; @@ -80,18 +81,19 @@ const Tabs = ({ isFilled ? styles['tabs-list--filled'] : styles['tabs-list--transparent'] )} > - {items.map(({ label, value, icon, suffix }) => { + {items.map(({ label, value, icon, suffix, triggerProps }) => { const isActive = activeTab === value; return ( {renderIcon(icon)} {label}