@codecademy/gamut
72.0.272.0.3-alpha.67eeac.0
agent-tools/skills/gamut-datatable/SKILL.md+
agent-tools/skills/gamut-datatable/SKILL.mdNew file+258
Index: package/agent-tools/skills/gamut-datatable/SKILL.md
===================================================================
--- package/agent-tools/skills/gamut-datatable/SKILL.md
+++ package/agent-tools/skills/gamut-datatable/SKILL.md
@@ -0,0 +1,258 @@
+---
+name: gamut-datatable
+description: Use this skill when building a DataTable for bulk data analysis, comparison, sorting, or filtering — including column configuration, the useLocalQuery hook, row action menus, empty/loading states, and color mode. Do not use for item management with row expansion or selection (see gamut-datalist), or for small static lists (see gamut-list).
+---
+
+# Gamut DataTable
+
+Structured, query-capable table for bulk data analysis and comparison. Sorting, filtering, loading, and empty states are built in. Use when the goal is to scan and compare information across rows — not to manage individual items.
+
+Source: `@codecademy/gamut` — [DataTable.tsx](https://github.com/Codecademy/gamut/blob/main/packages/gamut/src/DataList/DataTable.tsx)
+
+See also: [`gamut-datalist`](../gamut-datalist/SKILL.md) — item-focused list with expansion and selection. [`gamut-list`](../gamut-list/SKILL.md) — lower-level list primitives for fully custom layouts. [`gamut-accessibility`](../gamut-accessibility/SKILL.md) — ARIA and keyboard interaction. [`gamut-color-mode`](../gamut-color-mode/SKILL.md) — dark/light mode with `Background`.
+
+Storybook: [Organisms / Lists & Tables / DataTable](https://gamut.codecademy.com/?path=/docs-organisms-lists-tables-datatable--docs)
+
+## Components
+
+```tsx
+import { DataTable } from '@codecademy/gamut';
+import { useLocalQuery } from '@codecademy/gamut';
+```
+
+| Symbol | Role |
+| --------------- | ----------------------------------------------------------------------------------------- |
+| `DataTable` | Root component; always renders as a `table`-variant `List` with scrolling enabled |
+| `useLocalQuery` | Client-side hook for sort/filter state; spread its return value directly into `DataTable` |
+
+## When to use DataTable
+
+- Displaying data that users need to **compare across rows** (metrics, scores, statuses, dates).
+- When the data set needs **sorting, filtering, or both** and that logic lives client-side.
+- Dashboards, admin tables, reports, and data-dense views.
+- When rows are **not individually selectable or expandable** — use `DataList` if you need those.
+
+**Do not use DataTable when:**
+
+- Users engage with items individually (expand for details, select for bulk actions) → use [`DataList`](../gamut-datalist/SKILL.md).
+- The list is small, static, and needs fully custom row layouts → use [`List`](../gamut-list/SKILL.md) directly.
+- There is no data at all and you just need a layout container.
+
+## Design principles
+
+- **Prioritize comparison**: arrange columns to encourage scanning and finding patterns, not storytelling.
+- **Surface secondary info on drill-down**: use Coachmarks, Tooltips, Modals, or Flyovers rather than cramming detail into table cells.
+- **Avoid information overload**: determine what belongs en-masse vs. what should live on a detail surface. Order columns by priority; collapse lower-priority columns at smaller sizes.
+- **Use cell-level interactions carefully**: anchors and links for navigation; popovers for in-context actions.
+
+## Props
+
+| Prop | Type | Default | Notes |
+| ----------------------- | ------------------------- | ------------------- | ------------------------------------------------------------------- |
+| `id` | `string` | required | Unique ID for the table |
+| `idKey` | `keyof Row` | required | Row identifier — must be a `string \| number` field |
+| `rows` | `Row[]` | required | Data array |
+| `columns` | `ColumnConfig<Row>[]` | required | Column definitions |
+| `query` | `Query<Row>` | — | Current sort/filter state; use `useLocalQuery` or manage externally |
+| `onQueryChange` | `OnQueryChange<Row>` | — | Called when sort or filter changes |
+| `loading` | `boolean` | `false` | Replaces row content with shimmer placeholders |
+| `spacing` | `'condensed' \| 'normal'` | `'condensed'` | Row padding |
+| `scrollable` | `boolean` | `true` | Enables horizontal scroll with sticky first column |
+| `shadow` | `boolean` | `false` | Shows a right-side scroll-indicator shadow |
+| `height` | `string` | `'100%'` | Container height when `scrollable` is true |
+| `minHeight` | `number` | `0` | Minimum container height |
+| `emptyMessage` | `ReactNode` | default empty state | Rendered when `rows` is empty |
+| `showOverflow` | `boolean` | — | Shows overflow indicators in cells |
+| `disableContainerQuery` | `boolean` | `false` | Falls back to media queries instead of container queries |
+| `scrollToTopOnUpdate` | `boolean` | `false` | Scrolls to top when rows change |
+| `wrapperWidth` | `string` | — | Custom wrapper width override |
+
+## ColumnConfig
+
+| Field | Type | Notes |
+| ---------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
+| `key` | `keyof Row` | Required; maps to a row data field |
+| `header` | `string` | Column header label |
+| `type` | `'header' \| 'control'` | `'header'` makes the column sticky when scrollable; `'control'` right-aligns and removes padding for action buttons |
+| `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | Column width; omit to fit content |
+| `fill` | `boolean` | Expands the column to fill remaining width |
+| `justify` | `'left' \| 'right'` | Cell alignment |
+| `sortable` | `boolean` | Adds a sort toggle to the column header |
+| `filters` | `string[]` | Adds a filter dropdown with these string values |
+| `options` | `Array<string \| { text: string; value: string }>` | Alternative to `filters` when display text differs from value |
+| `render` | `(row: Row) => ReactElement \| null` | Custom cell renderer |
+
+## Basic usage
+
+```tsx
+import { DataTable, useLocalQuery } from '@codecademy/gamut';
+
+const columns = [
+ { key: 'name', header: 'Name', size: 'md', sortable: true },
+ {
+ key: 'role',
+ header: 'Role',
+ size: 'md',
+ filters: ['Engineer', 'Design', 'Product'],
+ },
+ {
+ key: 'score',
+ header: 'Score',
+ size: 'sm',
+ sortable: true,
+ justify: 'right',
+ },
+];
+
+const MyTable = ({ data }) => {
+ const queryData = useLocalQuery({ idKey: 'id', rows: data, columns });
+
+ return (
+ <DataTable id="my-table" idKey="id" columns={columns} {...queryData} />
+ );
+};
+```
+
+## Custom cell render
+
+Use `render` for cells that need non-text content (status badges, progress bars, action buttons).
+
+```tsx
+const columns = [
+ { key: 'name', header: 'Name', size: 'md', type: 'header' },
+ {
+ key: 'status',
+ header: 'Status',
+ size: 'sm',
+ render: (row) => (
+ <Badge variant={row.status === 'active' ? 'green' : 'red'}>
+ {row.status}
+ </Badge>
+ ),
+ },
+];
+```
+
+## Row action menus
+
+Use a `type: 'control'` column with `PopoverContainer` and `Menu` for row-level actions.
+
+```tsx
+import {
+ DataTable,
+ IconButton,
+ Menu,
+ MenuItem,
+ Modal,
+ PopoverContainer,
+} from '@codecademy/gamut';
+import { MiniKebabMenuIcon } from '@codecademy/gamut-icons';
+import { useRef, useState } from 'react';
+
+const RowActions = ({ rowId }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const ref = useRef(null);
+
+ return (
+ <Box display="inline-block" p={8} ref={ref}>
+ <IconButton
+ icon={MiniKebabMenuIcon}
+ tip="Row actions"
+ variant="secondary"
+ onClick={() => setIsOpen(!isOpen)}
+ />
+ <PopoverContainer
+ alignment="bottom-left"
+ allowPageInteraction
+ closeOnViewportExit
+ isOpen={isOpen}
+ offset={0}
+ targetRef={ref}
+ onRequestClose={() => setIsOpen(false)}
+ >
+ <Menu borderRadius="md" spacing="normal" variant="popover">
+ <MenuItem onClick={() => setIsOpen(false)}>Edit</MenuItem>
+ <MenuItem
+ onClick={() => {
+ setIsOpen(false);
+ setIsModalOpen(true);
+ }}
+ >
+ View details
+ </MenuItem>
+ </Menu>
+ </PopoverContainer>
+ <Modal
+ isOpen={isModalOpen}
+ size="small"
+ title="Details"
+ onRequestClose={() => setIsModalOpen(false)}
+ >
+ {/* detail content */}
+ </Modal>
+ </Box>
+ );
+};
+
+const columns = [
+ { key: 'name', header: 'Name', size: 'md', type: 'header' },
+ {
+ key: 'id',
+ header: '',
+ size: 'sm',
+ type: 'control',
+ justify: 'right',
+ render: (row) => <RowActions rowId={row.id} />,
+ },
+];
+```
+
+Key points:
+
+- `closeOnViewportExit` on `PopoverContainer` closes the menu when its row scrolls out of view.
+- `allowPageInteraction` lets users interact with the table while the menu is open.
+- Modals opened from menu items render at `zIndex={3}` by default, above the table header.
+
+## Scrollable table
+
+`scrollable` defaults to `true` on DataTable. The first `type: 'header'` column sticks to the left. Add `shadow` for a visual overflow indicator.
+
+```tsx
+<DataTable id="wide-table" idKey="id" columns={columns} rows={rows} shadow />
+```
+
+## Empty state
+
+DataTable shows a default empty state when `rows` is empty. Override with `emptyMessage`. For the `tbody > tr > th` layout pattern required for valid table semantics, see [`gamut-datalist` — Empty state](../gamut-datalist/SKILL.md#empty-state).
+
+## Loading state
+
+Pass `loading` to replace row content with shimmer placeholders while data fetches.
+
+```tsx
+<DataTable id="my-table" idKey="id" columns={columns} rows={[]} loading />
+```
+
+## Color mode
+
+DataTable inherits background color from the `current-background` token. Wrap in `Background` from `@codecademy/gamut-styles` to apply a surface color and automatically switch to dark mode contrast.
+
+```tsx
+import { Background } from '@codecademy/gamut-styles';
+
+<Background bg="black" p={8}>
+ <DataTable id="dark-table" idKey="id" rows={data} columns={columns} />
+</Background>;
+```
+
+## Container queries
+
+DataTable uses CSS container queries by default for responsive column stacking. Pass `disableContainerQuery` when the table lives in a constrained container or you are managing your own responsive logic — same pattern as [`gamut-datalist`](../gamut-datalist/SKILL.md#container-queries).
+
+## Accessibility
+
+- DataTable renders as a semantic `<table>` element automatically.
+- Sort controls receive `aria-sort` automatically when `sortable` is set on a column.
+- Filter controls are keyboard-accessible via the column header dropdowns.
+- For custom `emptyMessage`, use `tbody > tr > th` structure for valid table semantics (see empty state example above).