@boostdev/design-system-components 2.4.2 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -20
- package/README.md +3 -23
- package/dist/client.cjs +115 -61
- package/dist/client.css +537 -537
- package/dist/client.d.cts +11 -1
- package/dist/client.d.ts +11 -1
- package/dist/client.js +115 -61
- package/dist/index.cjs +115 -61
- package/dist/index.css +537 -537
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +115 -61
- package/dist/web-components/index.js +23 -3
- package/package.json +2 -10
- package/src/components/layout/Grid/Grid.mdx +20 -2
- package/src/components/layout/Grid/Grid.spec.tsx +194 -4
- package/src/components/layout/Grid/Grid.tsx +15 -1
- package/src/components/layout/Grid/masonry.ts +127 -9
- package/dist/native/index.cjs +0 -4337
- package/dist/native/index.d.cts +0 -574
- package/dist/native/index.d.ts +0 -574
- package/dist/native/index.js +0 -4421
- package/src/components/interaction/Button/Button.native.mdx +0 -91
- package/src/components/interaction/Button/Button.native.spec.tsx +0 -55
- package/src/components/interaction/Button/Button.native.stories.tsx +0 -141
- package/src/components/interaction/Button/Button.native.tsx +0 -105
- package/src/components/interaction/Dialog/Dialog.native.mdx +0 -61
- package/src/components/interaction/Dialog/Dialog.native.spec.tsx +0 -73
- package/src/components/interaction/Dialog/Dialog.native.stories.tsx +0 -53
- package/src/components/interaction/Dialog/Dialog.native.tsx +0 -128
- package/src/components/interaction/Drawer/Drawer.native.mdx +0 -58
- package/src/components/interaction/Drawer/Drawer.native.spec.tsx +0 -81
- package/src/components/interaction/Drawer/Drawer.native.stories.tsx +0 -33
- package/src/components/interaction/Drawer/Drawer.native.tsx +0 -175
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.mdx +0 -74
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.spec.tsx +0 -78
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.stories.tsx +0 -51
- package/src/components/interaction/DropdownMenu/DropdownMenu.native.tsx +0 -254
- package/src/components/interaction/Popover/Popover.native.mdx +0 -61
- package/src/components/interaction/Popover/Popover.native.spec.tsx +0 -73
- package/src/components/interaction/Popover/Popover.native.stories.tsx +0 -44
- package/src/components/interaction/Popover/Popover.native.tsx +0 -87
- package/src/components/interaction/Rating/Rating.native.mdx +0 -55
- package/src/components/interaction/Rating/Rating.native.spec.tsx +0 -38
- package/src/components/interaction/Rating/Rating.native.stories.tsx +0 -37
- package/src/components/interaction/Rating/Rating.native.tsx +0 -50
- package/src/components/interaction/Toast/Toast.native.mdx +0 -81
- package/src/components/interaction/Toast/Toast.native.spec.tsx +0 -80
- package/src/components/interaction/Toast/Toast.native.stories.tsx +0 -47
- package/src/components/interaction/Toast/Toast.native.tsx +0 -202
- package/src/components/interaction/form/Checkbox/Checkbox.native.mdx +0 -74
- package/src/components/interaction/form/Checkbox/Checkbox.native.spec.tsx +0 -42
- package/src/components/interaction/form/Checkbox/Checkbox.native.stories.tsx +0 -34
- package/src/components/interaction/form/Checkbox/Checkbox.native.tsx +0 -80
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.mdx +0 -78
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.spec.tsx +0 -57
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.stories.tsx +0 -96
- package/src/components/interaction/form/CheckboxGroup/CheckboxGroup.native.tsx +0 -61
- package/src/components/interaction/form/Combobox/Combobox.native.mdx +0 -86
- package/src/components/interaction/form/Combobox/Combobox.native.spec.tsx +0 -60
- package/src/components/interaction/form/Combobox/Combobox.native.stories.tsx +0 -99
- package/src/components/interaction/form/Combobox/Combobox.native.tsx +0 -211
- package/src/components/interaction/form/FileInput/FileInput.native.mdx +0 -86
- package/src/components/interaction/form/FileInput/FileInput.native.spec.tsx +0 -73
- package/src/components/interaction/form/FileInput/FileInput.native.stories.tsx +0 -69
- package/src/components/interaction/form/FileInput/FileInput.native.tsx +0 -129
- package/src/components/interaction/form/FormInput/FormInput.native.mdx +0 -81
- package/src/components/interaction/form/FormInput/FormInput.native.spec.tsx +0 -51
- package/src/components/interaction/form/FormInput/FormInput.native.stories.tsx +0 -54
- package/src/components/interaction/form/FormInput/FormInput.native.tsx +0 -122
- package/src/components/interaction/form/NumberInput/NumberInput.native.mdx +0 -87
- package/src/components/interaction/form/NumberInput/NumberInput.native.spec.tsx +0 -63
- package/src/components/interaction/form/NumberInput/NumberInput.native.stories.tsx +0 -63
- package/src/components/interaction/form/NumberInput/NumberInput.native.tsx +0 -198
- package/src/components/interaction/form/Radio/Radio.native.spec.tsx +0 -79
- package/src/components/interaction/form/Radio/Radio.native.stories.tsx +0 -44
- package/src/components/interaction/form/Radio/Radio.native.tsx +0 -79
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.mdx +0 -79
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.spec.tsx +0 -66
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.stories.tsx +0 -96
- package/src/components/interaction/form/RadioGroup/RadioGroup.native.tsx +0 -61
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.mdx +0 -78
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.spec.tsx +0 -82
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.stories.tsx +0 -99
- package/src/components/interaction/form/SegmentedControl/SegmentedControl.native.tsx +0 -121
- package/src/components/interaction/form/Select/Select.native.mdx +0 -85
- package/src/components/interaction/form/Select/Select.native.spec.tsx +0 -64
- package/src/components/interaction/form/Select/Select.native.stories.tsx +0 -75
- package/src/components/interaction/form/Select/Select.native.tsx +0 -252
- package/src/components/interaction/form/Slider/Slider.native.mdx +0 -83
- package/src/components/interaction/form/Slider/Slider.native.spec.tsx +0 -51
- package/src/components/interaction/form/Slider/Slider.native.stories.tsx +0 -58
- package/src/components/interaction/form/Slider/Slider.native.tsx +0 -188
- package/src/components/interaction/form/Switch/Switch.native.spec.tsx +0 -60
- package/src/components/interaction/form/Switch/Switch.native.stories.tsx +0 -33
- package/src/components/interaction/form/Switch/Switch.native.tsx +0 -59
- package/src/components/interaction/form/Textarea/Textarea.native.mdx +0 -76
- package/src/components/interaction/form/Textarea/Textarea.native.spec.tsx +0 -50
- package/src/components/interaction/form/Textarea/Textarea.native.stories.tsx +0 -50
- package/src/components/interaction/form/Textarea/Textarea.native.tsx +0 -108
- package/src/components/interaction/form/atoms/Label.native.spec.tsx +0 -39
- package/src/components/interaction/form/atoms/Label.native.stories.tsx +0 -18
- package/src/components/interaction/form/atoms/Label.native.tsx +0 -29
- package/src/components/interaction/form/atoms/Message.native.spec.tsx +0 -30
- package/src/components/interaction/form/atoms/Message.native.stories.tsx +0 -22
- package/src/components/interaction/form/atoms/Message.native.tsx +0 -34
- package/src/components/layout/ButtonGroup/ButtonGroup.native.spec.tsx +0 -83
- package/src/components/layout/ButtonGroup/ButtonGroup.native.stories.tsx +0 -44
- package/src/components/layout/ButtonGroup/ButtonGroup.native.tsx +0 -44
- package/src/components/layout/Card/Card.native.spec.tsx +0 -102
- package/src/components/layout/Card/Card.native.stories.tsx +0 -51
- package/src/components/layout/Card/Card.native.tsx +0 -89
- package/src/components/layout/IconWrapper/IconWrapper.native.spec.tsx +0 -39
- package/src/components/layout/IconWrapper/IconWrapper.native.stories.tsx +0 -37
- package/src/components/layout/IconWrapper/IconWrapper.native.tsx +0 -20
- package/src/components/layout/SectionHeader/SectionHeader.native.spec.tsx +0 -51
- package/src/components/layout/SectionHeader/SectionHeader.native.stories.tsx +0 -36
- package/src/components/layout/SectionHeader/SectionHeader.native.tsx +0 -79
- package/src/components/ui/Accordion/Accordion.native.mdx +0 -65
- package/src/components/ui/Accordion/Accordion.native.spec.tsx +0 -69
- package/src/components/ui/Accordion/Accordion.native.stories.tsx +0 -52
- package/src/components/ui/Accordion/Accordion.native.tsx +0 -141
- package/src/components/ui/Alert/Alert.native.mdx +0 -62
- package/src/components/ui/Alert/Alert.native.spec.tsx +0 -76
- package/src/components/ui/Alert/Alert.native.stories.tsx +0 -41
- package/src/components/ui/Alert/Alert.native.tsx +0 -94
- package/src/components/ui/Avatar/Avatar.native.mdx +0 -50
- package/src/components/ui/Avatar/Avatar.native.spec.tsx +0 -47
- package/src/components/ui/Avatar/Avatar.native.stories.tsx +0 -32
- package/src/components/ui/Avatar/Avatar.native.tsx +0 -85
- package/src/components/ui/Badge/Badge.native.mdx +0 -54
- package/src/components/ui/Badge/Badge.native.spec.tsx +0 -45
- package/src/components/ui/Badge/Badge.native.stories.tsx +0 -34
- package/src/components/ui/Badge/Badge.native.tsx +0 -50
- package/src/components/ui/Breadcrumb/Breadcrumb.native.mdx +0 -52
- package/src/components/ui/Breadcrumb/Breadcrumb.native.spec.tsx +0 -49
- package/src/components/ui/Breadcrumb/Breadcrumb.native.stories.tsx +0 -46
- package/src/components/ui/Breadcrumb/Breadcrumb.native.tsx +0 -83
- package/src/components/ui/Calendar/Calendar.native.mdx +0 -58
- package/src/components/ui/Calendar/Calendar.native.spec.tsx +0 -92
- package/src/components/ui/Calendar/Calendar.native.stories.tsx +0 -45
- package/src/components/ui/Calendar/Calendar.native.tsx +0 -294
- package/src/components/ui/Carousel/Carousel.native.mdx +0 -55
- package/src/components/ui/Carousel/Carousel.native.spec.tsx +0 -55
- package/src/components/ui/Carousel/Carousel.native.stories.tsx +0 -59
- package/src/components/ui/Carousel/Carousel.native.tsx +0 -184
- package/src/components/ui/Collapsible/Collapsible.native.mdx +0 -53
- package/src/components/ui/Collapsible/Collapsible.native.spec.tsx +0 -68
- package/src/components/ui/Collapsible/Collapsible.native.stories.tsx +0 -48
- package/src/components/ui/Collapsible/Collapsible.native.tsx +0 -139
- package/src/components/ui/DescriptionList/DescriptionList.native.mdx +0 -50
- package/src/components/ui/DescriptionList/DescriptionList.native.spec.tsx +0 -37
- package/src/components/ui/DescriptionList/DescriptionList.native.stories.tsx +0 -48
- package/src/components/ui/DescriptionList/DescriptionList.native.tsx +0 -54
- package/src/components/ui/Link/Link.native.mdx +0 -53
- package/src/components/ui/Link/Link.native.spec.tsx +0 -35
- package/src/components/ui/Link/Link.native.stories.tsx +0 -46
- package/src/components/ui/Link/Link.native.tsx +0 -48
- package/src/components/ui/Loading/Loading.native.spec.tsx +0 -24
- package/src/components/ui/Loading/Loading.native.stories.tsx +0 -31
- package/src/components/ui/Loading/Loading.native.tsx +0 -29
- package/src/components/ui/NotificationBanner/NotificationBanner.native.spec.tsx +0 -85
- package/src/components/ui/NotificationBanner/NotificationBanner.native.stories.tsx +0 -37
- package/src/components/ui/NotificationBanner/NotificationBanner.native.tsx +0 -76
- package/src/components/ui/Pagination/Pagination.native.mdx +0 -62
- package/src/components/ui/Pagination/Pagination.native.spec.tsx +0 -86
- package/src/components/ui/Pagination/Pagination.native.stories.tsx +0 -47
- package/src/components/ui/Pagination/Pagination.native.tsx +0 -157
- package/src/components/ui/Progress/Progress.native.spec.tsx +0 -56
- package/src/components/ui/Progress/Progress.native.stories.tsx +0 -34
- package/src/components/ui/Progress/Progress.native.tsx +0 -84
- package/src/components/ui/ProgressCircle/ProgressCircle.native.mdx +0 -63
- package/src/components/ui/ProgressCircle/ProgressCircle.native.spec.tsx +0 -39
- package/src/components/ui/ProgressCircle/ProgressCircle.native.stories.tsx +0 -36
- package/src/components/ui/ProgressCircle/ProgressCircle.native.tsx +0 -176
- package/src/components/ui/Separator/Separator.native.spec.tsx +0 -39
- package/src/components/ui/Separator/Separator.native.stories.tsx +0 -38
- package/src/components/ui/Separator/Separator.native.tsx +0 -32
- package/src/components/ui/Skeleton/Skeleton.native.spec.tsx +0 -39
- package/src/components/ui/Skeleton/Skeleton.native.stories.tsx +0 -31
- package/src/components/ui/Skeleton/Skeleton.native.tsx +0 -42
- package/src/components/ui/Table/Table.native.mdx +0 -72
- package/src/components/ui/Table/Table.native.spec.tsx +0 -83
- package/src/components/ui/Table/Table.native.stories.tsx +0 -55
- package/src/components/ui/Table/Table.native.tsx +0 -196
- package/src/components/ui/Tabs/Tabs.native.mdx +0 -65
- package/src/components/ui/Tabs/Tabs.native.spec.tsx +0 -65
- package/src/components/ui/Tabs/Tabs.native.stories.tsx +0 -57
- package/src/components/ui/Tabs/Tabs.native.tsx +0 -133
- package/src/components/ui/Tooltip/Tooltip.native.mdx +0 -47
- package/src/components/ui/Tooltip/Tooltip.native.spec.tsx +0 -37
- package/src/components/ui/Tooltip/Tooltip.native.stories.tsx +0 -38
- package/src/components/ui/Tooltip/Tooltip.native.tsx +0 -57
- package/src/components/ui/Typography/Typography.native.mdx +0 -56
- package/src/components/ui/Typography/Typography.native.spec.tsx +0 -49
- package/src/components/ui/Typography/Typography.native.stories.tsx +0 -36
- package/src/components/ui/Typography/Typography.native.tsx +0 -65
- package/src/native/ThemeContext.tsx +0 -28
- package/src/native/tokens.ts +0 -13
- package/src/native.ts +0 -76
- package/src/stories/ReactNative.mdx +0 -156
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Table.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Table (React Native)
|
|
7
|
-
|
|
8
|
-
React Native table component for displaying structured data in rows and columns. Supports optional sortable columns, captions, and horizontal scrolling for wide tables. Alternating row backgrounds improve readability.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** -- API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
import { Table } from '@boostdev/design-system-components';
|
|
16
|
-
|
|
17
|
-
<Table
|
|
18
|
-
columns={[
|
|
19
|
-
{ key: 'name', header: 'Name' },
|
|
20
|
-
{ key: 'email', header: 'Email' },
|
|
21
|
-
]}
|
|
22
|
-
rows={[
|
|
23
|
-
{ name: 'Alice', email: 'alice@example.com' },
|
|
24
|
-
{ name: 'Bob', email: 'bob@example.com' },
|
|
25
|
-
]}
|
|
26
|
-
/>
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Examples
|
|
30
|
-
|
|
31
|
-
### Default
|
|
32
|
-
<Canvas of={Stories.Default} />
|
|
33
|
-
|
|
34
|
-
### With caption
|
|
35
|
-
<Canvas of={Stories.WithCaption} />
|
|
36
|
-
|
|
37
|
-
### Sortable columns
|
|
38
|
-
<Canvas of={Stories.Sortable} />
|
|
39
|
-
|
|
40
|
-
### Empty table
|
|
41
|
-
<Canvas of={Stories.Empty} />
|
|
42
|
-
|
|
43
|
-
## Props
|
|
44
|
-
|
|
45
|
-
<ArgTypes of={Stories} />
|
|
46
|
-
|
|
47
|
-
## Props reference
|
|
48
|
-
|
|
49
|
-
| Prop | Type | Default | Description |
|
|
50
|
-
|------|------|---------|-------------|
|
|
51
|
-
| `columns` | `TableColumn<Row>[]` | -- | Column definitions |
|
|
52
|
-
| `rows` | `Row[]` | -- | Data rows |
|
|
53
|
-
| `caption` | `string` | -- | Optional table caption |
|
|
54
|
-
| `sortKey` | `string` | -- | Currently sorted column key |
|
|
55
|
-
| `sortDirection` | `"asc" \| "desc"` | -- | Current sort direction |
|
|
56
|
-
| `onSort` | `(key: string, direction: "asc" \| "desc") => void` | -- | Called when a sortable header is pressed |
|
|
57
|
-
| `style` | `StyleProp<ViewStyle>` | -- | Custom style for the scroll wrapper |
|
|
58
|
-
|
|
59
|
-
### TableColumn
|
|
60
|
-
|
|
61
|
-
| Prop | Type | Description |
|
|
62
|
-
|------|------|-------------|
|
|
63
|
-
| `key` | `string` | Column key matching row property |
|
|
64
|
-
| `header` | `ReactNode` | Header cell content |
|
|
65
|
-
| `sortable` | `boolean` | Enables sort button on header |
|
|
66
|
-
| `render` | `(row: Row) => ReactNode` | Custom cell renderer |
|
|
67
|
-
|
|
68
|
-
## Accessibility
|
|
69
|
-
|
|
70
|
-
- Sortable headers have `button` role with descriptive labels
|
|
71
|
-
- Sort indicators are hidden from the accessibility tree
|
|
72
|
-
- Table scrolls horizontally when content exceeds the viewport width
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
-
import { Table } from './Table.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const columns = [
|
|
10
|
-
{ key: 'name', header: 'Name' },
|
|
11
|
-
{ key: 'age', header: 'Age' },
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
const rows = [
|
|
15
|
-
{ name: 'Alice', age: 29 },
|
|
16
|
-
{ name: 'Bob', age: 34 },
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
describe('Table (native)', () => {
|
|
20
|
-
it('renders column headers', () => {
|
|
21
|
-
renderWithTheme(<Table columns={columns} rows={rows} />);
|
|
22
|
-
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
23
|
-
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('renders row data', () => {
|
|
27
|
-
renderWithTheme(<Table columns={columns} rows={rows} />);
|
|
28
|
-
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
29
|
-
expect(screen.getByText('29')).toBeInTheDocument();
|
|
30
|
-
expect(screen.getByText('Bob')).toBeInTheDocument();
|
|
31
|
-
expect(screen.getByText('34')).toBeInTheDocument();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('renders a caption when provided', () => {
|
|
35
|
-
renderWithTheme(<Table columns={columns} rows={rows} caption="Team" />);
|
|
36
|
-
expect(screen.getByText('Team')).toBeInTheDocument();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('renders an empty table without errors', () => {
|
|
40
|
-
renderWithTheme(<Table columns={columns} rows={[]} />);
|
|
41
|
-
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
42
|
-
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('calls onSort when a sortable column header is pressed', () => {
|
|
46
|
-
const onSort = vi.fn();
|
|
47
|
-
const sortableColumns = [
|
|
48
|
-
{ key: 'name', header: 'Name', sortable: true },
|
|
49
|
-
{ key: 'age', header: 'Age' },
|
|
50
|
-
];
|
|
51
|
-
renderWithTheme(
|
|
52
|
-
<Table columns={sortableColumns} rows={rows} onSort={onSort} />,
|
|
53
|
-
);
|
|
54
|
-
fireEvent.click(screen.getByRole('button', { name: /Sort by Name/ }));
|
|
55
|
-
expect(onSort).toHaveBeenCalledWith('name', 'asc');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('toggles sort direction on subsequent presses', () => {
|
|
59
|
-
const onSort = vi.fn();
|
|
60
|
-
const sortableColumns = [
|
|
61
|
-
{ key: 'name', header: 'Name', sortable: true },
|
|
62
|
-
];
|
|
63
|
-
renderWithTheme(
|
|
64
|
-
<Table
|
|
65
|
-
columns={sortableColumns}
|
|
66
|
-
rows={rows}
|
|
67
|
-
sortKey="name"
|
|
68
|
-
sortDirection="asc"
|
|
69
|
-
onSort={onSort}
|
|
70
|
-
/>,
|
|
71
|
-
);
|
|
72
|
-
fireEvent.click(screen.getByRole('button', { name: /Sort by Name/ }));
|
|
73
|
-
expect(onSort).toHaveBeenCalledWith('name', 'desc');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('uses custom render function for cells', () => {
|
|
77
|
-
const customColumns = [
|
|
78
|
-
{ key: 'name', header: 'Name', render: (row: { name: string }) => `Mr. ${row.name}` },
|
|
79
|
-
];
|
|
80
|
-
renderWithTheme(<Table columns={customColumns} rows={[{ name: 'Alice' }]} />);
|
|
81
|
-
expect(screen.getByText('Mr. Alice')).toBeInTheDocument();
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
|
-
import { Table } from './Table.native';
|
|
5
|
-
|
|
6
|
-
interface Person { name: string; role: string; age: number }
|
|
7
|
-
|
|
8
|
-
const meta = {
|
|
9
|
-
tags: ['!stable', 'alpha'],
|
|
10
|
-
title: 'React Native/UI/Table',
|
|
11
|
-
component: Table<Person>,
|
|
12
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
13
|
-
parameters: { layout: 'padded' },
|
|
14
|
-
} satisfies Meta<typeof Table<Person>>;
|
|
15
|
-
|
|
16
|
-
export default meta;
|
|
17
|
-
type Story = StoryObj<typeof meta>;
|
|
18
|
-
|
|
19
|
-
const columns = [
|
|
20
|
-
{ key: 'name', header: 'Name' },
|
|
21
|
-
{ key: 'role', header: 'Role' },
|
|
22
|
-
{ key: 'age', header: 'Age' },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const rows: Person[] = [
|
|
26
|
-
{ name: 'Alice Johnson', role: 'Engineer', age: 29 },
|
|
27
|
-
{ name: 'Bob Smith', role: 'Designer', age: 34 },
|
|
28
|
-
{ name: 'Carol White', role: 'Manager', age: 41 },
|
|
29
|
-
{ name: 'Dave Brown', role: 'Analyst', age: 26 },
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
export const Default: Story = {
|
|
33
|
-
args: { columns, rows },
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export const WithCaption: Story = {
|
|
37
|
-
args: { columns, rows, caption: 'Team Members' },
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const Sortable: Story = {
|
|
41
|
-
args: {
|
|
42
|
-
columns: [
|
|
43
|
-
{ key: 'name', header: 'Name', sortable: true },
|
|
44
|
-
{ key: 'role', header: 'Role' },
|
|
45
|
-
{ key: 'age', header: 'Age', sortable: true },
|
|
46
|
-
],
|
|
47
|
-
rows,
|
|
48
|
-
sortKey: 'name',
|
|
49
|
-
sortDirection: 'asc',
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export const Empty: Story = {
|
|
54
|
-
args: { columns, rows: [] },
|
|
55
|
-
};
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
StyleSheet,
|
|
4
|
-
View,
|
|
5
|
-
Text,
|
|
6
|
-
ScrollView,
|
|
7
|
-
Pressable,
|
|
8
|
-
StyleProp,
|
|
9
|
-
ViewStyle,
|
|
10
|
-
} from 'react-native';
|
|
11
|
-
import { spacing, font, border, lineHeight } from '../../../native/tokens';
|
|
12
|
-
import { useTheme } from '../../../native/ThemeContext';
|
|
13
|
-
|
|
14
|
-
export interface TableColumn<Row> {
|
|
15
|
-
key: string;
|
|
16
|
-
header: ReactNode;
|
|
17
|
-
sortable?: boolean;
|
|
18
|
-
render?: (row: Row) => ReactNode;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface TableProps<Row extends Record<string, unknown>> {
|
|
22
|
-
columns: TableColumn<Row>[];
|
|
23
|
-
rows: Row[];
|
|
24
|
-
caption?: string;
|
|
25
|
-
sortKey?: string;
|
|
26
|
-
sortDirection?: 'asc' | 'desc';
|
|
27
|
-
onSort?: (key: string, direction: 'asc' | 'desc') => void;
|
|
28
|
-
style?: StyleProp<ViewStyle>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const styles = StyleSheet.create({
|
|
32
|
-
wrapper: {
|
|
33
|
-
borderRadius: border.radius.s,
|
|
34
|
-
overflow: 'hidden',
|
|
35
|
-
},
|
|
36
|
-
caption: {
|
|
37
|
-
fontSize: font.size.bodyS,
|
|
38
|
-
fontFamily: font.family.body,
|
|
39
|
-
fontWeight: font.weight.semibold,
|
|
40
|
-
lineHeight: lineHeight.bodyS,
|
|
41
|
-
paddingVertical: spacing.xs,
|
|
42
|
-
paddingHorizontal: spacing.m,
|
|
43
|
-
},
|
|
44
|
-
headerRow: {
|
|
45
|
-
flexDirection: 'row',
|
|
46
|
-
},
|
|
47
|
-
headerCell: {
|
|
48
|
-
flex: 1,
|
|
49
|
-
minWidth: 100,
|
|
50
|
-
paddingVertical: spacing.s,
|
|
51
|
-
paddingHorizontal: spacing.m,
|
|
52
|
-
flexDirection: 'row',
|
|
53
|
-
alignItems: 'center',
|
|
54
|
-
gap: spacing.xxs,
|
|
55
|
-
},
|
|
56
|
-
headerText: {
|
|
57
|
-
fontSize: font.size.bodyS,
|
|
58
|
-
fontFamily: font.family.body,
|
|
59
|
-
fontWeight: font.weight.semibold,
|
|
60
|
-
lineHeight: lineHeight.bodyS,
|
|
61
|
-
},
|
|
62
|
-
sortIndicator: {
|
|
63
|
-
fontSize: font.size.bodyS,
|
|
64
|
-
},
|
|
65
|
-
row: {
|
|
66
|
-
flexDirection: 'row',
|
|
67
|
-
borderTopWidth: 1,
|
|
68
|
-
minHeight: 44,
|
|
69
|
-
},
|
|
70
|
-
cell: {
|
|
71
|
-
flex: 1,
|
|
72
|
-
minWidth: 100,
|
|
73
|
-
paddingVertical: spacing.s,
|
|
74
|
-
paddingHorizontal: spacing.m,
|
|
75
|
-
justifyContent: 'center',
|
|
76
|
-
},
|
|
77
|
-
cellText: {
|
|
78
|
-
fontSize: font.size.body,
|
|
79
|
-
fontFamily: font.family.body,
|
|
80
|
-
fontWeight: font.weight.body,
|
|
81
|
-
lineHeight: lineHeight.body,
|
|
82
|
-
},
|
|
83
|
-
pressed: {
|
|
84
|
-
opacity: 0.75,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
export function Table<Row extends Record<string, unknown>>({
|
|
89
|
-
columns,
|
|
90
|
-
rows,
|
|
91
|
-
caption,
|
|
92
|
-
sortKey,
|
|
93
|
-
sortDirection,
|
|
94
|
-
onSort,
|
|
95
|
-
style,
|
|
96
|
-
}: Readonly<TableProps<Row>>) {
|
|
97
|
-
const { colors } = useTheme();
|
|
98
|
-
|
|
99
|
-
const handleSort = (key: string) => {
|
|
100
|
-
if (!onSort) return;
|
|
101
|
-
const nextDirection = sortKey === key && sortDirection === 'asc' ? 'desc' : 'asc';
|
|
102
|
-
onSort(key, nextDirection);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const getSortLabel = (col: TableColumn<Row>): string => {
|
|
106
|
-
const base = `Sort by ${String(col.header)}`;
|
|
107
|
-
if (sortKey !== col.key) return base;
|
|
108
|
-
return `${base}, currently ${sortDirection === 'asc' ? 'ascending' : 'descending'}`;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
return (
|
|
112
|
-
<ScrollView
|
|
113
|
-
horizontal
|
|
114
|
-
showsHorizontalScrollIndicator={true}
|
|
115
|
-
style={[styles.wrapper, { backgroundColor: colors.colorBg }, style]}
|
|
116
|
-
>
|
|
117
|
-
<View>
|
|
118
|
-
{caption && (
|
|
119
|
-
<Text style={[styles.caption, { color: colors.colorOnBgSubtle }]}>
|
|
120
|
-
{caption}
|
|
121
|
-
</Text>
|
|
122
|
-
)}
|
|
123
|
-
|
|
124
|
-
<View style={[styles.headerRow, { backgroundColor: colors.colorBgSubtle }]}>
|
|
125
|
-
{columns.map(col =>
|
|
126
|
-
col.sortable ? (
|
|
127
|
-
<Pressable
|
|
128
|
-
key={col.key}
|
|
129
|
-
onPress={() => handleSort(col.key)}
|
|
130
|
-
accessibilityRole="button"
|
|
131
|
-
accessibilityLabel={getSortLabel(col)}
|
|
132
|
-
style={({ pressed }) => [styles.headerCell, pressed && styles.pressed]}
|
|
133
|
-
>
|
|
134
|
-
<Text style={[styles.headerText, { color: colors.colorOnBg }]}>
|
|
135
|
-
{col.header}
|
|
136
|
-
</Text>
|
|
137
|
-
<Text
|
|
138
|
-
style={[
|
|
139
|
-
styles.sortIndicator,
|
|
140
|
-
{
|
|
141
|
-
color: sortKey === col.key
|
|
142
|
-
? colors.colorInteractive
|
|
143
|
-
: colors.colorOnBgSubtle,
|
|
144
|
-
},
|
|
145
|
-
]}
|
|
146
|
-
importantForAccessibility="no"
|
|
147
|
-
accessibilityElementsHidden={true}
|
|
148
|
-
>
|
|
149
|
-
{sortKey === col.key
|
|
150
|
-
? sortDirection === 'asc' ? '▲' : '▼'
|
|
151
|
-
: '▽'}
|
|
152
|
-
</Text>
|
|
153
|
-
</Pressable>
|
|
154
|
-
) : (
|
|
155
|
-
<View key={col.key} style={styles.headerCell}>
|
|
156
|
-
<Text style={[styles.headerText, { color: colors.colorOnBg }]}>
|
|
157
|
-
{col.header}
|
|
158
|
-
</Text>
|
|
159
|
-
</View>
|
|
160
|
-
),
|
|
161
|
-
)}
|
|
162
|
-
</View>
|
|
163
|
-
|
|
164
|
-
{rows.map((row, rowIndex) => (
|
|
165
|
-
<View
|
|
166
|
-
key={rowIndex}
|
|
167
|
-
style={[
|
|
168
|
-
styles.row,
|
|
169
|
-
{ borderTopColor: colors.colorBgSubtle },
|
|
170
|
-
rowIndex % 2 === 1 && { backgroundColor: colors.colorBgSubtle },
|
|
171
|
-
]}
|
|
172
|
-
accessibilityRole="none"
|
|
173
|
-
>
|
|
174
|
-
{columns.map(col => (
|
|
175
|
-
<View key={col.key} style={styles.cell}>
|
|
176
|
-
{col.render ? (
|
|
177
|
-
typeof col.render(row) === 'string' ? (
|
|
178
|
-
<Text style={[styles.cellText, { color: colors.colorOnBg }]}>
|
|
179
|
-
{col.render(row)}
|
|
180
|
-
</Text>
|
|
181
|
-
) : (
|
|
182
|
-
col.render(row)
|
|
183
|
-
)
|
|
184
|
-
) : (
|
|
185
|
-
<Text style={[styles.cellText, { color: colors.colorOnBg }]}>
|
|
186
|
-
{String(row[col.key] ?? '')}
|
|
187
|
-
</Text>
|
|
188
|
-
)}
|
|
189
|
-
</View>
|
|
190
|
-
))}
|
|
191
|
-
</View>
|
|
192
|
-
))}
|
|
193
|
-
</View>
|
|
194
|
-
</ScrollView>
|
|
195
|
-
);
|
|
196
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { Meta, Canvas, ArgTypes } from '@storybook/blocks';
|
|
2
|
-
import * as Stories from './Tabs.native.stories';
|
|
3
|
-
|
|
4
|
-
<Meta of={Stories} />
|
|
5
|
-
|
|
6
|
-
# Tabs (React Native)
|
|
7
|
-
|
|
8
|
-
React Native tabs component with a horizontally scrollable tab bar and content panels. Active tab is indicated with an underline in the interactive color.
|
|
9
|
-
|
|
10
|
-
> **Status: alpha** -- API may change before stable release.
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
import { Tabs } from '@boostdev/design-system-components';
|
|
16
|
-
|
|
17
|
-
<Tabs
|
|
18
|
-
tabs={[
|
|
19
|
-
{ id: 'one', label: 'Tab One', content: <Text>Content one</Text> },
|
|
20
|
-
{ id: 'two', label: 'Tab Two', content: <Text>Content two</Text> },
|
|
21
|
-
]}
|
|
22
|
-
/>
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Examples
|
|
26
|
-
|
|
27
|
-
### Default
|
|
28
|
-
<Canvas of={Stories.Default} />
|
|
29
|
-
|
|
30
|
-
### With default tab
|
|
31
|
-
<Canvas of={Stories.WithDefaultTab} />
|
|
32
|
-
|
|
33
|
-
### With disabled tab
|
|
34
|
-
<Canvas of={Stories.WithDisabledTab} />
|
|
35
|
-
|
|
36
|
-
### Many tabs (scrollable)
|
|
37
|
-
<Canvas of={Stories.ManyTabs} />
|
|
38
|
-
|
|
39
|
-
## Props
|
|
40
|
-
|
|
41
|
-
<ArgTypes of={Stories} />
|
|
42
|
-
|
|
43
|
-
## Props reference
|
|
44
|
-
|
|
45
|
-
| Prop | Type | Default | Description |
|
|
46
|
-
|------|------|---------|-------------|
|
|
47
|
-
| `tabs` | `TabItem[]` | -- | Array of tab definitions |
|
|
48
|
-
| `defaultTab` | `string` | First tab ID | ID of initially active tab |
|
|
49
|
-
| `style` | `StyleProp<ViewStyle>` | -- | Custom style for the container |
|
|
50
|
-
|
|
51
|
-
### TabItem
|
|
52
|
-
|
|
53
|
-
| Prop | Type | Description |
|
|
54
|
-
|------|------|-------------|
|
|
55
|
-
| `id` | `string` | Unique identifier |
|
|
56
|
-
| `label` | `ReactNode` | Tab button label |
|
|
57
|
-
| `content` | `ReactNode` | Panel content |
|
|
58
|
-
| `disabled` | `boolean` | Prevents selection |
|
|
59
|
-
|
|
60
|
-
## Accessibility
|
|
61
|
-
|
|
62
|
-
- Tab bar uses `tabbar` accessibility role
|
|
63
|
-
- Each tab button uses `tab` role with `selected` state
|
|
64
|
-
- Content area uses `tabpanel` role
|
|
65
|
-
- Disabled tabs have `disabled` accessibility state and reduced opacity
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
3
|
-
import { Tabs } from './Tabs.native';
|
|
4
|
-
|
|
5
|
-
function renderWithTheme(ui: React.ReactElement) {
|
|
6
|
-
return render(<ThemeProvider>{ui}</ThemeProvider>);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const tabs = [
|
|
10
|
-
{ id: 'one', label: 'Tab One', content: <span>Content One</span> },
|
|
11
|
-
{ id: 'two', label: 'Tab Two', content: <span>Content Two</span> },
|
|
12
|
-
{ id: 'three', label: 'Tab Three', content: <span>Content Three</span> },
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
describe('Tabs (native)', () => {
|
|
16
|
-
it('renders all tab labels', () => {
|
|
17
|
-
renderWithTheme(<Tabs tabs={tabs} />);
|
|
18
|
-
expect(screen.getByText('Tab One')).toBeInTheDocument();
|
|
19
|
-
expect(screen.getByText('Tab Two')).toBeInTheDocument();
|
|
20
|
-
expect(screen.getByText('Tab Three')).toBeInTheDocument();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('shows first tab content by default', () => {
|
|
24
|
-
renderWithTheme(<Tabs tabs={tabs} />);
|
|
25
|
-
expect(screen.getByText('Content One')).toBeInTheDocument();
|
|
26
|
-
expect(screen.queryByText('Content Two')).not.toBeInTheDocument();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('respects defaultTab prop', () => {
|
|
30
|
-
renderWithTheme(<Tabs tabs={tabs} defaultTab="two" />);
|
|
31
|
-
expect(screen.queryByText('Content One')).not.toBeInTheDocument();
|
|
32
|
-
expect(screen.getByText('Content Two')).toBeInTheDocument();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('switches content when a tab is pressed', () => {
|
|
36
|
-
renderWithTheme(<Tabs tabs={tabs} />);
|
|
37
|
-
fireEvent.click(screen.getByText('Tab Two'));
|
|
38
|
-
expect(screen.queryByText('Content One')).not.toBeInTheDocument();
|
|
39
|
-
expect(screen.getByText('Content Two')).toBeInTheDocument();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('renders tab roles for all tabs', () => {
|
|
43
|
-
renderWithTheme(<Tabs tabs={tabs} />);
|
|
44
|
-
expect(screen.getByRole('tab', { name: 'Tab One' })).toBeInTheDocument();
|
|
45
|
-
expect(screen.getByRole('tab', { name: 'Tab Two' })).toBeInTheDocument();
|
|
46
|
-
expect(screen.getByRole('tab', { name: 'Tab Three' })).toBeInTheDocument();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('disables tabs with disabled flag', () => {
|
|
50
|
-
const withDisabled = [
|
|
51
|
-
...tabs,
|
|
52
|
-
{ id: 'four', label: 'Disabled Tab', content: <span>Nope</span>, disabled: true },
|
|
53
|
-
];
|
|
54
|
-
renderWithTheme(<Tabs tabs={withDisabled} />);
|
|
55
|
-
expect(screen.getByRole('tab', { name: 'Disabled Tab' })).toHaveAttribute(
|
|
56
|
-
'aria-disabled',
|
|
57
|
-
'true',
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('renders tabpanel role for active content', () => {
|
|
62
|
-
renderWithTheme(<Tabs tabs={tabs} />);
|
|
63
|
-
expect(screen.getByRole('tabpanel')).toBeInTheDocument();
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Text } from 'react-native';
|
|
3
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
4
|
-
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
5
|
-
import { Tabs } from './Tabs.native';
|
|
6
|
-
|
|
7
|
-
const meta = {
|
|
8
|
-
tags: ['!stable', 'alpha'],
|
|
9
|
-
title: 'React Native/UI/Tabs',
|
|
10
|
-
component: Tabs,
|
|
11
|
-
decorators: [(Story) => <ThemeProvider colorScheme="light"><Story /></ThemeProvider>],
|
|
12
|
-
parameters: { layout: 'padded' },
|
|
13
|
-
} satisfies Meta<typeof Tabs>;
|
|
14
|
-
|
|
15
|
-
export default meta;
|
|
16
|
-
type Story = StoryObj<typeof meta>;
|
|
17
|
-
|
|
18
|
-
const sampleTabs = [
|
|
19
|
-
{ id: 'overview', label: 'Overview', content: <Text>Overview content goes here.</Text> },
|
|
20
|
-
{ id: 'features', label: 'Features', content: <Text>Feature list and descriptions.</Text> },
|
|
21
|
-
{ id: 'pricing', label: 'Pricing', content: <Text>Pricing tiers and plan details.</Text> },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
export const Default: Story = {
|
|
25
|
-
args: {
|
|
26
|
-
tabs: sampleTabs,
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export const WithDefaultTab: Story = {
|
|
31
|
-
args: {
|
|
32
|
-
tabs: sampleTabs,
|
|
33
|
-
defaultTab: 'features',
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const WithDisabledTab: Story = {
|
|
38
|
-
args: {
|
|
39
|
-
tabs: [
|
|
40
|
-
...sampleTabs,
|
|
41
|
-
{ id: 'admin', label: 'Admin', content: <Text>Admin panel.</Text>, disabled: true },
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const ManyTabs: Story = {
|
|
47
|
-
args: {
|
|
48
|
-
tabs: [
|
|
49
|
-
{ id: 'home', label: 'Home', content: <Text>Home content</Text> },
|
|
50
|
-
{ id: 'discover', label: 'Discover', content: <Text>Discover content</Text> },
|
|
51
|
-
{ id: 'trending', label: 'Trending', content: <Text>Trending content</Text> },
|
|
52
|
-
{ id: 'saved', label: 'Saved', content: <Text>Saved content</Text> },
|
|
53
|
-
{ id: 'settings', label: 'Settings', content: <Text>Settings content</Text> },
|
|
54
|
-
{ id: 'profile', label: 'Profile', content: <Text>Profile content</Text> },
|
|
55
|
-
],
|
|
56
|
-
},
|
|
57
|
-
};
|