@utilitywarehouse/hearth-react-native 0.14.1 → 0.15.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +1 -1
- package/CHANGELOG.md +55 -0
- package/build/components/BottomSheet/index.d.ts +5 -5
- package/build/components/BottomSheet/index.js +4 -4
- package/build/components/Checkbox/Checkbox.d.ts +1 -1
- package/build/components/Checkbox/Checkbox.js +2 -4
- package/build/components/DescriptionList/DescriptionListItem.js +8 -1
- package/build/components/HTMLElements/ListItem.d.ts +9 -1
- package/build/components/HTMLElements/ListItem.js +1 -2
- package/build/components/HTMLElements/OrderedList.d.ts +3 -2
- package/build/components/HTMLElements/OrderedList.js +29 -4
- package/build/components/HTMLElements/UnorderedList.d.ts +3 -2
- package/build/components/HTMLElements/UnorderedList.js +29 -4
- package/build/components/Helper/Helper.js +1 -1
- package/build/components/Helper/HelperText.js +1 -0
- package/build/components/Input/Input.js +2 -2
- package/build/components/Modal/Modal.d.ts +1 -1
- package/build/components/Modal/Modal.js +49 -4
- package/build/components/Modal/Modal.props.d.ts +1 -0
- package/build/components/PillGroup/PillGroup.props.d.ts +19 -7
- package/package.json +1 -1
- package/src/components/BottomSheet/index.ts +7 -5
- package/src/components/Checkbox/Checkbox.tsx +7 -2
- package/src/components/DescriptionList/DescriptionListItem.tsx +8 -1
- package/src/components/HTMLElements/ListItem.tsx +11 -3
- package/src/components/HTMLElements/Lists.docs.mdx +64 -16
- package/src/components/HTMLElements/OrderedList.stories.tsx +33 -2
- package/src/components/HTMLElements/OrderedList.tsx +54 -6
- package/src/components/HTMLElements/UnorderedList.stories.tsx +63 -5
- package/src/components/HTMLElements/UnorderedList.tsx +50 -6
- package/src/components/Helper/Helper.tsx +1 -1
- package/src/components/Helper/HelperText.tsx +1 -0
- package/src/components/Input/Input.tsx +2 -0
- package/src/components/Modal/Modal.props.ts +1 -0
- package/src/components/Modal/Modal.tsx +86 -23
- package/src/components/PillGroup/PillGroup.props.ts +25 -11
- package/src/components/PillGroup/PillGroup.stories.tsx +5 -6
- package/src/components/PillGroup/PillGroup.tsx +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { Canvas, Controls, Meta, Story } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import { BodyText, Center, LI, OL, UL } from '../..';
|
|
3
|
+
import { BackToTopButton, UsageWrap, ViewFigmaButton } from '../../../docs/components';
|
|
4
|
+
import * as UnorderedListStory from './UnorderedList.stories';
|
|
4
5
|
|
|
5
6
|
<Meta title="Utility Components / UL & OL (Lists)" />
|
|
6
7
|
|
|
@@ -11,6 +12,9 @@ import { ViewFigmaButton, BackToTopButton, UsageWrap } from '../../../docs/compo
|
|
|
11
12
|
The `UL` (Unordered List) and `OL` (Ordered List) components are used to display lists of items. `UL` displays a bulleted list, and `OL` displays a numbered list. The `LI` (List Item) component is used to define each item within these lists.
|
|
12
13
|
|
|
13
14
|
- [Usage](#usage)
|
|
15
|
+
- [Unordered List (UL)](#unordered-list-ul)
|
|
16
|
+
- [Ordered List (OL)](#ordered-list-ol)
|
|
17
|
+
- [Customising List Styles](#customising-list-styles)
|
|
14
18
|
- [Components](#components)
|
|
15
19
|
|
|
16
20
|
## Usage
|
|
@@ -73,32 +77,76 @@ const MyComponent = () => {
|
|
|
73
77
|
};
|
|
74
78
|
```
|
|
75
79
|
|
|
80
|
+
### Customising List Styles
|
|
81
|
+
|
|
82
|
+
You can customise the appearance of list markers using props like `listStyleIcon`, `listStyleImage`, `listStyleColour`, and dimensions. These props can be applied to the `UL`/`OL` container to affect all items, or on individual `LI` components to override them.
|
|
83
|
+
|
|
84
|
+
<Canvas of={UnorderedListStory.WithCustomIcon} />
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { UL, LI } from '@utilitywarehouse/native-ui';
|
|
88
|
+
import { TickMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
89
|
+
import { Image } from 'react-native';
|
|
90
|
+
|
|
91
|
+
const MyComponent = () => {
|
|
92
|
+
return (
|
|
93
|
+
<UL listStyleColour="feedbackDangerSurfaceDefault">
|
|
94
|
+
<LI>Primary colored bullet</LI>
|
|
95
|
+
<LI listStyleColour="feedbackPositiveSurfaceDefault">Danger colored bullet override</LI>
|
|
96
|
+
<LI listStyleIcon={TickMediumIcon}>Icon bullet</LI>
|
|
97
|
+
<LI
|
|
98
|
+
listStyleImage={<Image source={{ uri: '...' }} />}
|
|
99
|
+
listStyleWidth={20}
|
|
100
|
+
listStyleHeight={20}
|
|
101
|
+
>
|
|
102
|
+
Image bullet
|
|
103
|
+
</LI>
|
|
104
|
+
</UL>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
76
109
|
## Components
|
|
77
110
|
|
|
78
111
|
### `UL`
|
|
79
112
|
|
|
80
113
|
The `UL` component is a container for unordered list items. It inherits all the properties of React Native's [`View` component](https://reactnative.dev/docs/view).
|
|
81
114
|
|
|
82
|
-
| Property
|
|
83
|
-
|
|
|
84
|
-
| `gap`
|
|
85
|
-
| `bulletStyle`
|
|
86
|
-
| `children`
|
|
115
|
+
| Property | Type | Default | Description |
|
|
116
|
+
| ----------------- | --------------------- | ------- | -------------------------------------------------------- |
|
|
117
|
+
| `gap` | `SpaceValue` | `'100'` | The gap between the list items. |
|
|
118
|
+
| `bulletStyle` | `ViewStyle` | - | Custom style for the bullet points. |
|
|
119
|
+
| `children` | `React.ReactNode` | - | The `LI` components to be rendered within the list. |
|
|
120
|
+
| `listStyleImage` | `React.ReactElement` | - | Custom element (e.g. Image) to use as the bullet/marker. |
|
|
121
|
+
| `listStyleIcon` | `React.ComponentType` | - | Custom icon component to use as the bullet/marker. |
|
|
122
|
+
| `listStyleWidth` | `number` | `20` | Width of the custom bullet/marker. |
|
|
123
|
+
| `listStyleHeight` | `number` | `20` | Height of the custom bullet/marker. |
|
|
124
|
+
| `listStyleColour` | `ColorValue` | - | Color of the bullet/marker. |
|
|
87
125
|
|
|
88
126
|
### `OL`
|
|
89
127
|
|
|
90
128
|
The `OL` component is a container for ordered list items. It inherits all the properties of React Native's [`View` component](https://reactnative.dev/docs/view).
|
|
91
129
|
|
|
92
|
-
| Property
|
|
93
|
-
|
|
|
94
|
-
| `gap`
|
|
95
|
-
| `bulletStyle`
|
|
96
|
-
| `children`
|
|
130
|
+
| Property | Type | Default | Description |
|
|
131
|
+
| ----------------- | --------------------- | ------- | -------------------------------------------------------- |
|
|
132
|
+
| `gap` | `SpaceValue` | `'100'` | The gap between the list items. |
|
|
133
|
+
| `bulletStyle` | `ViewStyle` | - | Custom style for the numbers. |
|
|
134
|
+
| `children` | `React.ReactNode` | - | The `LI` components to be rendered within the list. |
|
|
135
|
+
| `listStyleImage` | `React.ReactElement` | - | Custom element (e.g. Image) to use as the bullet/marker. |
|
|
136
|
+
| `listStyleIcon` | `React.ComponentType` | - | Custom icon component to use as the bullet/marker. |
|
|
137
|
+
| `listStyleWidth` | `number` | `20` | Width of the custom bullet/marker. |
|
|
138
|
+
| `listStyleHeight` | `number` | `20` | Height of the custom bullet/marker. |
|
|
139
|
+
| `listStyleColour` | `ColorValue` | - | Color of the number/marker. |
|
|
97
140
|
|
|
98
141
|
### `LI`
|
|
99
142
|
|
|
100
143
|
The `LI` component represents an item in a list. It wraps its children in a `BodyText` component if the children are a string. It inherits all the properties of React Native's [`View` component](https://reactnative.dev/docs/view).
|
|
101
144
|
|
|
102
|
-
| Property
|
|
103
|
-
|
|
|
104
|
-
| `children`
|
|
145
|
+
| Property | Type | Default | Description |
|
|
146
|
+
| ----------------- | --------------------- | ------- | -------------------------------------------------------- |
|
|
147
|
+
| `children` | `React.ReactNode` | - | The content to be displayed inside the list item. |
|
|
148
|
+
| `listStyleImage` | `React.ReactElement` | - | Custom element (e.g. Image) to use as the bullet/marker. |
|
|
149
|
+
| `listStyleIcon` | `React.ComponentType` | - | Custom icon component to use as the bullet/marker. |
|
|
150
|
+
| `listStyleWidth` | `number` | `20` | Width of the custom bullet/marker. |
|
|
151
|
+
| `listStyleHeight` | `number` | `20` | Height of the custom bullet/marker. |
|
|
152
|
+
| `listStyleColour` | `ColorValue` | - | Color of the bullet/marker/number. |
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
-
import OrderedList from './OrderedList';
|
|
3
|
-
import ListItem from './ListItem';
|
|
4
2
|
import { primitive } from '@utilitywarehouse/hearth-tokens/js';
|
|
3
|
+
import { View } from 'react-native';
|
|
5
4
|
import { InputType } from 'storybook/internal/types';
|
|
6
5
|
import { SpaceValue } from '../../types';
|
|
6
|
+
import ListItem from './ListItem';
|
|
7
|
+
import OrderedList from './OrderedList';
|
|
7
8
|
|
|
8
9
|
const gap: InputType = {
|
|
9
10
|
options: Object.keys(primitive.space),
|
|
@@ -53,3 +54,33 @@ export const WithCustomGap: Story = {
|
|
|
53
54
|
</OrderedList>
|
|
54
55
|
),
|
|
55
56
|
};
|
|
57
|
+
|
|
58
|
+
export const WithColoredNumbers: Story = {
|
|
59
|
+
render: ({ ...args }) => (
|
|
60
|
+
<OrderedList {...args} listStyleColour="piggyPink300">
|
|
61
|
+
<ListItem>Item 1</ListItem>
|
|
62
|
+
<ListItem>Item 2</ListItem>
|
|
63
|
+
</OrderedList>
|
|
64
|
+
),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const WithIconOverride: Story = {
|
|
68
|
+
render: ({ ...args }) => {
|
|
69
|
+
const CustomIcon = (props: any) => (
|
|
70
|
+
<View
|
|
71
|
+
style={{
|
|
72
|
+
width: props.width,
|
|
73
|
+
height: props.height,
|
|
74
|
+
backgroundColor: props.color || 'blue',
|
|
75
|
+
borderRadius: 4,
|
|
76
|
+
}}
|
|
77
|
+
/>
|
|
78
|
+
);
|
|
79
|
+
return (
|
|
80
|
+
<OrderedList {...args} listStyleIcon={CustomIcon}>
|
|
81
|
+
<ListItem>Item 1 (overridden)</ListItem>
|
|
82
|
+
<ListItem>Item 2 (overridden)</ListItem>
|
|
83
|
+
</OrderedList>
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -1,27 +1,75 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { StyleSheet, View, ViewProps, ViewStyle } from 'react-native';
|
|
3
|
-
import { useStyleProps } from '../../hooks';
|
|
3
|
+
import { useStyleProps, useTheme } from '../../hooks';
|
|
4
4
|
import { SpaceValue } from '../../types';
|
|
5
|
+
import { getFlattenedColorValue } from '../../utils';
|
|
5
6
|
import { BodyText } from '../BodyText';
|
|
7
|
+
import { ListItemProps, ListStyleProps } from './ListItem';
|
|
6
8
|
|
|
7
|
-
export interface OrderedListProps extends ViewProps {
|
|
9
|
+
export interface OrderedListProps extends ViewProps, ListStyleProps {
|
|
8
10
|
children: ViewProps['children'];
|
|
9
11
|
gap?: SpaceValue;
|
|
10
12
|
bulletStyle?: ViewStyle;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
const OrderedList = ({
|
|
15
|
+
const OrderedList = ({
|
|
16
|
+
children,
|
|
17
|
+
gap = '100',
|
|
18
|
+
style,
|
|
19
|
+
listStyleImage,
|
|
20
|
+
listStyleIcon,
|
|
21
|
+
listStyleWidth,
|
|
22
|
+
listStyleHeight,
|
|
23
|
+
listStyleColour,
|
|
24
|
+
...rest
|
|
25
|
+
}: OrderedListProps) => {
|
|
14
26
|
const { computedStyles } = useStyleProps({ gap });
|
|
27
|
+
const theme = useTheme();
|
|
15
28
|
let itemNumber = 0;
|
|
29
|
+
|
|
16
30
|
return (
|
|
17
31
|
<View style={[computedStyles, style]} {...rest}>
|
|
18
32
|
{React.Children.map(children, child => {
|
|
19
33
|
if (React.isValidElement(child)) {
|
|
20
34
|
itemNumber++;
|
|
35
|
+
const childProps = child.props as ListItemProps;
|
|
36
|
+
|
|
37
|
+
const image = childProps.listStyleImage ?? listStyleImage;
|
|
38
|
+
const Icon = childProps.listStyleIcon ?? listStyleIcon;
|
|
39
|
+
const width = childProps.listStyleWidth ?? listStyleWidth ?? 20;
|
|
40
|
+
const height = childProps.listStyleHeight ?? listStyleHeight ?? 20;
|
|
41
|
+
const colourRaw = childProps.listStyleColour ?? listStyleColour;
|
|
42
|
+
|
|
43
|
+
const colour = colourRaw ? getFlattenedColorValue(colourRaw, theme.color) : undefined;
|
|
44
|
+
|
|
45
|
+
let bullet;
|
|
46
|
+
if (image) {
|
|
47
|
+
const imageEl = image as React.ReactElement<any>;
|
|
48
|
+
bullet = React.cloneElement(imageEl, {
|
|
49
|
+
style: [{ width, height }, imageEl.props.style],
|
|
50
|
+
});
|
|
51
|
+
} else if (Icon) {
|
|
52
|
+
bullet = (
|
|
53
|
+
<Icon width={width} height={height} color={colour ?? theme.color.text.primary} />
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
bullet = (
|
|
57
|
+
<BodyText
|
|
58
|
+
style={[styles.number, colour && { color: colour }]}
|
|
59
|
+
>{`${itemNumber}.`}</BodyText>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const isCustom = !!(image || Icon);
|
|
64
|
+
|
|
21
65
|
return (
|
|
22
66
|
<View style={styles.listItemContainer}>
|
|
23
|
-
<
|
|
24
|
-
{
|
|
67
|
+
{isCustom ? <View style={{ marginRight: 8 }}>{bullet}</View> : bullet}
|
|
68
|
+
{
|
|
69
|
+
React.cloneElement(child as React.ReactElement<ListItemProps>, {
|
|
70
|
+
style: [childProps.style, { flex: 1 }],
|
|
71
|
+
}) as ViewProps['children']
|
|
72
|
+
}
|
|
25
73
|
</View>
|
|
26
74
|
);
|
|
27
75
|
}
|
|
@@ -40,7 +88,7 @@ const styles = StyleSheet.create({
|
|
|
40
88
|
},
|
|
41
89
|
number: {
|
|
42
90
|
marginRight: 8,
|
|
43
|
-
lineHeight: undefined,
|
|
91
|
+
lineHeight: undefined,
|
|
44
92
|
},
|
|
45
93
|
});
|
|
46
94
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
2
|
-
import
|
|
3
|
-
import ListItem from './ListItem';
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-native';
|
|
2
|
+
import { TickMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
3
|
import { primitive } from '@utilitywarehouse/hearth-tokens/js';
|
|
5
|
-
import {
|
|
4
|
+
import { Image, View } from 'react-native';
|
|
5
|
+
import ListItem from './ListItem';
|
|
6
|
+
import UnorderedList from './UnorderedList';
|
|
6
7
|
|
|
7
|
-
const gap
|
|
8
|
+
const gap = {
|
|
8
9
|
options: Object.keys(primitive.space),
|
|
9
10
|
control: 'select',
|
|
10
11
|
description: 'Gap between list items.',
|
|
@@ -50,3 +51,60 @@ export const WithCustomGap: Story = {
|
|
|
50
51
|
</UnorderedList>
|
|
51
52
|
),
|
|
52
53
|
};
|
|
54
|
+
|
|
55
|
+
export const WithCustomIcon: Story = {
|
|
56
|
+
render: ({ ...args }) => {
|
|
57
|
+
return (
|
|
58
|
+
<UnorderedList
|
|
59
|
+
{...args}
|
|
60
|
+
listStyleIcon={TickMediumIcon}
|
|
61
|
+
listStyleColour="feedbackDangerSurfaceDefault"
|
|
62
|
+
>
|
|
63
|
+
<ListItem>List item 1 with icon</ListItem>
|
|
64
|
+
<ListItem>List item 2 with icon</ListItem>
|
|
65
|
+
<ListItem>
|
|
66
|
+
List item 3 with icon is a long example to test alignment, lorem ipsum dolor sit amet
|
|
67
|
+
</ListItem>
|
|
68
|
+
</UnorderedList>
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const WithCustomImage: Story = {
|
|
74
|
+
render: ({ ...args }) => (
|
|
75
|
+
<UnorderedList
|
|
76
|
+
{...args}
|
|
77
|
+
listStyleImage={
|
|
78
|
+
<Image
|
|
79
|
+
source={{ uri: 'https://placehold.co/20x20.png' }}
|
|
80
|
+
style={{ width: 20, height: 20 }}
|
|
81
|
+
/>
|
|
82
|
+
}
|
|
83
|
+
>
|
|
84
|
+
<ListItem>List item 1 with image</ListItem>
|
|
85
|
+
<ListItem>List item 2 with image</ListItem>
|
|
86
|
+
</UnorderedList>
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const WithIndividualItemOverride: Story = {
|
|
91
|
+
render: ({ ...args }) => {
|
|
92
|
+
const CheckIcon = (props: any) => (
|
|
93
|
+
<View
|
|
94
|
+
style={{
|
|
95
|
+
width: props.width,
|
|
96
|
+
height: props.height,
|
|
97
|
+
backgroundColor: 'green',
|
|
98
|
+
borderRadius: 10,
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
return (
|
|
103
|
+
<UnorderedList {...args}>
|
|
104
|
+
<ListItem>Default bullet item</ListItem>
|
|
105
|
+
<ListItem listStyleIcon={CheckIcon}>Success item</ListItem>
|
|
106
|
+
<ListItem listStyleColour="blue600">Colored bullet item</ListItem>
|
|
107
|
+
</UnorderedList>
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -1,25 +1,69 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { StyleSheet, View, ViewProps, ViewStyle } from 'react-native';
|
|
3
|
-
import { useStyleProps } from '../../hooks';
|
|
3
|
+
import { useStyleProps, useTheme } from '../../hooks';
|
|
4
4
|
import { SpaceValue } from '../../types';
|
|
5
|
+
import { getFlattenedColorValue } from '../../utils';
|
|
5
6
|
import { BodyText } from '../BodyText';
|
|
7
|
+
import { ListItemProps, ListStyleProps } from './ListItem';
|
|
6
8
|
|
|
7
|
-
export interface UnorderedListProps extends ViewProps {
|
|
9
|
+
export interface UnorderedListProps extends ViewProps, ListStyleProps {
|
|
8
10
|
children: ViewProps['children'];
|
|
9
11
|
gap?: SpaceValue;
|
|
10
12
|
bulletStyle?: ViewStyle;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
const UnorderedList = ({
|
|
15
|
+
const UnorderedList = ({
|
|
16
|
+
children,
|
|
17
|
+
gap = '100',
|
|
18
|
+
style,
|
|
19
|
+
listStyleImage,
|
|
20
|
+
listStyleIcon,
|
|
21
|
+
listStyleWidth,
|
|
22
|
+
listStyleHeight,
|
|
23
|
+
listStyleColour,
|
|
24
|
+
...rest
|
|
25
|
+
}: UnorderedListProps) => {
|
|
14
26
|
const { computedStyles } = useStyleProps({ gap });
|
|
27
|
+
const theme = useTheme();
|
|
28
|
+
|
|
15
29
|
return (
|
|
16
30
|
<View style={[computedStyles, style]} {...rest}>
|
|
17
31
|
{React.Children.map(children, child => {
|
|
18
32
|
if (React.isValidElement(child)) {
|
|
33
|
+
const childProps = child.props as ListItemProps;
|
|
34
|
+
|
|
35
|
+
const image = childProps.listStyleImage ?? listStyleImage;
|
|
36
|
+
const Icon = childProps.listStyleIcon ?? listStyleIcon;
|
|
37
|
+
const width = childProps.listStyleWidth ?? listStyleWidth ?? 20;
|
|
38
|
+
const height = childProps.listStyleHeight ?? listStyleHeight ?? 20;
|
|
39
|
+
const colourRaw = childProps.listStyleColour ?? listStyleColour;
|
|
40
|
+
|
|
41
|
+
const colour = colourRaw ? getFlattenedColorValue(colourRaw, theme.color) : undefined;
|
|
42
|
+
|
|
43
|
+
let bullet;
|
|
44
|
+
if (image) {
|
|
45
|
+
const imageEl = image as React.ReactElement<any>;
|
|
46
|
+
bullet = React.cloneElement(imageEl, {
|
|
47
|
+
style: [{ width, height }, imageEl.props.style],
|
|
48
|
+
});
|
|
49
|
+
} else if (Icon) {
|
|
50
|
+
bullet = (
|
|
51
|
+
<Icon width={width} height={height} color={colour ?? theme.color.text.primary} />
|
|
52
|
+
);
|
|
53
|
+
} else {
|
|
54
|
+
bullet = <BodyText style={[styles.bullet, colour && { color: colour }]}>•</BodyText>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isCustom = !!(image || Icon);
|
|
58
|
+
|
|
19
59
|
return (
|
|
20
60
|
<View style={styles.listItemContainer}>
|
|
21
|
-
<
|
|
22
|
-
{
|
|
61
|
+
{isCustom ? <View style={{ marginRight: 8 }}>{bullet}</View> : bullet}
|
|
62
|
+
{
|
|
63
|
+
React.cloneElement(child as React.ReactElement<ListItemProps>, {
|
|
64
|
+
style: [childProps.style, { flex: 1 }],
|
|
65
|
+
}) as ViewProps['children']
|
|
66
|
+
}
|
|
23
67
|
</View>
|
|
24
68
|
);
|
|
25
69
|
}
|
|
@@ -38,7 +82,7 @@ const styles = StyleSheet.create({
|
|
|
38
82
|
},
|
|
39
83
|
bullet: {
|
|
40
84
|
marginRight: 8,
|
|
41
|
-
lineHeight: undefined,
|
|
85
|
+
lineHeight: undefined,
|
|
42
86
|
},
|
|
43
87
|
});
|
|
44
88
|
|
|
@@ -48,6 +48,7 @@ const Input = forwardRef<TextInput, InputProps>(
|
|
|
48
48
|
clearable = false,
|
|
49
49
|
required,
|
|
50
50
|
inBottomSheet = false,
|
|
51
|
+
style,
|
|
51
52
|
...props
|
|
52
53
|
},
|
|
53
54
|
ref
|
|
@@ -96,6 +97,7 @@ const Input = forwardRef<TextInput, InputProps>(
|
|
|
96
97
|
isFocused={focused}
|
|
97
98
|
type={type as undefined}
|
|
98
99
|
isRequired={isRequired}
|
|
100
|
+
style={style}
|
|
99
101
|
>
|
|
100
102
|
{children ? (
|
|
101
103
|
<>{children}</>
|
|
@@ -12,6 +12,7 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
|
|
|
12
12
|
description?: string;
|
|
13
13
|
inNavModal?: boolean;
|
|
14
14
|
fullscreen?: boolean;
|
|
15
|
+
stickyFooter?: boolean;
|
|
15
16
|
children?: ViewProps['children'];
|
|
16
17
|
onPressPrimaryButton?: () => void;
|
|
17
18
|
primaryButtonText?: string;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BottomSheetFooter,
|
|
3
|
+
BottomSheetFooterProps,
|
|
4
|
+
BottomSheetScrollViewMethods,
|
|
5
|
+
SNAP_POINT_TYPE,
|
|
6
|
+
} from '@gorhom/bottom-sheet';
|
|
2
7
|
import { BottomSheetModalMethods } from '@gorhom/bottom-sheet/lib/typescript/types';
|
|
3
8
|
import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
4
9
|
import { useCallback, useEffect, useImperativeHandle, useRef } from 'react';
|
|
@@ -43,6 +48,7 @@ const Modal = ({
|
|
|
43
48
|
secondaryButtonProps,
|
|
44
49
|
closeButtonProps,
|
|
45
50
|
inNavModal = false,
|
|
51
|
+
stickyFooter = true,
|
|
46
52
|
...props
|
|
47
53
|
}: ModalProps) => {
|
|
48
54
|
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
|
|
@@ -155,7 +161,37 @@ const Modal = ({
|
|
|
155
161
|
}
|
|
156
162
|
};
|
|
157
163
|
|
|
158
|
-
|
|
164
|
+
const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
|
|
165
|
+
|
|
166
|
+
styles.useVariants({
|
|
167
|
+
loading,
|
|
168
|
+
bothButtons: !!(onPressPrimaryButton && onPressSecondaryButton),
|
|
169
|
+
noButtons,
|
|
170
|
+
stickyFooter,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const footer = (
|
|
174
|
+
<View style={styles.footer}>
|
|
175
|
+
{onPressPrimaryButton && primaryButtonText ? (
|
|
176
|
+
<Button
|
|
177
|
+
onPress={handlePrimaryButtonPress}
|
|
178
|
+
text={primaryButtonText}
|
|
179
|
+
{...primaryButtonProps}
|
|
180
|
+
variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
|
|
181
|
+
colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
|
|
182
|
+
/>
|
|
183
|
+
) : null}
|
|
184
|
+
{onPressSecondaryButton && secondaryButtonText ? (
|
|
185
|
+
<Button
|
|
186
|
+
onPress={handleSecondaryButtonPress}
|
|
187
|
+
text={secondaryButtonText}
|
|
188
|
+
{...secondaryButtonProps}
|
|
189
|
+
variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
|
|
190
|
+
colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
|
|
191
|
+
/>
|
|
192
|
+
) : null}
|
|
193
|
+
</View>
|
|
194
|
+
);
|
|
159
195
|
|
|
160
196
|
const content = (
|
|
161
197
|
<>
|
|
@@ -216,31 +252,28 @@ const Modal = ({
|
|
|
216
252
|
</View>
|
|
217
253
|
) : null}
|
|
218
254
|
{children}
|
|
219
|
-
|
|
220
|
-
{onPressPrimaryButton && primaryButtonText ? (
|
|
221
|
-
<Button
|
|
222
|
-
onPress={handlePrimaryButtonPress}
|
|
223
|
-
text={primaryButtonText}
|
|
224
|
-
{...primaryButtonProps}
|
|
225
|
-
variant={(primaryButtonProps?.variant as 'solid') ?? 'solid'}
|
|
226
|
-
colorScheme={(primaryButtonProps?.colorScheme as 'highlight') ?? 'highlight'}
|
|
227
|
-
/>
|
|
228
|
-
) : null}
|
|
229
|
-
{onPressSecondaryButton && secondaryButtonText ? (
|
|
230
|
-
<Button
|
|
231
|
-
onPress={handleSecondaryButtonPress}
|
|
232
|
-
text={secondaryButtonText}
|
|
233
|
-
{...secondaryButtonProps}
|
|
234
|
-
variant={(secondaryButtonProps?.variant as 'outline') ?? 'outline'}
|
|
235
|
-
colorScheme={(secondaryButtonProps?.colorScheme as 'functional') ?? 'functional'}
|
|
236
|
-
/>
|
|
237
|
-
) : null}
|
|
238
|
-
</View>
|
|
255
|
+
{!stickyFooter && !noButtons ? footer : null}
|
|
239
256
|
</View>
|
|
240
257
|
)}
|
|
241
258
|
</>
|
|
242
259
|
);
|
|
243
260
|
|
|
261
|
+
const renderFooter = useCallback(
|
|
262
|
+
(props: BottomSheetFooterProps) => (
|
|
263
|
+
<BottomSheetFooter {...props}>
|
|
264
|
+
<View style={styles.footerWrap}>{footer}</View>
|
|
265
|
+
</BottomSheetFooter>
|
|
266
|
+
),
|
|
267
|
+
[
|
|
268
|
+
onPressPrimaryButton,
|
|
269
|
+
primaryButtonText,
|
|
270
|
+
onPressSecondaryButton,
|
|
271
|
+
secondaryButtonText,
|
|
272
|
+
primaryButtonProps,
|
|
273
|
+
secondaryButtonProps,
|
|
274
|
+
]
|
|
275
|
+
);
|
|
276
|
+
|
|
244
277
|
return inNavModal ? (
|
|
245
278
|
<View style={{ flex: 1, backgroundColor: theme.color.background.primary }}>
|
|
246
279
|
{Platform.OS === 'android' ? (
|
|
@@ -262,11 +295,12 @@ const Modal = ({
|
|
|
262
295
|
showHandle={typeof loading !== 'undefined' && loading ? false : props.showHandle}
|
|
263
296
|
accessible={false}
|
|
264
297
|
style={styles.modal}
|
|
298
|
+
footerComponent={stickyFooter && !noButtons ? renderFooter : undefined}
|
|
265
299
|
{...props}
|
|
266
300
|
onChange={handleChange}
|
|
267
301
|
>
|
|
268
302
|
{loading ? <View style={styles.loadingTop} /> : null}
|
|
269
|
-
<BottomSheetScrollView contentContainerStyle={styles.
|
|
303
|
+
<BottomSheetScrollView contentContainerStyle={styles.scrollView} ref={scrollViewRef}>
|
|
270
304
|
{content}
|
|
271
305
|
</BottomSheetScrollView>
|
|
272
306
|
</BottomSheetModal>
|
|
@@ -288,6 +322,30 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
288
322
|
},
|
|
289
323
|
},
|
|
290
324
|
},
|
|
325
|
+
scrollView: {
|
|
326
|
+
flex: 1,
|
|
327
|
+
variants: {
|
|
328
|
+
bothButtons: {
|
|
329
|
+
true: {
|
|
330
|
+
paddingBottom: 166 + rt.insets.bottom - theme.components.modal.padding,
|
|
331
|
+
},
|
|
332
|
+
false: {
|
|
333
|
+
paddingBottom: 102 + rt.insets.bottom - theme.components.modal.padding,
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
noButtons: {
|
|
337
|
+
true: {
|
|
338
|
+
paddingBottom: rt.insets.bottom + theme.components.modal.padding,
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
stickyFooter: {
|
|
342
|
+
true: {},
|
|
343
|
+
false: {
|
|
344
|
+
paddingBottom: rt.insets.bottom + theme.components.modal.padding,
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
291
349
|
header: {
|
|
292
350
|
flexDirection: 'row',
|
|
293
351
|
gap: theme.components.modal.gap,
|
|
@@ -321,6 +379,11 @@ const styles = StyleSheet.create((theme, rt) => ({
|
|
|
321
379
|
footer: {
|
|
322
380
|
gap: theme.components.modal.action.gap,
|
|
323
381
|
},
|
|
382
|
+
footerWrap: {
|
|
383
|
+
backgroundColor: theme.color.surface.neutral.strong,
|
|
384
|
+
paddingHorizontal: theme.components.bottomSheet.padding,
|
|
385
|
+
paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
|
|
386
|
+
},
|
|
324
387
|
inNavModalContainer: {
|
|
325
388
|
flex: 1,
|
|
326
389
|
...(Platform.OS === 'ios' ? { backgroundColor: theme.components.overlay.backgroundColor } : {}),
|
|
@@ -1,22 +1,36 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ScrollViewProps, ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
-
export interface
|
|
5
|
-
extends Omit<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** Multi-select mode. Default = false */
|
|
10
|
-
multiple?: boolean;
|
|
11
|
-
|
|
4
|
+
export interface PillGroupBaseProps
|
|
5
|
+
extends Omit<
|
|
6
|
+
ScrollViewProps,
|
|
7
|
+
'horizontal' | 'contentContainerStyle' | 'showsHorizontalScrollIndicator'
|
|
8
|
+
> {
|
|
12
9
|
/** Allow pills to wrap lines. Default = true */
|
|
13
10
|
wrap?: boolean;
|
|
14
11
|
|
|
15
|
-
/** Handle selection changes */
|
|
16
|
-
onChange?: (value: string | string[]) => void;
|
|
17
|
-
|
|
18
12
|
/** Children must be <Pill> elements */
|
|
19
13
|
children: React.ReactNode;
|
|
20
14
|
|
|
21
15
|
style?: ViewStyle | ViewStyle[];
|
|
22
16
|
}
|
|
17
|
+
|
|
18
|
+
interface SinglePillGroupProps extends PillGroupBaseProps {
|
|
19
|
+
/** Multi-select mode. Default = false */
|
|
20
|
+
multiple?: false;
|
|
21
|
+
/** Controlled selected value */
|
|
22
|
+
value: string;
|
|
23
|
+
/** Handle selection changes */
|
|
24
|
+
onChange?: (value: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface MultiPillGroupProps extends PillGroupBaseProps {
|
|
28
|
+
/** Multi-select mode. Default = false */
|
|
29
|
+
multiple: true;
|
|
30
|
+
/** Controlled selected value(s) */
|
|
31
|
+
value: string[];
|
|
32
|
+
/** Handle selection changes */
|
|
33
|
+
onChange?: (value: string[]) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type PillGroupProps = SinglePillGroupProps | MultiPillGroupProps;
|