# Component/Table/DataTable

> Props: component-table-datatable.props.txt

## Examples


### Auto Reset Row Select Off

autoResetRowSelect=false일 경우 rows 변경시에도 selection 상태가 유지됩니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const [rows, setRows] = useState<DataTableRowsType<DataType>>((args.rows as DataTableRowsType<DataType>) || []);
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => rows, [rows]);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      setSelectedRows(rows);
    };

    return (
      <Stack direction='column' gap={20}>
        <Stack width='100%' gap={12} align='center'>
          <Button kind='primary' onClick={() => setRows(rows === selectionRows ? selectionRows2 : selectionRows)}>
            Rows 데이터 변경
          </Button>
        </Stack>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          autoResetRowSelection={false}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'autoResetRowSelect=false일 경우 rows 변경시에도 selection 상태가 유지됩니다.'
      }
    }
  }
}
```

### Auto Reset Row Select On

autoResetRowSelect=true일 경우 rows 변경시 Row select 상태는 자동 초기화됩니다

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const [rows, setRows] = useState<DataTableRowsType<DataType>>((args.rows as DataTableRowsType<DataType>) || []);
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => rows, [rows]);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      setSelectedRows(rows);
    };

    return (
      <Stack direction='column' gap={20}>
        <Stack width='100%' gap={12} align='center'>
          <Button kind='primary' onClick={() => setRows(rows === selectionRows ? selectionRows2 : selectionRows)}>
            Rows 데이터 변경
          </Button>
        </Stack>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} onSelectRows={handleSelectedRows} />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'autoResetRowSelect=true일 경우 rows 변경시 Row select 상태는 자동 초기화됩니다'
      }
    }
  }
}
```

### Base

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => args.columns as DataTableColumnsType<DataType> ?? columns, []);
    const cachedRows = useMemo(() => args.rows as DataTableRowsType<DataType> ?? rows, []);
    return <Stack direction='column' width='100%' gap={20}>
        <Summary />
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>;
  }
}
```

### Beforehand Expansion By Key

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const [is_selectable, setSelectable] = useState<boolean>(false);
    const [is_fixed, setFixed] = useState<boolean>(false);
    const [isNoData, setNoData] = useState<boolean>(false);
    const [expanded, setExpanded] = useState<Key[] | undefined>(args.rowExpansion as Key[]);

    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);
    const tableRows = useMemo(() => (isNoData ? [] : cachedRows), [cachedRows, isNoData]);

    const handleExpandRows = (rowKeys: Key[]) => {
      setExpanded(rowKeys);
      args.onExpandRows?.(rowKeys);
    };

    return (
      <Stack direction='column' width={args?.scroll?.x && is_fixed ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        <Stack gap={16}>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>Selectable</BaseText>
            <Switch isOn={is_selectable} offText='OFF' onClick={() => setSelectable(!is_selectable)} onText='ON' />
          </Stack>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>Fixed</BaseText>
            <Switch isOn={is_fixed} offText='OFF' onClick={() => setFixed(!is_fixed)} onText='ON' />
          </Stack>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>No Data</BaseText>
            <Switch isOn={isNoData} offText='OFF' onClick={() => setNoData(!isNoData)} onText='ON' />
          </Stack>
        </Stack>
        <DataTable
          {...args}
          renderExpandedRow={(row, index) => {
            const is_expanded = row.expandable as boolean;
            return is_expanded ? index === 2 ? <ExpandComponent2 /> : <ExpandComponent /> : null;
          }}
          selectableRows={is_selectable}
          columns={is_fixed ? fixedExpandRowCellColumns : cachedColumns}
          rows={tableRows}
          rowExpansion={expanded}
          onExpandRows={handleExpandRows}
        />
        {args.rowExpansion && (
          <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            <BaseText>
              <b>RowExpansion: Array key</b>
            </BaseText>
            <pre>
              <code style={{ fontSize: '12px' }}>
                {JSON.stringify(
                  {
                    rowExpansion: expanded,
                  },
                  null,
                  2,
                )}
              </code>
            </pre>
          </Stack>
        )}
      </Stack>
    );
  },
  args: {
    columns: customRowCellColumns,
    rows: ExpandRows,
    scroll: {
      x: 1200
    },
    rowExpansion: ['fruit-1'],
    onExpandRows: (rowKeys: Key[]) => {
      console.log('Expand Row Keys =>', rowKeys);
    }
  }
}
```

