@pickle-ui/react-native 1.0.0-beta.0 → 1.0.0-beta.2
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/README.md +70 -11
- package/dist/components/Badge.d.ts +6 -3
- package/dist/components/Badge.d.ts.map +1 -1
- package/dist/components/Badge.js +36 -7
- package/dist/components/Badge.js.map +1 -1
- package/dist/components/Button.d.ts +11 -4
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +100 -15
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Card.d.ts +15 -5
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/Card.js +28 -10
- package/dist/components/Card.js.map +1 -1
- package/dist/components/Flex.d.ts +25 -0
- package/dist/components/Flex.d.ts.map +1 -0
- package/dist/components/Flex.js +24 -0
- package/dist/components/Flex.js.map +1 -0
- package/dist/components/Grid.d.ts +36 -0
- package/dist/components/Grid.d.ts.map +1 -0
- package/dist/components/Grid.js +68 -0
- package/dist/components/Grid.js.map +1 -0
- package/dist/components/Separator.d.ts +11 -0
- package/dist/components/Separator.d.ts.map +1 -0
- package/dist/components/Separator.js +12 -0
- package/dist/components/Separator.js.map +1 -0
- package/dist/components/Spinner.d.ts +14 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +41 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/Text.d.ts +7 -3
- package/dist/components/Text.d.ts.map +1 -1
- package/dist/components/Text.js +23 -11
- package/dist/components/Text.js.map +1 -1
- package/dist/components/XStack.d.ts +4 -0
- package/dist/components/XStack.d.ts.map +1 -0
- package/dist/components/XStack.js +6 -0
- package/dist/components/XStack.js.map +1 -0
- package/dist/components/YStack.d.ts +4 -0
- package/dist/components/YStack.d.ts.map +1 -0
- package/dist/components/YStack.js +6 -0
- package/dist/components/YStack.js.map +1 -0
- package/dist/components/_internal/InputFrame.d.ts +13 -0
- package/dist/components/_internal/InputFrame.d.ts.map +1 -0
- package/dist/components/_internal/InputFrame.js +14 -0
- package/dist/components/_internal/InputFrame.js.map +1 -0
- package/dist/components/_internal/StoryItem.d.ts +6 -0
- package/dist/components/_internal/StoryItem.d.ts.map +1 -0
- package/dist/components/_internal/StoryItem.js +9 -0
- package/dist/components/_internal/StoryItem.js.map +1 -0
- package/dist/components/_internal/layout.d.ts +79 -0
- package/dist/components/_internal/layout.d.ts.map +1 -0
- package/dist/components/_internal/layout.js +73 -0
- package/dist/components/_internal/layout.js.map +1 -0
- package/dist/components/_internal/useControllableState.d.ts +7 -0
- package/dist/components/_internal/useControllableState.d.ts.map +1 -0
- package/dist/components/_internal/useControllableState.js +17 -0
- package/dist/components/_internal/useControllableState.js.map +1 -0
- package/dist/components/form/Checkbox.d.ts +26 -0
- package/dist/components/form/Checkbox.d.ts.map +1 -0
- package/dist/components/form/Checkbox.js +41 -0
- package/dist/components/form/Checkbox.js.map +1 -0
- package/dist/components/form/Form.context.d.ts +34 -0
- package/dist/components/form/Form.context.d.ts.map +1 -0
- package/dist/components/form/Form.context.js +53 -0
- package/dist/components/form/Form.context.js.map +1 -0
- package/dist/components/form/Form.d.ts +33 -0
- package/dist/components/form/Form.d.ts.map +1 -0
- package/dist/components/form/Form.js +36 -0
- package/dist/components/form/Form.js.map +1 -0
- package/dist/components/form/FormCheckbox.d.ts +7 -0
- package/dist/components/form/FormCheckbox.d.ts.map +1 -0
- package/dist/components/form/FormCheckbox.js +24 -0
- package/dist/components/form/FormCheckbox.js.map +1 -0
- package/dist/components/form/FormDescription.d.ts +4 -0
- package/dist/components/form/FormDescription.d.ts.map +1 -0
- package/dist/components/form/FormDescription.js +7 -0
- package/dist/components/form/FormDescription.js.map +1 -0
- package/dist/components/form/FormError.d.ts +5 -0
- package/dist/components/form/FormError.d.ts.map +1 -0
- package/dist/components/form/FormError.js +8 -0
- package/dist/components/form/FormError.js.map +1 -0
- package/dist/components/form/FormField.d.ts +17 -0
- package/dist/components/form/FormField.d.ts.map +1 -0
- package/dist/components/form/FormField.js +36 -0
- package/dist/components/form/FormField.js.map +1 -0
- package/dist/components/form/FormInput.d.ts +10 -0
- package/dist/components/form/FormInput.d.ts.map +1 -0
- package/dist/components/form/FormInput.js +36 -0
- package/dist/components/form/FormInput.js.map +1 -0
- package/dist/components/form/FormInputGroup.d.ts +8 -0
- package/dist/components/form/FormInputGroup.d.ts.map +1 -0
- package/dist/components/form/FormInputGroup.js +18 -0
- package/dist/components/form/FormInputGroup.js.map +1 -0
- package/dist/components/form/FormLabel.d.ts +4 -0
- package/dist/components/form/FormLabel.d.ts.map +1 -0
- package/dist/components/form/FormLabel.js +7 -0
- package/dist/components/form/FormLabel.js.map +1 -0
- package/dist/components/form/FormRadioGroup.d.ts +11 -0
- package/dist/components/form/FormRadioGroup.d.ts.map +1 -0
- package/dist/components/form/FormRadioGroup.js +24 -0
- package/dist/components/form/FormRadioGroup.js.map +1 -0
- package/dist/components/form/FormRoot.d.ts +13 -0
- package/dist/components/form/FormRoot.d.ts.map +1 -0
- package/dist/components/form/FormRoot.js +19 -0
- package/dist/components/form/FormRoot.js.map +1 -0
- package/dist/components/form/FormTextarea.d.ts +7 -0
- package/dist/components/form/FormTextarea.d.ts.map +1 -0
- package/dist/components/form/FormTextarea.js +27 -0
- package/dist/components/form/FormTextarea.js.map +1 -0
- package/dist/components/form/Input.d.ts +17 -0
- package/dist/components/form/Input.d.ts.map +1 -0
- package/dist/components/form/Input.js +32 -0
- package/dist/components/form/Input.js.map +1 -0
- package/dist/components/form/InputGroup.context.d.ts +10 -0
- package/dist/components/form/InputGroup.context.d.ts.map +1 -0
- package/dist/components/form/InputGroup.context.js +8 -0
- package/dist/components/form/InputGroup.context.js.map +1 -0
- package/dist/components/form/InputGroup.d.ts +31 -0
- package/dist/components/form/InputGroup.d.ts.map +1 -0
- package/dist/components/form/InputGroup.js +123 -0
- package/dist/components/form/InputGroup.js.map +1 -0
- package/dist/components/form/RadioGroup.d.ts +29 -0
- package/dist/components/form/RadioGroup.d.ts.map +1 -0
- package/dist/components/form/RadioGroup.js +42 -0
- package/dist/components/form/RadioGroup.js.map +1 -0
- package/dist/components/form/Textarea.d.ts +19 -0
- package/dist/components/form/Textarea.d.ts.map +1 -0
- package/dist/components/form/Textarea.js +26 -0
- package/dist/components/form/Textarea.js.map +1 -0
- package/dist/entries/card.d.ts +1 -1
- package/dist/entries/card.d.ts.map +1 -1
- package/dist/entries/checkbox.d.ts +3 -0
- package/dist/entries/checkbox.d.ts.map +1 -0
- package/dist/entries/checkbox.js +2 -0
- package/dist/entries/checkbox.js.map +1 -0
- package/dist/entries/flex.d.ts +3 -0
- package/dist/entries/flex.d.ts.map +1 -0
- package/dist/entries/flex.js +2 -0
- package/dist/entries/flex.js.map +1 -0
- package/dist/entries/form.d.ts +3 -0
- package/dist/entries/form.d.ts.map +1 -0
- package/dist/entries/form.js +2 -0
- package/dist/entries/form.js.map +1 -0
- package/dist/entries/grid.d.ts +3 -0
- package/dist/entries/grid.d.ts.map +1 -0
- package/dist/entries/grid.js +2 -0
- package/dist/entries/grid.js.map +1 -0
- package/dist/entries/input-group.d.ts +3 -0
- package/dist/entries/input-group.d.ts.map +1 -0
- package/dist/entries/input-group.js +2 -0
- package/dist/entries/input-group.js.map +1 -0
- package/dist/entries/input.d.ts +3 -0
- package/dist/entries/input.d.ts.map +1 -0
- package/dist/entries/input.js +2 -0
- package/dist/entries/input.js.map +1 -0
- package/dist/entries/radio-group.d.ts +3 -0
- package/dist/entries/radio-group.d.ts.map +1 -0
- package/dist/entries/radio-group.js +2 -0
- package/dist/entries/radio-group.js.map +1 -0
- package/dist/entries/separator.d.ts +3 -0
- package/dist/entries/separator.d.ts.map +1 -0
- package/dist/entries/separator.js +2 -0
- package/dist/entries/separator.js.map +1 -0
- package/dist/entries/spinner.d.ts +3 -0
- package/dist/entries/spinner.d.ts.map +1 -0
- package/dist/entries/spinner.js +2 -0
- package/dist/entries/spinner.js.map +1 -0
- package/dist/entries/textarea.d.ts +3 -0
- package/dist/entries/textarea.d.ts.map +1 -0
- package/dist/entries/textarea.js +2 -0
- package/dist/entries/textarea.js.map +1 -0
- package/dist/entries/theme.d.ts +3 -0
- package/dist/entries/theme.d.ts.map +1 -0
- package/dist/entries/theme.js +2 -0
- package/dist/entries/theme.js.map +1 -0
- package/dist/entries/x-stack.d.ts +3 -0
- package/dist/entries/x-stack.d.ts.map +1 -0
- package/dist/entries/x-stack.js +2 -0
- package/dist/entries/x-stack.js.map +1 -0
- package/dist/entries/y-stack.d.ts +3 -0
- package/dist/entries/y-stack.d.ts.map +1 -0
- package/dist/entries/y-stack.js +2 -0
- package/dist/entries/y-stack.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/cn.d.ts.map +1 -1
- package/dist/lib/cn.js +13 -1
- package/dist/lib/cn.js.map +1 -1
- package/package.json +10 -3
- package/src/base.css +7 -2
- package/src/components/Badge.stories.tsx +51 -6
- package/src/components/Badge.test.tsx +15 -0
- package/src/components/Badge.tsx +73 -17
- package/src/components/Button.stories.tsx +96 -8
- package/src/components/Button.test.tsx +69 -0
- package/src/components/Button.tsx +159 -18
- package/src/components/Card.stories.tsx +17 -4
- package/src/components/Card.test.tsx +24 -1
- package/src/components/Card.tsx +71 -13
- package/src/components/Flex.stories.tsx +58 -0
- package/src/components/Flex.test.tsx +57 -0
- package/src/components/Flex.tsx +52 -0
- package/src/components/Grid.stories.tsx +55 -0
- package/src/components/Grid.test.tsx +140 -0
- package/src/components/Grid.tsx +166 -0
- package/src/components/Separator.stories.tsx +39 -0
- package/src/components/Separator.test.tsx +25 -0
- package/src/components/Separator.tsx +30 -0
- package/src/components/Spinner.stories.tsx +35 -0
- package/src/components/Spinner.test.tsx +30 -0
- package/src/components/Spinner.tsx +75 -0
- package/src/components/Text.stories.tsx +52 -2
- package/src/components/Text.test.tsx +35 -3
- package/src/components/Text.tsx +42 -12
- package/src/components/XStack.stories.tsx +46 -0
- package/src/components/XStack.test.tsx +13 -0
- package/src/components/XStack.tsx +7 -0
- package/src/components/YStack.stories.tsx +46 -0
- package/src/components/YStack.test.tsx +13 -0
- package/src/components/YStack.tsx +7 -0
- package/src/components/_internal/InputFrame.tsx +50 -0
- package/src/components/_internal/Story.tsx +34 -0
- package/src/components/_internal/StoryItem.tsx +24 -0
- package/src/components/_internal/layout.ts +103 -0
- package/src/components/_internal/useControllableState.ts +25 -0
- package/src/components/form/Checkbox.stories.tsx +67 -0
- package/src/components/form/Checkbox.test.tsx +88 -0
- package/src/components/form/Checkbox.tsx +141 -0
- package/src/components/form/Form.context.ts +91 -0
- package/src/components/form/Form.stories.tsx +152 -0
- package/src/components/form/Form.test.tsx +195 -0
- package/src/components/form/Form.tsx +51 -0
- package/src/components/form/FormCheckbox.tsx +42 -0
- package/src/components/form/FormDescription.tsx +14 -0
- package/src/components/form/FormError.tsx +17 -0
- package/src/components/form/FormField.tsx +82 -0
- package/src/components/form/FormInput.tsx +70 -0
- package/src/components/form/FormInputGroup.tsx +30 -0
- package/src/components/form/FormLabel.tsx +17 -0
- package/src/components/form/FormRadioGroup.tsx +48 -0
- package/src/components/form/FormRoot.tsx +41 -0
- package/src/components/form/FormTextarea.tsx +56 -0
- package/src/components/form/Input.stories.tsx +105 -0
- package/src/components/form/Input.test.tsx +76 -0
- package/src/components/form/Input.tsx +81 -0
- package/src/components/form/InputGroup.context.ts +11 -0
- package/src/components/form/InputGroup.stories.tsx +129 -0
- package/src/components/form/InputGroup.test.tsx +95 -0
- package/src/components/form/InputGroup.tsx +266 -0
- package/src/components/form/RadioGroup.stories.tsx +62 -0
- package/src/components/form/RadioGroup.test.tsx +98 -0
- package/src/components/form/RadioGroup.tsx +151 -0
- package/src/components/form/Textarea.stories.tsx +61 -0
- package/src/components/form/Textarea.test.tsx +35 -0
- package/src/components/form/Textarea.tsx +74 -0
- package/src/entries/card.ts +2 -0
- package/src/entries/checkbox.ts +2 -0
- package/src/entries/flex.ts +2 -0
- package/src/entries/form.ts +13 -0
- package/src/entries/grid.ts +2 -0
- package/src/entries/input-group.ts +8 -0
- package/src/entries/input.ts +2 -0
- package/src/entries/radio-group.ts +5 -0
- package/src/entries/separator.ts +2 -0
- package/src/entries/spinner.ts +2 -0
- package/src/entries/textarea.ts +2 -0
- package/src/entries/theme.ts +7 -0
- package/src/entries/x-stack.ts +2 -0
- package/src/entries/y-stack.ts +2 -0
- package/src/index.ts +13 -0
- package/src/lib/cn.test.ts +6 -0
- package/src/lib/cn.ts +14 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
|
+
|
|
3
|
+
import { Story } from './_internal/Story';
|
|
4
|
+
import { StoryItem as Item } from './_internal/StoryItem';
|
|
5
|
+
import { Grid } from './Grid';
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
component: Grid,
|
|
9
|
+
parameters: { layout: 'fullscreen' },
|
|
10
|
+
title: 'components/Grid',
|
|
11
|
+
} satisfies Meta<typeof Grid>;
|
|
12
|
+
|
|
13
|
+
export default meta;
|
|
14
|
+
type StoryDefinition = StoryObj<typeof meta>;
|
|
15
|
+
|
|
16
|
+
export const All: StoryDefinition = {
|
|
17
|
+
render: () => (
|
|
18
|
+
<Story.Layout className="max-w-6xl" title="Grid">
|
|
19
|
+
<Story.Section title="Columns and gaps">
|
|
20
|
+
<Grid columns={3} gap={4}>
|
|
21
|
+
{['One', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven'].map(
|
|
22
|
+
(label) => (
|
|
23
|
+
<Item className="min-h-20" key={label}>
|
|
24
|
+
{label}
|
|
25
|
+
</Item>
|
|
26
|
+
),
|
|
27
|
+
)}
|
|
28
|
+
</Grid>
|
|
29
|
+
</Story.Section>
|
|
30
|
+
|
|
31
|
+
<Story.Section title="Rows and column flow">
|
|
32
|
+
<Grid flow="column" gapX={4} gapY={2} rows={2}>
|
|
33
|
+
{['A', 'B', 'C', 'D', 'E', 'F'].map((label) => (
|
|
34
|
+
<Item className="min-h-20" key={label}>
|
|
35
|
+
{label}
|
|
36
|
+
</Item>
|
|
37
|
+
))}
|
|
38
|
+
</Grid>
|
|
39
|
+
</Story.Section>
|
|
40
|
+
|
|
41
|
+
<Story.Section title="Placement">
|
|
42
|
+
<Grid
|
|
43
|
+
className="border-border rounded-md border border-dashed p-4"
|
|
44
|
+
columns={3}
|
|
45
|
+
gap={4}
|
|
46
|
+
placeItems="center"
|
|
47
|
+
>
|
|
48
|
+
<Item className="min-h-40">Tall item</Item>
|
|
49
|
+
<Item>Centered</Item>
|
|
50
|
+
<Item className="flex-1">Full height</Item>
|
|
51
|
+
</Grid>
|
|
52
|
+
</Story.Section>
|
|
53
|
+
</Story.Layout>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { render, screen, within } from '@testing-library/react-native';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { Grid } from './Grid';
|
|
5
|
+
|
|
6
|
+
const LABELS = ['A', 'B', 'C', 'D', 'E'];
|
|
7
|
+
|
|
8
|
+
/** The labels of each grid row, empty cells as `''`. */
|
|
9
|
+
function gridRows() {
|
|
10
|
+
return screen
|
|
11
|
+
.getByTestId('grid')
|
|
12
|
+
.children.map((row) =>
|
|
13
|
+
typeof row === 'string'
|
|
14
|
+
? []
|
|
15
|
+
: row.children.map((cell) =>
|
|
16
|
+
typeof cell === 'string'
|
|
17
|
+
? cell
|
|
18
|
+
: (within(cell).queryByText(/./)?.props.children ?? ''),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function renderGrid(props: React.ComponentProps<typeof Grid>) {
|
|
24
|
+
return render(
|
|
25
|
+
<Grid testID="grid" {...props}>
|
|
26
|
+
{LABELS.map((label) => (
|
|
27
|
+
<Text key={label}>{label}</Text>
|
|
28
|
+
))}
|
|
29
|
+
</Grid>,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('Grid', () => {
|
|
34
|
+
it('fills rows first, keeping empty cells in the last row', async () => {
|
|
35
|
+
await renderGrid({ columns: 3 });
|
|
36
|
+
|
|
37
|
+
expect(gridRows()).toEqual([
|
|
38
|
+
['A', 'B', 'C'],
|
|
39
|
+
['D', 'E', ''],
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('fills columns first with flow="column"', async () => {
|
|
44
|
+
await renderGrid({ flow: 'column', rows: 2 });
|
|
45
|
+
|
|
46
|
+
expect(gridRows()).toEqual([
|
|
47
|
+
['A', 'C', 'E'],
|
|
48
|
+
['B', 'D', ''],
|
|
49
|
+
]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('keeps the rows it is given, even empty ones', async () => {
|
|
53
|
+
await renderGrid({ flow: 'column', rows: 3 });
|
|
54
|
+
|
|
55
|
+
expect(gridRows()).toEqual([
|
|
56
|
+
['A', 'D'],
|
|
57
|
+
['B', 'E'],
|
|
58
|
+
['C', ''],
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
await renderGrid({ columns: 2, rows: 4 });
|
|
62
|
+
|
|
63
|
+
expect(gridRows()).toEqual([
|
|
64
|
+
['A', 'B'],
|
|
65
|
+
['C', 'D'],
|
|
66
|
+
['E', ''],
|
|
67
|
+
['', ''],
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('places the dense flows like row and column', async () => {
|
|
72
|
+
await renderGrid({ flow: 'column-dense', rows: 2 });
|
|
73
|
+
|
|
74
|
+
expect(gridRows()).toEqual([
|
|
75
|
+
['A', 'C', 'E'],
|
|
76
|
+
['B', 'D', ''],
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('gives each child of a fragment its own cell', async () => {
|
|
81
|
+
await render(
|
|
82
|
+
<Grid columns={2} testID="grid">
|
|
83
|
+
<>
|
|
84
|
+
<Text>A</Text>
|
|
85
|
+
<Text>B</Text>
|
|
86
|
+
</>
|
|
87
|
+
<Text>C</Text>
|
|
88
|
+
</Grid>,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(gridRows()).toEqual([
|
|
92
|
+
['A', 'B'],
|
|
93
|
+
['C', ''],
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('distributes rows and cells with placeContent', async () => {
|
|
98
|
+
await renderGrid({ columns: 2, justify: 'end', placeContent: 'stretch' });
|
|
99
|
+
|
|
100
|
+
const grid = screen.getByTestId('grid');
|
|
101
|
+
expect(grid).toHaveProp('className', 'justify-start');
|
|
102
|
+
expect(grid.children[0]).toHaveProp(
|
|
103
|
+
'className',
|
|
104
|
+
'flex-row justify-start grow',
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
await renderGrid({ columns: 2, justify: 'end' });
|
|
108
|
+
|
|
109
|
+
expect(screen.getByTestId('grid').children[0]).toHaveProp(
|
|
110
|
+
'className',
|
|
111
|
+
'flex-row justify-end',
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('stacks its children in one column by default', async () => {
|
|
116
|
+
await renderGrid({});
|
|
117
|
+
|
|
118
|
+
expect(gridRows()).toEqual([['A'], ['B'], ['C'], ['D'], ['E']]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('spaces rows with gapY and columns with gapX', async () => {
|
|
122
|
+
await renderGrid({ columns: 2, gap: 4, gapX: 2 });
|
|
123
|
+
|
|
124
|
+
const grid = screen.getByTestId('grid');
|
|
125
|
+
expect(grid).toHaveProp('className', 'gap-y-4');
|
|
126
|
+
expect(grid.children[0]).toHaveProp('className', 'flex-row gap-x-2');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('places items in their cells', async () => {
|
|
130
|
+
await renderGrid({ align: 'end', columns: 2 });
|
|
131
|
+
const cell = screen.getByText('A').parent;
|
|
132
|
+
expect(cell).toHaveProp('className', 'flex-1 justify-end');
|
|
133
|
+
|
|
134
|
+
await renderGrid({ align: 'end', columns: 2, placeItems: 'center' });
|
|
135
|
+
expect(screen.getByText('A').parent).toHaveProp(
|
|
136
|
+
'className',
|
|
137
|
+
'flex-1 items-center justify-center',
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Children, Fragment, isValidElement, type ReactNode } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { cn } from '../lib/cn';
|
|
5
|
+
import {
|
|
6
|
+
gapXClasses,
|
|
7
|
+
gapYClasses,
|
|
8
|
+
type LayoutAlign,
|
|
9
|
+
type LayoutGap,
|
|
10
|
+
type LayoutJustify,
|
|
11
|
+
} from './_internal/layout';
|
|
12
|
+
|
|
13
|
+
// React Native has no CSS grid, so `Grid` places its children itself: one row
|
|
14
|
+
// of equal `flex-1` cells per grid row, spaced by `gapX`, and the rows stacked
|
|
15
|
+
// and spaced by `gapY`. Empty cells and rows keep their size, so every column
|
|
16
|
+
// stays the same width and `rows` keeps its tracks, as on the web.
|
|
17
|
+
|
|
18
|
+
type GridFlow = 'column' | 'column-dense' | 'dense' | 'row' | 'row-dense';
|
|
19
|
+
type GridPlaceContent = 'baseline' | 'stretch' | LayoutJustify;
|
|
20
|
+
type GridTrackCount = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
|
|
21
|
+
|
|
22
|
+
// Cells are columns: `items-*` places an item across the cell's width and
|
|
23
|
+
// `justify-*` down its height. A cell can't stretch an item vertically, so
|
|
24
|
+
// `stretch` and `baseline` place it at the top; give the item `flex-1` to fill
|
|
25
|
+
// its cell's height.
|
|
26
|
+
const horizontalClasses = {
|
|
27
|
+
baseline: 'items-start',
|
|
28
|
+
center: 'items-center',
|
|
29
|
+
end: 'items-end',
|
|
30
|
+
start: 'items-start',
|
|
31
|
+
stretch: 'items-stretch',
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
const verticalClasses = {
|
|
35
|
+
baseline: 'justify-start',
|
|
36
|
+
center: 'justify-center',
|
|
37
|
+
end: 'justify-end',
|
|
38
|
+
start: 'justify-start',
|
|
39
|
+
stretch: 'justify-start',
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
// Distributes the rows down the grid and the cells along each row, like CSS
|
|
43
|
+
// `align-content` and `justify-content`. Cells share each row's width, so only
|
|
44
|
+
// a grid taller than its rows shows the difference; `stretch` grows the rows
|
|
45
|
+
// into that height instead.
|
|
46
|
+
const contentClasses = {
|
|
47
|
+
around: 'justify-around',
|
|
48
|
+
baseline: 'justify-start',
|
|
49
|
+
between: 'justify-between',
|
|
50
|
+
center: 'justify-center',
|
|
51
|
+
end: 'justify-end',
|
|
52
|
+
evenly: 'justify-evenly',
|
|
53
|
+
start: 'justify-start',
|
|
54
|
+
stretch: 'justify-start',
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
export type GridProps = React.ComponentProps<typeof View> & {
|
|
58
|
+
/**
|
|
59
|
+
* Places each item down its cell, like CSS `align-items`. `stretch` and
|
|
60
|
+
* `baseline` place it at the top: give the item `flex-1` to fill the cell.
|
|
61
|
+
*/
|
|
62
|
+
align?: LayoutAlign;
|
|
63
|
+
columns?: GridTrackCount;
|
|
64
|
+
/**
|
|
65
|
+
* Fills rows first (`row`) or columns first (`column`). Items can't span
|
|
66
|
+
* cells on native, so the `dense` flows place them like `row` and `column`.
|
|
67
|
+
*/
|
|
68
|
+
flow?: GridFlow;
|
|
69
|
+
gap?: LayoutGap;
|
|
70
|
+
gapX?: LayoutGap;
|
|
71
|
+
gapY?: LayoutGap;
|
|
72
|
+
/** Distributes the cells along each row, like CSS `justify-content`. */
|
|
73
|
+
justify?: LayoutJustify;
|
|
74
|
+
/** Distributes the rows and the cells, like CSS `place-content`. */
|
|
75
|
+
placeContent?: GridPlaceContent;
|
|
76
|
+
/** Places each item across and down its cell, like CSS `place-items`. */
|
|
77
|
+
placeItems?: LayoutAlign;
|
|
78
|
+
rows?: GridTrackCount;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Lays its children out in equal columns, like `Grid` in @pickle-ui/react.
|
|
83
|
+
* Items fill their cell's width by default and the cells of a row share its
|
|
84
|
+
* height.
|
|
85
|
+
*/
|
|
86
|
+
export function Grid({
|
|
87
|
+
align,
|
|
88
|
+
children,
|
|
89
|
+
className,
|
|
90
|
+
columns,
|
|
91
|
+
flow = 'row',
|
|
92
|
+
gap,
|
|
93
|
+
gapX = gap,
|
|
94
|
+
gapY = gap,
|
|
95
|
+
justify,
|
|
96
|
+
placeContent,
|
|
97
|
+
placeItems,
|
|
98
|
+
rows,
|
|
99
|
+
...props
|
|
100
|
+
}: GridProps) {
|
|
101
|
+
const items = flattenChildren(children);
|
|
102
|
+
const byColumn = flow === 'column' || flow === 'column-dense';
|
|
103
|
+
const rowCount = byColumn
|
|
104
|
+
? (rows ?? 1)
|
|
105
|
+
: Math.max(rows ?? 0, Math.ceil(items.length / (columns ?? 1)));
|
|
106
|
+
const columnCount = byColumn
|
|
107
|
+
? Math.max(columns ?? 1, Math.ceil(items.length / rowCount))
|
|
108
|
+
: (columns ?? 1);
|
|
109
|
+
const vertical = placeItems ?? align;
|
|
110
|
+
const distribution = placeContent ?? justify;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<View
|
|
114
|
+
className={cn(
|
|
115
|
+
gapY === undefined ? undefined : gapYClasses[gapY],
|
|
116
|
+
placeContent === undefined ? undefined : contentClasses[placeContent],
|
|
117
|
+
className,
|
|
118
|
+
)}
|
|
119
|
+
{...props}
|
|
120
|
+
>
|
|
121
|
+
{Array.from({ length: rowCount }, (_, row) => (
|
|
122
|
+
<View
|
|
123
|
+
className={cn(
|
|
124
|
+
'flex-row',
|
|
125
|
+
gapX === undefined ? undefined : gapXClasses[gapX],
|
|
126
|
+
distribution === undefined
|
|
127
|
+
? undefined
|
|
128
|
+
: contentClasses[distribution],
|
|
129
|
+
placeContent === 'stretch' ? 'grow' : undefined,
|
|
130
|
+
)}
|
|
131
|
+
key={row}
|
|
132
|
+
>
|
|
133
|
+
{Array.from({ length: columnCount }, (_, column) => (
|
|
134
|
+
<View
|
|
135
|
+
className={cn(
|
|
136
|
+
'flex-1',
|
|
137
|
+
placeItems === undefined
|
|
138
|
+
? undefined
|
|
139
|
+
: horizontalClasses[placeItems],
|
|
140
|
+
vertical === undefined ? undefined : verticalClasses[vertical],
|
|
141
|
+
)}
|
|
142
|
+
key={column}
|
|
143
|
+
>
|
|
144
|
+
{
|
|
145
|
+
items[
|
|
146
|
+
byColumn
|
|
147
|
+
? column * rowCount + row
|
|
148
|
+
: row * columnCount + column
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
</View>
|
|
152
|
+
))}
|
|
153
|
+
</View>
|
|
154
|
+
))}
|
|
155
|
+
</View>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The grid items, with fragments opened up as on the web. */
|
|
160
|
+
function flattenChildren(children: ReactNode): ReactNode[] {
|
|
161
|
+
return Children.toArray(children).flatMap((child) =>
|
|
162
|
+
isValidElement<{ children?: ReactNode }>(child) && child.type === Fragment
|
|
163
|
+
? flattenChildren(child.props.children)
|
|
164
|
+
: [child],
|
|
165
|
+
);
|
|
166
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
|
+
|
|
3
|
+
import { Story } from './_internal/Story';
|
|
4
|
+
import { Separator } from './Separator';
|
|
5
|
+
import { Text } from './Text';
|
|
6
|
+
import { XStack } from './XStack';
|
|
7
|
+
import { YStack } from './YStack';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
component: Separator,
|
|
11
|
+
parameters: { layout: 'fullscreen' },
|
|
12
|
+
title: 'components/Separator',
|
|
13
|
+
} satisfies Meta<typeof Separator>;
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
type StoryDefinition = StoryObj<typeof meta>;
|
|
17
|
+
|
|
18
|
+
export const All: StoryDefinition = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<Story.Layout className="max-w-3xl" title="Separator">
|
|
21
|
+
<Story.Section title="Horizontal">
|
|
22
|
+
<YStack className="max-w-sm" gap={3}>
|
|
23
|
+
<Text>Account</Text>
|
|
24
|
+
<Separator />
|
|
25
|
+
<Text tone="muted">Manage your profile and preferences.</Text>
|
|
26
|
+
</YStack>
|
|
27
|
+
</Story.Section>
|
|
28
|
+
<Story.Section title="Vertical">
|
|
29
|
+
<XStack align="center" className="h-5" gap={3}>
|
|
30
|
+
<Text>Docs</Text>
|
|
31
|
+
<Separator orientation="vertical" />
|
|
32
|
+
<Text>Components</Text>
|
|
33
|
+
<Separator orientation="vertical" />
|
|
34
|
+
<Text>Theming</Text>
|
|
35
|
+
</XStack>
|
|
36
|
+
</Story.Section>
|
|
37
|
+
</Story.Layout>
|
|
38
|
+
),
|
|
39
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react-native';
|
|
2
|
+
|
|
3
|
+
import { Separator } from './Separator';
|
|
4
|
+
|
|
5
|
+
describe('Separator', () => {
|
|
6
|
+
it('renders a horizontal separator by default', async () => {
|
|
7
|
+
await render(<Separator testID="separator" />);
|
|
8
|
+
|
|
9
|
+
const separator = screen.getByTestId('separator');
|
|
10
|
+
expect(separator).toHaveProp('role', 'separator');
|
|
11
|
+
expect(separator).toHaveProp(
|
|
12
|
+
'className',
|
|
13
|
+
expect.stringContaining('h-px w-full'),
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders a vertical separator', async () => {
|
|
18
|
+
await render(<Separator orientation="vertical" testID="separator" />);
|
|
19
|
+
|
|
20
|
+
expect(screen.getByTestId('separator')).toHaveProp(
|
|
21
|
+
'className',
|
|
22
|
+
expect.stringContaining('w-px self-stretch'),
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { View } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../lib/cn';
|
|
4
|
+
|
|
5
|
+
export type SeparatorProps = React.ComponentProps<typeof View> & {
|
|
6
|
+
orientation?: 'horizontal' | 'vertical';
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A 1px rule in the border color, like `Separator` in @pickle-ui/react. Its
|
|
11
|
+
* `separator` role reaches screen readers on the web; iOS and Android have no
|
|
12
|
+
* separator trait, so they skip it like any non-focusable view.
|
|
13
|
+
*/
|
|
14
|
+
export function Separator({
|
|
15
|
+
className,
|
|
16
|
+
orientation = 'horizontal',
|
|
17
|
+
...props
|
|
18
|
+
}: SeparatorProps) {
|
|
19
|
+
return (
|
|
20
|
+
<View
|
|
21
|
+
className={cn(
|
|
22
|
+
'bg-border shrink-0',
|
|
23
|
+
orientation === 'horizontal' ? 'h-px w-full' : 'w-px self-stretch',
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
role="separator"
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
|
+
|
|
3
|
+
import { Story } from './_internal/Story';
|
|
4
|
+
import { Spinner } from './Spinner';
|
|
5
|
+
import { Text } from './Text';
|
|
6
|
+
import { XStack } from './XStack';
|
|
7
|
+
|
|
8
|
+
const meta = {
|
|
9
|
+
component: Spinner,
|
|
10
|
+
parameters: { layout: 'fullscreen' },
|
|
11
|
+
title: 'components/Spinner',
|
|
12
|
+
} satisfies Meta<typeof Spinner>;
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
type StoryDefinition = StoryObj<typeof meta>;
|
|
16
|
+
|
|
17
|
+
export const All: StoryDefinition = {
|
|
18
|
+
render: () => (
|
|
19
|
+
<Story.Layout className="max-w-3xl" title="Spinner">
|
|
20
|
+
<Story.Section title="Sizes">
|
|
21
|
+
<XStack align="center" gap={6}>
|
|
22
|
+
<Spinner className="size-4" />
|
|
23
|
+
<Spinner className="size-6" />
|
|
24
|
+
<Spinner className="size-8" />
|
|
25
|
+
</XStack>
|
|
26
|
+
</Story.Section>
|
|
27
|
+
<Story.Section title="With a label">
|
|
28
|
+
<XStack align="center" gap={2}>
|
|
29
|
+
<Spinner aria-hidden className="text-muted-foreground size-4" />
|
|
30
|
+
<Text tone="muted">Loading your workspace…</Text>
|
|
31
|
+
</XStack>
|
|
32
|
+
</Story.Section>
|
|
33
|
+
</Story.Layout>
|
|
34
|
+
),
|
|
35
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react-native';
|
|
2
|
+
|
|
3
|
+
import { Spinner } from './Spinner';
|
|
4
|
+
|
|
5
|
+
describe('Spinner', () => {
|
|
6
|
+
it('announces that something is loading', async () => {
|
|
7
|
+
await render(<Spinner />);
|
|
8
|
+
|
|
9
|
+
expect(
|
|
10
|
+
screen.getByRole('progressbar', { name: 'Loading' }),
|
|
11
|
+
).toBeOnTheScreen();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('accepts a custom accessible name', async () => {
|
|
15
|
+
await render(<Spinner accessibilityLabel="Saving" />);
|
|
16
|
+
|
|
17
|
+
expect(
|
|
18
|
+
screen.getByRole('progressbar', { name: 'Saving' }),
|
|
19
|
+
).toBeOnTheScreen();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('is decorative when aria-hidden', async () => {
|
|
23
|
+
await render(<Spinner aria-hidden testID="spinner" />);
|
|
24
|
+
|
|
25
|
+
expect(screen.queryByRole('progressbar')).toBeNull();
|
|
26
|
+
expect(
|
|
27
|
+
screen.getByTestId('spinner', { includeHiddenElements: true }),
|
|
28
|
+
).not.toHaveProp('accessibilityLabel');
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Animated, Easing, Platform, View } from 'react-native';
|
|
3
|
+
import { useResolveClassNames } from 'uniwind';
|
|
4
|
+
|
|
5
|
+
import { cn } from '../lib/cn';
|
|
6
|
+
|
|
7
|
+
export type SpinnerProps = Omit<
|
|
8
|
+
React.ComponentProps<typeof View>,
|
|
9
|
+
'children'
|
|
10
|
+
> & {
|
|
11
|
+
/** Hides the spinner from screen readers, when visible text already says what is loading. */
|
|
12
|
+
'aria-hidden'?: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The web `Spinner`'s look on native: a three-quarter ring turning once a
|
|
17
|
+
* second, drawn inside a `size-6` box like the web icon. Size it with
|
|
18
|
+
* `size-*` and color it with `text-*`, as on the web. Announced as "Loading"
|
|
19
|
+
* by default; `accessibilityLabel` replaces the name and `aria-hidden` makes
|
|
20
|
+
* it decorative.
|
|
21
|
+
*/
|
|
22
|
+
export function Spinner({
|
|
23
|
+
accessibilityLabel = 'Loading',
|
|
24
|
+
'aria-hidden': ariaHidden = false,
|
|
25
|
+
className,
|
|
26
|
+
style,
|
|
27
|
+
...props
|
|
28
|
+
}: SpinnerProps) {
|
|
29
|
+
const [turn] = useState(() => new Animated.Value(0));
|
|
30
|
+
const box = cn('text-foreground size-6', className);
|
|
31
|
+
// React Native has no `currentColor`: the ring reads the `text-*` color, and
|
|
32
|
+
// its stroke follows the box like the icon's (2px at 24px).
|
|
33
|
+
const { color, width } = useResolveClassNames(box);
|
|
34
|
+
const strokeWidth = typeof width === 'number' ? width / 12 : 2;
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const loop = Animated.loop(
|
|
38
|
+
Animated.timing(turn, {
|
|
39
|
+
duration: 1000,
|
|
40
|
+
easing: Easing.linear,
|
|
41
|
+
toValue: 1,
|
|
42
|
+
// react-native-web has no native driver and warns when asked for it.
|
|
43
|
+
useNativeDriver: Platform.OS !== 'web',
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
loop.start();
|
|
47
|
+
return () => loop.stop();
|
|
48
|
+
}, [turn]);
|
|
49
|
+
|
|
50
|
+
const rotate = turn.interpolate({
|
|
51
|
+
inputRange: [0, 1],
|
|
52
|
+
outputRange: ['0deg', '360deg'],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Animated.View
|
|
57
|
+
accessibilityLabel={ariaHidden ? undefined : accessibilityLabel}
|
|
58
|
+
accessibilityRole={ariaHidden ? undefined : 'progressbar'}
|
|
59
|
+
accessible={!ariaHidden}
|
|
60
|
+
aria-hidden={ariaHidden}
|
|
61
|
+
className={cn('items-center justify-center', box)}
|
|
62
|
+
style={[style, { transform: [{ rotate }] }]}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
<View
|
|
66
|
+
className="size-3/4 rounded-full"
|
|
67
|
+
style={{
|
|
68
|
+
borderColor: typeof color === 'string' ? color : undefined,
|
|
69
|
+
borderTopColor: 'transparent',
|
|
70
|
+
borderWidth: strokeWidth,
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
</Animated.View>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
2
|
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
|
|
3
5
|
import { type ComponentMatrixCellProps, Story } from './_internal/Story';
|
|
4
6
|
import { Text, type TextProps } from './Text';
|
|
5
7
|
|
|
@@ -16,6 +18,7 @@ type StoryDefinition = StoryObj<typeof meta>;
|
|
|
16
18
|
|
|
17
19
|
type TextTone = NonNullable<TextProps['tone']>;
|
|
18
20
|
type TextVariant = NonNullable<TextProps['variant']>;
|
|
21
|
+
type TextWeight = NonNullable<TextProps['weight']>;
|
|
19
22
|
|
|
20
23
|
const VARIANT_ROWS: { key: TextVariant; label: string }[] = [
|
|
21
24
|
{ key: 'h1', label: 'H1' },
|
|
@@ -25,6 +28,7 @@ const VARIANT_ROWS: { key: TextVariant; label: string }[] = [
|
|
|
25
28
|
{ key: 'lead', label: 'Lead' },
|
|
26
29
|
{ key: 'body', label: 'Body' },
|
|
27
30
|
{ key: 'small', label: 'Small' },
|
|
31
|
+
{ key: 'code', label: 'Code' },
|
|
28
32
|
];
|
|
29
33
|
|
|
30
34
|
const TONE_COLUMNS: { key: 'default' | TextTone; label: string }[] = [
|
|
@@ -34,6 +38,11 @@ const TONE_COLUMNS: { key: 'default' | TextTone; label: string }[] = [
|
|
|
34
38
|
{ key: 'destructive', label: 'Destructive' },
|
|
35
39
|
];
|
|
36
40
|
|
|
41
|
+
const WEIGHT_ROWS: { key: TextWeight; label: string }[] = [
|
|
42
|
+
{ key: 'normal', label: 'Normal' },
|
|
43
|
+
{ key: 'bold', label: 'Bold' },
|
|
44
|
+
];
|
|
45
|
+
|
|
37
46
|
function ToneCell({ column, row }: ComponentMatrixCellProps) {
|
|
38
47
|
return (
|
|
39
48
|
<Text
|
|
@@ -45,16 +54,57 @@ function ToneCell({ column, row }: ComponentMatrixCellProps) {
|
|
|
45
54
|
);
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
function WeightCell({ row }: ComponentMatrixCellProps) {
|
|
58
|
+
return (
|
|
59
|
+
<Text variant="body" weight={row.key as TextWeight}>
|
|
60
|
+
{row.label} weight
|
|
61
|
+
</Text>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
48
65
|
export const All: StoryDefinition = {
|
|
49
66
|
render: () => (
|
|
50
67
|
<Story.Layout className="max-w-6xl" title="Text">
|
|
51
|
-
<Story.Section title="
|
|
68
|
+
<Story.Section title="Variant scale">
|
|
69
|
+
<View className="gap-4">
|
|
70
|
+
{VARIANT_ROWS.map(({ key, label }) => (
|
|
71
|
+
<View className="flex-row items-baseline gap-4" key={key}>
|
|
72
|
+
<Text className="w-12" tone="muted" variant="small">
|
|
73
|
+
{label}
|
|
74
|
+
</Text>
|
|
75
|
+
<Text className="flex-1" variant={key}>
|
|
76
|
+
{key === 'code'
|
|
77
|
+
? 'npx expo install @pickle-ui/react-native'
|
|
78
|
+
: 'The quick brown fox jumps over the lazy dog.'}
|
|
79
|
+
</Text>
|
|
80
|
+
</View>
|
|
81
|
+
))}
|
|
82
|
+
</View>
|
|
83
|
+
</Story.Section>
|
|
84
|
+
|
|
85
|
+
<Story.Section title="Tone (color axis)">
|
|
52
86
|
<Story.Matrix
|
|
53
87
|
Cell={ToneCell}
|
|
54
88
|
columns={TONE_COLUMNS}
|
|
55
|
-
rows={VARIANT_ROWS}
|
|
89
|
+
rows={VARIANT_ROWS.slice(0, 4)}
|
|
90
|
+
/>
|
|
91
|
+
</Story.Section>
|
|
92
|
+
|
|
93
|
+
<Story.Section title="Weight">
|
|
94
|
+
<Story.Matrix
|
|
95
|
+
Cell={WeightCell}
|
|
96
|
+
columns={[{ key: 'body', label: 'Body' }]}
|
|
97
|
+
rows={WEIGHT_ROWS}
|
|
56
98
|
/>
|
|
57
99
|
</Story.Section>
|
|
100
|
+
|
|
101
|
+
<Story.Section title="Truncate">
|
|
102
|
+
<View className="border-border w-64 rounded-md border p-3">
|
|
103
|
+
<Text truncate>
|
|
104
|
+
A project name long enough to run past the edge of its container
|
|
105
|
+
</Text>
|
|
106
|
+
</View>
|
|
107
|
+
</Story.Section>
|
|
58
108
|
</Story.Layout>
|
|
59
109
|
),
|
|
60
110
|
};
|