# gds-table - React

## Import

```typescript
import { GdsTable } from '@sebgroup/green-core/react'
```

## Usage

Use the component as a React JSX element:

```tsx
<GdsTable>
  {/* content */}
</GdsTable>
```

## Event Handling

Events are handled using React event handler props. Event names are converted from kebab-case to camelCase with an "on" prefix:

| Web Component Event | React Handler Prop | Handler Example |
|---------------------|--------------------|-----------------|
| `gds-page-change` | `onGdsPageChange` | `onGdsPageChange={handler}` |
| `gds-rows-change` | `onGdsRowsChange` | `onGdsRowsChange={handler}` |
| `gds-sort-change` | `onGdsSortChange` | `onGdsSortChange={handler}` |
| `gds-table-data-loaded` | `onGdsTableDataLoaded` | `onGdsTableDataLoaded={handler}` |
| `gds-table-data-error` | `onGdsTableDataError` | `onGdsTableDataError={handler}` |
| `gds-table-selection` | `onGdsTableSelection` | `onGdsTableSelection={handler}` |

## Slots in React

Web component slots work as regular React children. To target a named slot, add the `slot` attribute to the child element — no portals or refs needed.

```tsx
<GdsTable headline="Users" columns={columns} data={dataProvider} searchable>
  {/* Named slot: header-lead */}
  <GdsDropdown slot="header-lead" size="small" plain>
    <span slot="trigger">Filter</span>
    <GdsOption value="active">Active</GdsOption>
    <GdsOption value="inactive">Inactive</GdsOption>
  </GdsDropdown>

  {/* Named slot: header-trail */}
  <GdsButton slot="header-trail" rank="secondary" size="small">
    Export
  </GdsButton>

  {/* Named slot: empty */}
  <div slot="empty">No data available</div>
</GdsTable>
```

### Custom cell slot content

Cell slots require three steps: configure slots in the data provider using `Slot()`, listen for loaded data, and render slot content as children.

#### Step 1: Configure slots in the data provider

Import `Slot` from the types file and wrap cell values that need rich content:

```tsx
import { Slot } from '@sebgroup/green-core/components/table/table.types'

// Slot(value, slotIds) — value is used for sorting/search, slotIds define the cell layout
// The special slot ID 'value' renders the plain text value inline

async function dataProvider(request) {
  const data = await fetchData(request)
  return {
    ...data,
    rows: data.rows.map((row) => ({
      ...row,
      name: Slot(row.name, ['avatar', 'value']), // image + text
      email: Slot(row.email, ['value', 'copy-button']), // text + button
      status: Slot(row.status, ['status']), // badge only (no plain text)
    })),
  }
}
```

Cells without `Slot()` render as plain text automatically.

#### Step 2: Listen for data and render slot content

Slot elements use the naming convention `columnKey:rowId:slotId` and are rendered as direct children of `<GdsTable>`:

```tsx
function TableWithSlots() {
  const [rows, setRows] = useState([])

  return (
    <GdsTable columns={columns} data={dataProvider} onGdsTableDataLoaded={(e) => setRows(e.detail.rows)}>
      {rows.map((row) => (
        <React.Fragment key={row.id}>
          {/* name column: avatar slot */}
          <GdsImg slot={`name:${row.id}:avatar`} src={row.avatarUrl} width="xl" height="xl" />

          {/* email column: copy-button slot */}
          <GdsButton slot={`email:${row.id}:copy-button`} rank="tertiary" size="small">
            <IconCopy />
          </GdsButton>

          {/* status column: status slot */}
          <GdsBadge slot={`status:${row.id}:status`} variant={row.status === 'Active' ? 'positive' : 'negative'} size="small">
            {row.status}
          </GdsBadge>
        </React.Fragment>
      ))}
    </GdsTable>
  )
}
```