### Beforehand Selection By Function

rowSelection 속성을 함수형태로 전달하여 해당하는 row 선택 상태를 업데이트할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);

    const rowSelectFilter = (row: DataType) => row.amount < 20;
    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          rowSelection={rowSelectFilter}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>RowSelection: function type</b>
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  rowSelectionFunction: '(row: DataType) => row.amount < 20',
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'rowSelection 속성을 함수형태로 전달하여 해당하는 row 선택 상태를 업데이트할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### Beforehand Selection By Key

rowSelection 속성을 row 고유 key값을 배열로 전달하여 row 선택 상태를 업데이트할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} onSelectRows={handleSelectedRows} />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>RowSelection: Array key</b>
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  rowSelectionList: args.rowSelection,
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true,
    rowSelection: ['fruit-1', 'fruit-2', 'fruit-4', 'fruit-5']
  },
  parameters: {
    docs: {
      description: {
        story: 'rowSelection 속성을 row 고유 key값을 배열로 전달하여 row 선택 상태를 업데이트할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### Cell Span

테이블 컬럼 cellAttributes 속성을 통해 특정 테이블 셀 rowSpan, colSpan, hidden 여부를 지정할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: SpanColumns,
    rows: SpanRows,
    cellHoverStyle: false
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼 cellAttributes 속성을 통해 특정 테이블 셀 rowSpan, colSpan, hidden 여부를 지정할 수 있습니다.'
      }
    }
  }
}
```

### Clickable Selection

selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          rowSelection={selectedRowKeys}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRowKeys: selectedRowKeys,
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true,
    clickableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### Column Align

테이블 컬럼별 정렬과 셀단위 정렬을 지정할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customColumns
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼별 정렬과 셀단위 정렬을 지정할 수 있습니다.'
      }
    }
  }
}
```

### Column Fixed

가로스크롤 발생시 테이블 컬럼을 고정할 수 있습니다. 그룹 컬럼일 경우 지원하지 않습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: fixedColumns,
    scroll: {
      x: 1200
    }
  },
  parameters: {
    docs: {
      description: {
        story: '가로스크롤 발생시 테이블 컬럼을 고정할 수 있습니다. 그룹 컬럼일 경우 지원하지 않습니다.'
      }
    }
  }
}
```

### Column Group

테이블 컬럼 그룹을 구성할 수 있습니다. 상위 부모 컬럼은 id 속성이 없습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: groupColumns,
    rows: groupRows
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼 그룹을 구성할 수 있습니다. 상위 부모 컬럼은 id 속성이 없습니다.'
      }
    }
  }
}
```

### Column Width Min Width

테이블 컬럼별 너비, 최소 너비를 지정할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' gap={20}>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
        <Stack direction='column' p={10} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <Stack direction='column' gap={15}>
            <BaseText>
              (참고) scroll.x 또는 resize props 사용시 table-layout은 fixed 처리됩니다.(기본값: auto)
              <br />
              scroll.x 값 사용하는 경우 각 셀의 너비를 명시적으로 모두 지정하는것을 권장합니다.
            </BaseText>

            <BaseText kind='Body_15_SemiBold'>Width</BaseText>
          </Stack>
          <BaseText>
            <ul>
              <li>minWidth 속성없이 사용된다면 자동으로 해당 width값으로 minWidth 처리됩니다.</li>
              <li>
                table-layout: auto 일 경우
                <ul>
                  <li>
                    모든 셀의 너비가 고정된다면 모든 셀의 너비를 더한 값보다 브라우저 크기가 커질때 동적으로 변할 수
                    있습니다.
                  </li>
                  <li>
                    특정 셀의 너비를 필수로 고정해야한다면 모든 셀의 너비를 지정하지 않고 고정할 셀만 너비를 지정하세요.
                    너비를 지정하지 않은 셀들은 가변으로, 지정된 셀은 너비를 보장할 수 있습니다.
                  </li>
                </ul>
              </li>
            </ul>
          </BaseText>
          <BaseText kind='Body_15_SemiBold'>MinWidth</BaseText>
          <BaseText>
            <ul>
              <li>width 속성없이 minWidth만 사용된다면 가변 너비를 가지되 최소 너비를 보장합니다.</li>
              <li>
                width 속성과 함께 사용된다면 해당 width값으로 너비를 기본을 가지되 작아질 경우 minWidth 최소 너비를
                보장합니다.
              </li>
            </ul>
          </BaseText>
        </Stack>
      </Stack>
    );
  },
  args: {
    columns: customWidthColumns
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼별 너비, 최소 너비를 지정할 수 있습니다.'
      }
    }
  }
}
```

### Custom Column Cell

테이블 컬럼별 커스텀을 할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customColumnCell
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼별 커스텀을 할 수 있습니다.'
      }
    }
  }
}
```

