Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions packages/demo/src/components/demo/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -483,11 +483,7 @@ export const TableDemo = ({
<IconButton name="EllipsisVertical" label="Row actions" />
</TableCell>
</TableRow>
<TableRow
clickable={variant === "clickable"}
data-state={selectedRow === 2 ? "selected" : undefined}
onClick={variant === "clickable" ? () => toggleRow(2) : undefined}
>
<TableRow href="https://eqtylab.io">
<TableCell>Charlie Brown</TableCell>
<TableCell>charlie@example.com</TableCell>
<TableCell>Viewer</TableCell>
Expand Down
15 changes: 11 additions & 4 deletions packages/demo/src/content/components/table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ Basic usage:

### Clickable Rows

Rows accept an `onClick` handler, which enables hover interactions. Use `data-state="selected"` to mark a row as selected.
Rows can be made interactive in two ways:

- **`onClick`** — Pass a click handler for custom behavior (e.g., toggling selection state).
- **`href`** — Pass a URL to make the row navigable on click. This also adds keyboard accessibility (`Enter`/`Space` to activate) and proper ARIA semantics. Add an `hrefLabel` prop to tell screen reader users where the link will go.

Both props enable hover interactions automatically. Use `data-state="selected"` to mark a row as selected.

Try clicking a row below to toggle its selected state!

Expand Down Expand Up @@ -310,6 +315,8 @@ Use [container queries](https://tailwindcss.com/docs/responsive-design#what-are-

### TableRow

| Name | Description | Type | Default | Required |
| ----------- | ------------------------------------------- | --------- | ------- | -------- |
| `clickable` | Applies hover and cursor interaction styles | `boolean` | `false` | ❌ |
| Name | Description | Type | Default | Required |
| ----------- | ----------------------------------------------------------- | --------- | ---------------- | -------- |
| `clickable` | Applies hover and cursor interaction styles | `boolean` | `false` | ❌ |
| `href` | URL to navigate to on click | `string` | — | ❌ |
| `hrefLabel` | Accessible label for the link (announced by screen readers) | `string` | `"View details"` | ❌ |
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@eqtylab/equality",
"description": "EQTYLab's component and token-based design system",
"homepage": "https://equality.eqtylab.io/",
"version": "1.9.0",
"version": "1.9.1",
"license": "Apache-2.0",
"keywords": [
"component library",
Expand Down
26 changes: 25 additions & 1 deletion packages/ui/src/components/table/table-components.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,31 @@
--hover-darken: 50%;
--hover-lighten: 50%;
@apply cursor-pointer;
@apply hover:bg-mixed-light! dark:hover:bg-mixed-dark!;
}

/* Prevent row from highlighting when the user hovers clickable tags in the table */
.table-row--clickable:not([data-state='selected']):hover:not(
:has(button:hover, a:not(.table-row-link):hover)
) {
@apply bg-mixed-light! dark:bg-mixed-dark!;
}

/* Linked Row (stretched link pattern) */

.table-row--linked {
@apply relative;
}

.table-row-link {
@apply absolute inset-0 z-10;
}

.table-row--linked :is(button, a:not(.table-row-link)) {
@apply relative z-20;
}

.table-row--linked:has(.table-row-link:focus-visible) {
@apply outline-brand-primary outline outline-2 outline-offset-[-2px];
}

.table-body .table-row--clickable[data-state='selected'] {
Expand Down
51 changes: 43 additions & 8 deletions packages/ui/src/components/table/table-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,49 @@ TableFooter.displayName = 'TableFooter';

const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement> & { clickable?: boolean }
>(({ className, clickable, ...props }, ref) => (
<tr
ref={ref}
className={cn(styles['table-row'], clickable && styles['table-row--clickable'], className)}
{...props}
/>
));
React.HTMLAttributes<HTMLTableRowElement> & {
clickable?: boolean;
href?: string;
hrefLabel?: string;
}
>(
(
{ className, clickable, href, hrefLabel = 'View details', children, onClick, ...props },
ref
) => {
const isClickable = clickable || !!href;

const handleClick = (e: React.MouseEvent<HTMLTableRowElement>) => {
// Don't trigger row click if an interactive element was clicked
const target = e.target as HTMLElement;
if (target.closest('button, a:not([data-table-row-link])')) {
return;
}
onClick?.(e);
};

return (
<tr
ref={ref}
className={cn(
styles['table-row'],
isClickable && styles['table-row--clickable'],
href && styles['table-row--linked'],
className
)}
onClick={onClick ? handleClick : undefined}
{...props}
>
{href && (
<a href={href} className={styles['table-row-link']} data-table-row-link>
<span className="sr-only">{hrefLabel}</span>
</a>
)}
{children}
</tr>
);
}
);
TableRow.displayName = 'TableRow';

const TableHead = React.forwardRef<
Expand Down
Loading