### Custom No Data Padding

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    rows: [],
    noDataPadding: 24
  }
}
```

### Custom No Data Text

테이블 데이터가 없을 경우 노출할 텍스트를 커스텀할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    noDataText: <Stack direction='column' gap={16} align='center'>
        <BaseText kind='Body_14_SemiBold' color={semantic_colors.content.tertiary}>
          조회 가능한 신청 목록이 없습니다.
        </BaseText>
        <Button kind='primary'>Label</Button>
      </Stack>,
    rows: []
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 데이터가 없을 경우 노출할 텍스트를 커스텀할 수 있습니다.'
      }
    }
  }
}
```

### Custom Row Cell

테이블 로우별 커스텀을 할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customRowCellColumns,
    rows: rows
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 로우별 커스텀을 할 수 있습니다.'
      }
    }
  }
}
```

### Custom Row Cell Style

테이블 컬럼 cellAttributes 속성을 통해 특정 테이블 셀 스타일을 지정할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customRowStyleColumns,
    cellHoverStyle: false
  },
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼 cellAttributes 속성을 통해 특정 테이블 셀 스타일을 지정할 수 있습니다.'
      }
    }
  }
}
```

### Custom Row Color

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customRowCellColumns,
    rows: selectionRows,
    selectableRows: true,
    rowColor: (row: DataType, index: number) => index % 2 === 0 ? colors.red50 : colors.white
  }
}
```

### Default All Selection

selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>(args.rows);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>(args.rows.map((row) => row.key ?? ''));

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          rowSelection={selectedRowKeys}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRowKeys: selectedRowKeys,
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### Disabled

row에 disabled 속성을 지정할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} onSelectRows={handleSelectedRows} />
        <DescriptionForDisabled />
      </Stack>
    );
  },
  args: {
    columns: comprehensiveCustomColumns,
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'row에 disabled 속성을 지정할 수 있습니다.'
      }
    }
  }
}
```

### Disabled Cell Off

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customRowCellColumns,
    rows: selectionRows,
    selectableRows: true,
    clickableRows: true,
    cellDisabledStyle: false
  }
}
```

### Disabled Sticky Header

세로 스크롤 발생시 테이블 헤더는 자동으로 고정됩니다. false로 지정할 경우 스크롤 발생 여부와 상관없이 해제할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    stickyHeader: false,
    rows: [...rows, ...rows, ...rows, ...rows],
    scroll: {
      y: 300
    }
  },
  parameters: {
    docs: {
      description: {
        story: '세로 스크롤 발생시 테이블 헤더는 자동으로 고정됩니다. false로 지정할 경우 스크롤 발생 여부와 상관없이 해제할 수 있습니다.'
      }
    }
  }
}
```

### Divider Rows

테이블에 각 row의 하단에 구분자를 추가할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: customColumns,
    rows: SpanRows,
    dividerRows: (row, index) => index % 2 === 0
  },
  parameters: {
    docs: {
      description: {
        story: '테이블에 각 row의 하단에 구분자를 추가할 수 있습니다.'
      }
    }
  }
}
```

### Expandable Row

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const [is_selectable, setSelectable] = useState<boolean>(false);
    const [is_fixed, setFixed] = useState<boolean>(false);
    const [isNoData, setNoData] = useState<boolean>(false);
    const [expanded, setExpanded] = useState<Key[] | undefined>(args.rowExpansion as Key[]);

    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);
    const tableRows = useMemo(() => (isNoData ? [] : cachedRows), [cachedRows, isNoData]);

    const handleExpandRows = (rowKeys: Key[]) => {
      setExpanded(rowKeys);
      args.onExpandRows?.(rowKeys);
    };

    return (
      <Stack direction='column' width={args?.scroll?.x && is_fixed ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        <Stack gap={16}>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>Selectable</BaseText>
            <Switch isOn={is_selectable} offText='OFF' onClick={() => setSelectable(!is_selectable)} onText='ON' />
          </Stack>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>Fixed</BaseText>
            <Switch isOn={is_fixed} offText='OFF' onClick={() => setFixed(!is_fixed)} onText='ON' />
          </Stack>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>No Data</BaseText>
            <Switch isOn={isNoData} offText='OFF' onClick={() => setNoData(!isNoData)} onText='ON' />
          </Stack>
        </Stack>
        <DataTable
          {...args}
          renderExpandedRow={(row, index) => {
            const is_expanded = row.expandable as boolean;
            return is_expanded ? index === 2 ? <ExpandComponent2 /> : <ExpandComponent /> : null;
          }}
          selectableRows={is_selectable}
          columns={is_fixed ? fixedExpandRowCellColumns : cachedColumns}
          rows={tableRows}
          rowExpansion={expanded}
          onExpandRows={handleExpandRows}
        />
        {args.rowExpansion && (
          <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            <BaseText>
              <b>RowExpansion: Array key</b>
            </BaseText>
            <pre>
              <code style={{ fontSize: '12px' }}>
                {JSON.stringify(
                  {
                    rowExpansion: expanded,
                  },
                  null,
                  2,
                )}
              </code>
            </pre>
          </Stack>
        )}
      </Stack>
    );
  },
  args: {
    columns: customRowCellColumns,
    rows: ExpandRows,
    scroll: {
      x: 1200
    }
  }
}
```

### Expandable Row No Button

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const [is_selectable, setSelectable] = useState<boolean>(false);
    const [is_fixed, setFixed] = useState<boolean>(false);
    const [isNoData, setNoData] = useState<boolean>(false);
    const [expanded, setExpanded] = useState<Key[] | undefined>(args.rowExpansion as Key[]);

    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);
    const tableRows = useMemo(() => (isNoData ? [] : cachedRows), [cachedRows, isNoData]);

    const handleExpandRows = (rowKeys: Key[]) => {
      setExpanded(rowKeys);
      args.onExpandRows?.(rowKeys);
    };

    return (
      <Stack direction='column' width={args?.scroll?.x && is_fixed ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        <Stack gap={16}>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>Selectable</BaseText>
            <Switch isOn={is_selectable} offText='OFF' onClick={() => setSelectable(!is_selectable)} onText='ON' />
          </Stack>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>Fixed</BaseText>
            <Switch isOn={is_fixed} offText='OFF' onClick={() => setFixed(!is_fixed)} onText='ON' />
          </Stack>
          <Stack gap={4} align='center'>
            <BaseText kind='Body_14_SemiBold'>No Data</BaseText>
            <Switch isOn={isNoData} offText='OFF' onClick={() => setNoData(!isNoData)} onText='ON' />
          </Stack>
        </Stack>
        <DataTable
          {...args}
          renderExpandedRow={(row, index) => {
            const is_expanded = row.expandable as boolean;
            return is_expanded ? index === 2 ? <ExpandComponent2 /> : <ExpandComponent /> : null;
          }}
          selectableRows={is_selectable}
          columns={is_fixed ? fixedExpandRowCellColumns : cachedColumns}
          rows={tableRows}
          rowExpansion={expanded}
          onExpandRows={handleExpandRows}
        />
        {args.rowExpansion && (
          <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            <BaseText>
              <b>RowExpansion: Array key</b>
            </BaseText>
            <pre>
              <code style={{ fontSize: '12px' }}>
                {JSON.stringify(
                  {
                    rowExpansion: expanded,
                  },
                  null,
                  2,
                )}
              </code>
            </pre>
          </Stack>
        )}
      </Stack>
    );
  },
  args: {
    columns: customRowCellColumns,
    rows: ExpandRows,
    expandableButton: false,
    scroll: {
      x: 1200
    }
  }
}
```

### Loading

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    loading: true
  }
}
```

### No Data

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    rows: []
  }
}
```

### Partial Disabled Cell Off Table

전체 row가 disabled 상태일 때, cellAttributes에서 disabled: false를 반환하면 해당 셀만 활성화됩니다. 이를 통해 row 레벨의 disabled 스타일(opacity 등)이 적용되지 않고, 해당 셀만 정상적으로 동작합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: [...columns, {
      text: 'disabled 해지',
      id: 'id',
      cellAttributes: () => {
        return {
          disabled: false
        };
      }
    }],
    rows: rows.map(row => ({
      ...row,
      disabled: true
    }))
  },
  parameters: {
    docs: {
      description: {
        story: '전체 row가 disabled 상태일 때, cellAttributes에서 disabled: false를 반환하면 해당 셀만 활성화됩니다. 이를 통해 row 레벨의 disabled 스타일(opacity 등)이 적용되지 않고, 해당 셀만 정상적으로 동작합니다.'
      }
    }
  }
}
```

### Partial Disabled Cell On Table

전체 row가 활성화 상태일 때, cellAttributes에서 disabled: true를 반환하면 해당 셀만 비활성화됩니다. 이 경우 disabled 스타일(opacity 등)이 해당 셀에만 적용됩니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: [...columns, {
      text: '부분 disabled',
      id: 'id',
      cellAttributes: () => {
        return {
          disabled: true
        };
      }
    }],
    rows: rows.map(row => ({
      ...row
    }))
  },
  parameters: {
    docs: {
      description: {
        story: '전체 row가 활성화 상태일 때, cellAttributes에서 disabled: true를 반환하면 해당 셀만 비활성화됩니다. 이 경우 disabled 스타일(opacity 등)이 해당 셀에만 적용됩니다.'
      }
    }
  }
}
```

### Resize

width값 지정된 컬럼은 resize가 불가합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    resize: true
  },
  parameters: {
    docs: {
      description: {
        story: 'width값 지정된 컬럼은 resize가 불가합니다.'
      }
    }
  }
}
```

### Scroll

가로, 세로 스크롤 영역 너비/높이를 지정할 수 있습니다

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(
      () => (args.rows as DataTableRowsType<DataType>) ?? [...rows, ...rows, ...rows, ...rows],
      [],
    );

    return (
      <Stack direction='column' gap={20}>
        <BaseText kind='Body_15_SemiBold'>기본</BaseText>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
        <BaseText kind='Body_15_SemiBold'>Scroll y : 250</BaseText>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} scroll={{ y: 250 }} />
        <BaseText kind='Body_15_SemiBold'>Scroll y : 1000</BaseText>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} scroll={{ y: 1000 }} />
      </Stack>
    );
  },
  args: {
    scroll: {
      x: 1200
    }
  },
  parameters: {
    docs: {
      description: {
        story: '가로, 세로 스크롤 영역 너비/높이를 지정할 수 있습니다'
      }
    }
  }
}
```

### Selection

selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          rowSelection={selectedRowKeys}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRowKeys: selectedRowKeys,
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### Selection Cell Span

selectableRows의 cellAttributes 속성을 통해 체크박스의 rowSpan, colSpan, hidden 여부를 지정할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          rowSelection={selectedRowKeys}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRowKeys: selectedRowKeys,
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  args: {
    columns: SpanColumns,
    rows: SpanRows,
    cellHoverStyle: false,
    selectableRows: {
      cellAttributes: (row: DataType) => ({
        rowSpan: row.code === 410 ? 2 : 1,
        hidden: row.code === 410 && row.amount === 20
      })
    }
  },
  parameters: {
    docs: {
      description: {
        story: 'selectableRows의 cellAttributes 속성을 통해 체크박스의 rowSpan, colSpan, hidden 여부를 지정할 수 있습니다.'
      }
    }
  }
}
```

### Selection Large Data

selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => args.columns as DataTableColumnsType<DataType> ?? columns, []);
    const cachedRows = useMemo(() => Array.from({
      length: 1000
    }, (_, i) => ({
      ...selectionRows[i % selectionRows.length],
      key: 'fruit-' + i
    })), []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };
    return <Stack direction='column' gap={20}>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} onSelectRows={handleSelectedRows} rowSelection={selectedRowKeys} />
        <Stack direction='column' p={20} style={{
        backgroundColor: colors.gray10,
        borderRadius: '8px'
      }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
        </Stack>
      </Stack>;
  },
  args: {
    selectableRows: true
  },
  parameters: {
    a11y: {
      disable: true
    },
    docs: {
      description: {
        story: 'selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### Selection With Custom Key Extractor

rowSelectionKeyExtractor를 지정하면 row.key가 아닌 다른 값을 사용할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };

    return (
      <Stack direction='column' gap={20}>
        <DataTable
          {...args}
          columns={cachedColumns}
          rows={cachedRows}
          onSelectRows={handleSelectedRows}
          rowSelection={selectedRowKeys}
        />
        <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{ fontSize: '12px' }}>
              {JSON.stringify(
                {
                  selectedRowKeys: selectedRowKeys,
                  selectedRows: selectedRows.map((row) => row),
                },
                null,
                2,
              )}
            </code>
          </pre>
        </Stack>
      </Stack>
    );
  },
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => args.columns as DataTableColumnsType<DataType> ?? columns, []);
    const cachedRows = useMemo(() => args.rows as DataTableRowsType<DataType> ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>([]);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };
    return <Stack direction='column' gap={20}>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} onSelectRows={handleSelectedRows} rowSelectionKeyExtractor={(_, index) => index} rowSelection={selectedRowKeys} />
        <Stack direction='column' p={20} style={{
        backgroundColor: colors.gray10,
        borderRadius: '8px'
      }}>
          <BaseText>
            <b>{selectedRows.length}</b>개 Row 선택되었습니다.
          </BaseText>
          <pre>
            <code style={{
            fontSize: '12px'
          }}>
              {JSON.stringify({
              selectedRowKeys: selectedRowKeys,
              selectedRows: selectedRows.map(row => row)
            }, null, 2)}
            </code>
          </pre>
        </Stack>
      </Stack>;
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'rowSelectionKeyExtractor를 지정하면 row.key가 아닌 다른 값을 사용할 수 있습니다.'
      }
    }
  }
}
```

### Sort

테이블 컬럼 sortable 속성 사용시 해당 열 데이터 Sorting을 위한 onSortBy 이벤트를 전달합니다. Server side 소팅만 지원합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const DEFAULT_FILTER_ORDER: SortDirection[] = ['asc', 'desc', 'default'];
    const [data, setData] = useState<DataTableRowsType<DataType>>(rows);
    const [filter, setFilter] = useState({ sortBy: 'name', sortDirection: 'default' as SortDirection });

    const getSortDirection = (sortDirections: SortDirection[], current: SortDirection | null) => {
      if (!current) {
        return sortDirections[0];
      }
      const currentOrderIndex = sortDirections.indexOf(current) + 1;
      return currentOrderIndex === sortDirections.length ? sortDirections[0] : sortDirections[currentOrderIndex];
    };

    const handleSort = (sortByColumn: string) => {
      const nextSortOrder = getSortDirection(DEFAULT_FILTER_ORDER, filter.sortDirection);
      setFilter((prevFilter) => ({ ...prevFilter, sortBy: sortByColumn, sortDirection: nextSortOrder }));

      // server api call by sortId..
      // 정렬 예시를 위한 샘플 코드 => 실제 api 호출후 갱신된 데이터를 가정합니다.
      switch (nextSortOrder) {
        case 'asc':
          setData(sortBy([...data], sortByColumn));
          return;
        case 'desc':
          setData(sortBy([...data], sortByColumn).reverse());
          return;
        case 'default':
        default:
          setData(rows);
          return;
      }
    };

    return (
      <div>
        <BaseText as='div' style={{ marginBottom: '24px' }}>
          정렬 예시를 위한 샘플 코드로 실제 서버 사이드 소팅 api 호출후 갱신된 데이터를 가정합니다.
        </BaseText>
        <DataTable
          {...args}
          columns={(args.columns as DataTableColumnsType<DataType>) ?? sortColumns}
          rows={data}
          sortBy={filter.sortBy}
          sortDirection={filter.sortDirection}
          onSortBy={handleSort}
        />
      </div>
    );
  },
  args: {},
  parameters: {
    docs: {
      description: {
        story: '테이블 컬럼 sortable 속성 사용시 해당 열 데이터 Sorting을 위한 onSortBy 이벤트를 전달합니다. Server side 소팅만 지원합니다.'
      }
    }
  }
}
```

### Strict Default All Selection

selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? selectionRows, []);
    const [selectedRows, setSelectedRows] = useState<DataTableRowsType<DataType>>(args.rows);
    const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>(args.rows.map((row) => row.key ?? ''));

    const handleSelectedRows = (rowsKey: Key[], rows: DataTableRowsType<DataType>) => {
      console.log('Select Rows =>', rowsKey, rows);
      setSelectedRows(rows);
      setSelectedRowKeys(rowsKey);
    };

    return (
      <StrictMode>
        <Stack direction='column' gap={20}>
          <DataTable
            {...args}
            columns={cachedColumns}
            rows={cachedRows}
            onSelectRows={handleSelectedRows}
            rowSelection={selectedRowKeys}
          />
          <Stack direction='column' p={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            <BaseText>
              <b>{selectedRows.length}</b>개 Row 선택되었습니다.
            </BaseText>
            <pre>
              <code style={{ fontSize: '12px' }}>
                {JSON.stringify(
                  {
                    selectedRowKeys: selectedRowKeys,
                    selectedRows: selectedRows.map((row) => row),
                  },
                  null,
                  2,
                )}
              </code>
            </pre>
          </Stack>
        </Stack>
      </StrictMode>
    );
  },
  args: {
    rows: selectionRows,
    selectableRows: true
  },
  parameters: {
    docs: {
      description: {
        story: 'selectableRows 속성 사용시 Selection 테이블을 구성할 수 있습니다. 각 테이블 로우는 고유의 key 속성이 존재해야합니다.'
      }
    }
  }
}
```

### With Pagination

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const [currentPage, setCurrentPage] = useState<number>(1);
    const [data, setData] = useState<DataTableRowsType<DataType>>(rows);

    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => data, [data]);

    useEffect(() => {
      // server api call by pagination
      // Pagination 예시를 위한 샘플 코드 => 실제 api 호출후 갱신된 데이터를 가정합니다.
      const sampleData = [...data];
      setData(sampleData.reverse());
    }, [currentPage]);

    return (
      <div>
        <BaseText as='div' style={{ marginBottom: '24px' }}>
          Pagination 동작 예시를 위한 샘플 코드로 실제 api 호출후 갱신된 데이터를 가정합니다.
        </BaseText>
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
        <Stack direction='column' mt={24} align='center'>
          <Pagination currentPage={currentPage} totalPages={20} onChangePage={setCurrentPage} />
        </Stack>
      </div>
    );
  },
  args: {}
}
```

### Wrapper Sticky

columns의 wrapper를 false로 설정하면 cell 아래의 Stack을 제거할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: WrapperColumns,
    rows: SpanRows
  },
  parameters: {
    docs: {
      description: {
        story: 'columns의 wrapper를 false로 설정하면 cell 아래의 Stack을 제거할 수 있습니다.'
      }
    }
  }
}
```

### Wrapper Sticky Disabled

columns의 wrapper를 false로 설정하면 cell 아래의 Stack을 제거할 수 있습니다.

```tsx
{
  render: (args: DataTableProps<DataType>) => {
    const cachedColumns = useMemo(() => (args.columns as DataTableColumnsType<DataType>) ?? columns, []);
    const cachedRows = useMemo(() => (args.rows as DataTableRowsType<DataType>) ?? rows, []);

    return (
      <Stack direction='column' width={args?.scroll?.x ? Number(args.scroll.x) - 300 : '100%'} gap={20}>
        {args?.scroll && (
          <Stack gap={24} p={20} mb={20} style={{ backgroundColor: colors.gray10, borderRadius: '8px' }}>
            {args?.scroll?.x && <BaseText>Scroll x: {args.scroll.x}</BaseText>}
            {args?.scroll?.y && <BaseText>Scroll y: {args.scroll.y}</BaseText>}
          </Stack>
        )}
        <DataTable {...args} columns={cachedColumns} rows={cachedRows} />
      </Stack>
    );
  },
  args: {
    columns: WrapperColumns,
    rows: SpanRows.map(row => ({
      ...row,
      disabled: true
    }))
  },
  parameters: {
    docs: {
      description: {
        story: 'columns의 wrapper를 false로 설정하면 cell 아래의 Stack을 제거할 수 있습니다.'
      }
    }
  }
}
```