@butternutbox/pawprint-native 0.6.0 → 0.8.0
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 +15 -15
- package/CHANGELOG.md +16 -0
- package/dist/index.cjs +598 -333
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +556 -292
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/atoms/Switch/Switch.tsx +3 -1
- package/src/components/molecules/NativeSelectPicker/NativeSelectPicker.stories.tsx +98 -0
- package/src/components/molecules/NativeSelectPicker/NativeSelectPicker.styled.ts +127 -0
- package/src/components/molecules/NativeSelectPicker/NativeSelectPicker.tsx +250 -0
- package/src/components/molecules/NativeSelectPicker/index.ts +5 -0
- package/src/components/molecules/Notification/Notification.stories.tsx +4 -0
- package/src/components/molecules/Notification/Notification.tsx +43 -36
- package/src/components/molecules/TabNavigation/TabNavigation.stories.tsx +35 -0
- package/src/components/molecules/TabNavigation/TabNavigation.tsx +15 -1
- package/src/components/molecules/index.ts +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import React, { useState } from "react"
|
|
2
|
+
import { View } from "react-native"
|
|
3
|
+
import { NativeSelectPicker } from "./NativeSelectPicker"
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: "Molecules/NativeSelectPicker",
|
|
7
|
+
component: NativeSelectPicker,
|
|
8
|
+
argTypes: {
|
|
9
|
+
disabled: {
|
|
10
|
+
control: "boolean"
|
|
11
|
+
},
|
|
12
|
+
hideIcon: {
|
|
13
|
+
control: "boolean"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
decorators: [
|
|
17
|
+
(Story: React.ComponentType) => (
|
|
18
|
+
<View style={{ padding: 20, flex: 1, justifyContent: "center" }}>
|
|
19
|
+
<Story />
|
|
20
|
+
</View>
|
|
21
|
+
)
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const BANK_OPTIONS = [
|
|
26
|
+
{ label: "ABN Amro", value: "abn_amro" },
|
|
27
|
+
{ label: "ASN Bank", value: "asn_bank" },
|
|
28
|
+
{ label: "bunq B.V.", value: "bunq" },
|
|
29
|
+
{ label: "ING Bank", value: "ing" },
|
|
30
|
+
{ label: "Knab", value: "knab" },
|
|
31
|
+
{ label: "Rabobank", value: "rabobank" }
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
export const Basic = () => {
|
|
35
|
+
const [value, setValue] = useState<string | number | null>(null)
|
|
36
|
+
return (
|
|
37
|
+
<NativeSelectPicker
|
|
38
|
+
items={BANK_OPTIONS}
|
|
39
|
+
placeholder={{ label: "Select a bank" }}
|
|
40
|
+
value={value}
|
|
41
|
+
onValueChange={setValue}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const WithSelectedValue = () => {
|
|
47
|
+
const [value, setValue] = useState<string | number | null>("ing")
|
|
48
|
+
return (
|
|
49
|
+
<NativeSelectPicker
|
|
50
|
+
items={BANK_OPTIONS}
|
|
51
|
+
placeholder={{ label: "Select a bank" }}
|
|
52
|
+
value={value}
|
|
53
|
+
onValueChange={setValue}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const Disabled = () => {
|
|
59
|
+
const [value, setValue] = useState<string | number | null>(null)
|
|
60
|
+
return (
|
|
61
|
+
<NativeSelectPicker
|
|
62
|
+
items={BANK_OPTIONS}
|
|
63
|
+
placeholder={{ label: "Select a bank" }}
|
|
64
|
+
value={value}
|
|
65
|
+
onValueChange={setValue}
|
|
66
|
+
disabled
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const HideIcon = () => {
|
|
72
|
+
const [value, setValue] = useState<string | number | null>(null)
|
|
73
|
+
return (
|
|
74
|
+
<NativeSelectPicker
|
|
75
|
+
items={BANK_OPTIONS}
|
|
76
|
+
placeholder={{ label: "Select a bank" }}
|
|
77
|
+
value={value}
|
|
78
|
+
onValueChange={setValue}
|
|
79
|
+
hideIcon
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const NumericValues = () => {
|
|
85
|
+
const [value, setValue] = useState<string | number | null>(null)
|
|
86
|
+
return (
|
|
87
|
+
<NativeSelectPicker
|
|
88
|
+
items={[
|
|
89
|
+
{ label: "Option 1", value: 1 },
|
|
90
|
+
{ label: "Option 2", value: 2 },
|
|
91
|
+
{ label: "Option 3", value: 3 }
|
|
92
|
+
]}
|
|
93
|
+
placeholder={{ label: "Select an option" }}
|
|
94
|
+
value={value}
|
|
95
|
+
onValueChange={setValue}
|
|
96
|
+
/>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import styled from "@emotion/native"
|
|
2
|
+
import { View, Text } from "react-native"
|
|
3
|
+
import Animated from "react-native-reanimated"
|
|
4
|
+
import { DEFAULT_THEME_OPTIONS } from "@butternutbox/pawprint-tokens"
|
|
5
|
+
|
|
6
|
+
type AndroidOptionTextProps = {
|
|
7
|
+
isSelected: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const themeTokens = DEFAULT_THEME_OPTIONS.tokens
|
|
11
|
+
const { components, semantics } = themeTokens
|
|
12
|
+
const inputTokens = components.inputs
|
|
13
|
+
const { dimensions } = semantics
|
|
14
|
+
|
|
15
|
+
const parseSize = (value: string): number => {
|
|
16
|
+
return parseInt(value.replace("px", ""), 10)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Wrapper = styled(View)({
|
|
20
|
+
borderWidth: parseSize(inputTokens.borderWidth.field.selected),
|
|
21
|
+
borderColor: inputTokens.colour.field.border.selected,
|
|
22
|
+
borderRadius: parseSize(inputTokens.borderRadius.field.default),
|
|
23
|
+
backgroundColor: inputTokens.colour.field.background.default,
|
|
24
|
+
paddingHorizontal: parseSize(inputTokens.spacing.field.horizontalPadding),
|
|
25
|
+
paddingVertical: parseSize(inputTokens.spacing.field.verticalPadding),
|
|
26
|
+
height: parseSize(inputTokens.size.field.height),
|
|
27
|
+
flexDirection: "row",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
justifyContent: "space-between"
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const SelectIcon = styled(Animated.View)({
|
|
33
|
+
position: "absolute",
|
|
34
|
+
right: 0,
|
|
35
|
+
marginRight: 0
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const InputText = styled(Text)({
|
|
39
|
+
color: inputTokens.colour.field.text.default,
|
|
40
|
+
fontSize: parseSize(inputTokens.field.placeholder.default.fontSize),
|
|
41
|
+
fontWeight: inputTokens.field.placeholder.default.fontWeight,
|
|
42
|
+
flex: 1
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const PlaceholderText = styled(Text)({
|
|
46
|
+
color: inputTokens.colour.field.text.placeholder,
|
|
47
|
+
fontSize: parseSize(inputTokens.field.placeholder.default.fontSize),
|
|
48
|
+
fontWeight: inputTokens.field.placeholder.default.fontWeight,
|
|
49
|
+
flex: 1
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const ModalOverlay = styled(View)({
|
|
53
|
+
flex: 1,
|
|
54
|
+
backgroundColor: "rgba(0, 0, 0, 0.32)",
|
|
55
|
+
justifyContent: "flex-end"
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const AndroidDialog = styled(View)({
|
|
59
|
+
backgroundColor: inputTokens.colour.field.background.default,
|
|
60
|
+
borderTopLeftRadius: parseSize(dimensions.borderRadius.lg),
|
|
61
|
+
borderTopRightRadius: parseSize(dimensions.borderRadius.lg),
|
|
62
|
+
maxHeight: "80%",
|
|
63
|
+
minHeight: "40%",
|
|
64
|
+
paddingTop: parseSize(dimensions.spacing.md),
|
|
65
|
+
paddingBottom: parseSize(dimensions.spacing.xl),
|
|
66
|
+
paddingHorizontal: parseSize(dimensions.spacing.md)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const DialogTitle = styled(Text)({
|
|
70
|
+
fontSize: parseSize(inputTokens.field.placeholder.default.fontSize),
|
|
71
|
+
fontWeight: inputTokens.text.label.fontWeight,
|
|
72
|
+
color: inputTokens.colour.field.text.default,
|
|
73
|
+
marginBottom: parseSize(dimensions.spacing.md),
|
|
74
|
+
marginLeft: parseSize(dimensions.spacing.xs)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const AndroidOptionRow = styled(View)({
|
|
78
|
+
flexDirection: "row",
|
|
79
|
+
alignItems: "center",
|
|
80
|
+
paddingVertical: parseSize(dimensions.spacing.md),
|
|
81
|
+
paddingHorizontal: parseSize(dimensions.spacing.sm),
|
|
82
|
+
borderBottomWidth: parseSize(dimensions.borderWidth.sm),
|
|
83
|
+
borderBottomColor: themeTokens.primitives.colour.brand.brown[2]
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
const RadioOuter = styled(View)({
|
|
87
|
+
width: parseSize(dimensions.spacing.lg),
|
|
88
|
+
height: parseSize(dimensions.spacing.lg),
|
|
89
|
+
borderRadius: parseSize(dimensions.spacing.lg) / 2,
|
|
90
|
+
borderWidth: parseSize(dimensions.borderWidth.md),
|
|
91
|
+
borderColor: inputTokens.colour.field.border.selected,
|
|
92
|
+
alignItems: "center",
|
|
93
|
+
justifyContent: "center",
|
|
94
|
+
marginRight: parseSize(dimensions.spacing.sm)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const RadioInner = styled(View)({
|
|
98
|
+
width: parseSize(dimensions.spacing.md) / 2,
|
|
99
|
+
height: parseSize(dimensions.spacing.md) / 2,
|
|
100
|
+
borderRadius: parseSize(dimensions.spacing.md) / 4,
|
|
101
|
+
backgroundColor: inputTokens.colour.field.border.selected
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const AndroidOptionText = styled(Text)<AndroidOptionTextProps>(
|
|
105
|
+
({ isSelected }) => ({
|
|
106
|
+
fontSize: parseSize(inputTokens.field.placeholder.default.fontSize),
|
|
107
|
+
fontWeight: isSelected
|
|
108
|
+
? inputTokens.text.label.fontWeight
|
|
109
|
+
: inputTokens.field.placeholder.default.fontWeight,
|
|
110
|
+
color: inputTokens.colour.field.text.default,
|
|
111
|
+
flex: 1
|
|
112
|
+
})
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
Wrapper,
|
|
117
|
+
SelectIcon,
|
|
118
|
+
InputText,
|
|
119
|
+
PlaceholderText,
|
|
120
|
+
ModalOverlay,
|
|
121
|
+
AndroidDialog,
|
|
122
|
+
DialogTitle,
|
|
123
|
+
AndroidOptionRow,
|
|
124
|
+
RadioOuter,
|
|
125
|
+
RadioInner,
|
|
126
|
+
AndroidOptionText
|
|
127
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import React, { useCallback, useMemo, useState } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Platform,
|
|
4
|
+
ActionSheetIOS,
|
|
5
|
+
Modal,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
FlatList,
|
|
8
|
+
TouchableWithoutFeedback,
|
|
9
|
+
View,
|
|
10
|
+
ViewProps
|
|
11
|
+
} from "react-native"
|
|
12
|
+
import { useSharedValue, useAnimatedStyle } from "react-native-reanimated"
|
|
13
|
+
import { KeyboardArrowDown } from "@butternutbox/pawprint-icons/core"
|
|
14
|
+
import { DEFAULT_THEME_OPTIONS } from "@butternutbox/pawprint-tokens"
|
|
15
|
+
|
|
16
|
+
import * as S from "./NativeSelectPicker.styled"
|
|
17
|
+
|
|
18
|
+
const themeTokens = DEFAULT_THEME_OPTIONS.tokens
|
|
19
|
+
const { components, semantics } = themeTokens
|
|
20
|
+
const inputTokens = components.inputs
|
|
21
|
+
const { dimensions } = semantics
|
|
22
|
+
|
|
23
|
+
const parseSize = (value: string): number => {
|
|
24
|
+
return parseInt(value.replace("px", ""), 10)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type NativeSelectPickerItem = {
|
|
28
|
+
label: string
|
|
29
|
+
value: string | number
|
|
30
|
+
key?: string | number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type NativeSelectPickerProps = ViewProps & {
|
|
34
|
+
items: NativeSelectPickerItem[]
|
|
35
|
+
value?: string | number | null
|
|
36
|
+
placeholder?: {
|
|
37
|
+
label?: string
|
|
38
|
+
value?: string | number
|
|
39
|
+
}
|
|
40
|
+
onValueChange: (value: string | number) => void
|
|
41
|
+
disabled?: boolean
|
|
42
|
+
hideIcon?: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type IconAnimation = {
|
|
46
|
+
iconAnimationStyles: {
|
|
47
|
+
transform: Array<{ rotate: string }>
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const useIconAnimation = (): IconAnimation => {
|
|
52
|
+
const iconRotation = useSharedValue(0)
|
|
53
|
+
|
|
54
|
+
const iconAnimationStyles = useAnimatedStyle(() => {
|
|
55
|
+
"worklet"
|
|
56
|
+
return {
|
|
57
|
+
transform: [{ rotate: `${iconRotation.value}deg` }]
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
iconAnimationStyles
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* NativeSelectPicker is a native UI dropdown component that uses:
|
|
68
|
+
* - ActionSheetIOS on iOS (native system picker)
|
|
69
|
+
* - Modal with FlatList on Android
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* <NativeSelectPicker
|
|
74
|
+
* items={[
|
|
75
|
+
* { label: "Option 1", value: "opt1" },
|
|
76
|
+
* { label: "Option 2", value: "opt2" }
|
|
77
|
+
* ]}
|
|
78
|
+
* placeholder={{ label: "Select an option" }}
|
|
79
|
+
* value={selectedValue}
|
|
80
|
+
* onValueChange={setSelectedValue}
|
|
81
|
+
* />
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
const NativeSelectPicker = React.forwardRef<View, NativeSelectPickerProps>(
|
|
85
|
+
(
|
|
86
|
+
{
|
|
87
|
+
items,
|
|
88
|
+
value,
|
|
89
|
+
placeholder,
|
|
90
|
+
onValueChange,
|
|
91
|
+
disabled = false,
|
|
92
|
+
hideIcon = false,
|
|
93
|
+
...rest
|
|
94
|
+
},
|
|
95
|
+
ref
|
|
96
|
+
) => {
|
|
97
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
98
|
+
const { iconAnimationStyles } = useIconAnimation()
|
|
99
|
+
|
|
100
|
+
const hasPlaceholder =
|
|
101
|
+
placeholder &&
|
|
102
|
+
Object.keys(placeholder).length > 0 &&
|
|
103
|
+
placeholder.label !== undefined
|
|
104
|
+
|
|
105
|
+
const allItems = useMemo(() => {
|
|
106
|
+
if (hasPlaceholder) {
|
|
107
|
+
return [
|
|
108
|
+
{
|
|
109
|
+
label: placeholder.label!,
|
|
110
|
+
value: placeholder.value ?? "",
|
|
111
|
+
key: "__placeholder__"
|
|
112
|
+
},
|
|
113
|
+
...items
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
return items
|
|
117
|
+
}, [items, hasPlaceholder, placeholder])
|
|
118
|
+
|
|
119
|
+
const selectedItem = allItems.find((item) => item.value === value)
|
|
120
|
+
const displayLabel =
|
|
121
|
+
selectedItem?.label ?? (hasPlaceholder ? placeholder.label! : "")
|
|
122
|
+
|
|
123
|
+
const handleOpenIOS = useCallback(() => {
|
|
124
|
+
if (disabled) return
|
|
125
|
+
|
|
126
|
+
const options = [...allItems.map((item) => item.label), "Cancel"]
|
|
127
|
+
const cancelButtonIndex = options.length - 1
|
|
128
|
+
|
|
129
|
+
ActionSheetIOS.showActionSheetWithOptions(
|
|
130
|
+
{
|
|
131
|
+
options,
|
|
132
|
+
cancelButtonIndex
|
|
133
|
+
},
|
|
134
|
+
(buttonIndex) => {
|
|
135
|
+
if (buttonIndex !== cancelButtonIndex) {
|
|
136
|
+
onValueChange(allItems[buttonIndex].value)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
}, [disabled, allItems, onValueChange])
|
|
141
|
+
|
|
142
|
+
const handleOpenAndroid = useCallback(() => {
|
|
143
|
+
if (!disabled) {
|
|
144
|
+
setIsOpen(true)
|
|
145
|
+
}
|
|
146
|
+
}, [disabled])
|
|
147
|
+
|
|
148
|
+
const handleClose = useCallback(() => {
|
|
149
|
+
setIsOpen(false)
|
|
150
|
+
}, [])
|
|
151
|
+
|
|
152
|
+
const handleSelect = useCallback(
|
|
153
|
+
(itemValue: string | number) => {
|
|
154
|
+
onValueChange(itemValue)
|
|
155
|
+
setIsOpen(false)
|
|
156
|
+
},
|
|
157
|
+
[onValueChange]
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<S.Wrapper ref={ref} {...rest}>
|
|
162
|
+
<TouchableOpacity
|
|
163
|
+
activeOpacity={0.8}
|
|
164
|
+
onPress={Platform.OS === "ios" ? handleOpenIOS : handleOpenAndroid}
|
|
165
|
+
disabled={disabled}
|
|
166
|
+
hitSlop={{
|
|
167
|
+
top: parseSize(dimensions.spacing.md),
|
|
168
|
+
right: parseSize(dimensions.spacing.md),
|
|
169
|
+
bottom: parseSize(dimensions.spacing.md),
|
|
170
|
+
left: parseSize(dimensions.spacing.md)
|
|
171
|
+
}}
|
|
172
|
+
accessibilityRole="button"
|
|
173
|
+
style={{ flexDirection: "row", alignItems: "center" }}
|
|
174
|
+
>
|
|
175
|
+
<S.InputText numberOfLines={1}>
|
|
176
|
+
{displayLabel || (
|
|
177
|
+
<S.PlaceholderText>
|
|
178
|
+
{placeholder?.label || "Select"}
|
|
179
|
+
</S.PlaceholderText>
|
|
180
|
+
)}
|
|
181
|
+
</S.InputText>
|
|
182
|
+
{!hideIcon && (
|
|
183
|
+
<S.SelectIcon style={[iconAnimationStyles]}>
|
|
184
|
+
<KeyboardArrowDown
|
|
185
|
+
width={parseSize(dimensions.spacing.xl)}
|
|
186
|
+
height={parseSize(dimensions.spacing.xl)}
|
|
187
|
+
color={inputTokens.colour.field.text.default}
|
|
188
|
+
/>
|
|
189
|
+
</S.SelectIcon>
|
|
190
|
+
)}
|
|
191
|
+
</TouchableOpacity>
|
|
192
|
+
|
|
193
|
+
{Platform.OS === "android" && (
|
|
194
|
+
<Modal
|
|
195
|
+
visible={isOpen}
|
|
196
|
+
transparent
|
|
197
|
+
animationType="fade"
|
|
198
|
+
onRequestClose={handleClose}
|
|
199
|
+
>
|
|
200
|
+
<TouchableWithoutFeedback
|
|
201
|
+
accessibilityRole="button"
|
|
202
|
+
onPress={handleClose}
|
|
203
|
+
>
|
|
204
|
+
<S.ModalOverlay>
|
|
205
|
+
<TouchableWithoutFeedback>
|
|
206
|
+
<S.AndroidDialog>
|
|
207
|
+
<S.DialogTitle>{"Select an option"}</S.DialogTitle>
|
|
208
|
+
<FlatList
|
|
209
|
+
data={allItems}
|
|
210
|
+
keyExtractor={(item, index) =>
|
|
211
|
+
item.key?.toString() ?? `${item.value}-${index}`
|
|
212
|
+
}
|
|
213
|
+
renderItem={({ item }) => {
|
|
214
|
+
const isSelected = item.value === value
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<TouchableOpacity
|
|
218
|
+
onPress={() => handleSelect(item.value)}
|
|
219
|
+
activeOpacity={0.7}
|
|
220
|
+
accessibilityRole="button"
|
|
221
|
+
>
|
|
222
|
+
<S.AndroidOptionRow>
|
|
223
|
+
<S.RadioOuter>
|
|
224
|
+
{isSelected && <S.RadioInner />}
|
|
225
|
+
</S.RadioOuter>
|
|
226
|
+
<S.AndroidOptionText isSelected={isSelected}>
|
|
227
|
+
{item.label}
|
|
228
|
+
</S.AndroidOptionText>
|
|
229
|
+
</S.AndroidOptionRow>
|
|
230
|
+
</TouchableOpacity>
|
|
231
|
+
)
|
|
232
|
+
}}
|
|
233
|
+
style={{
|
|
234
|
+
maxHeight: parseSize(dimensions.spacing["8xl"])
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
</S.AndroidDialog>
|
|
238
|
+
</TouchableWithoutFeedback>
|
|
239
|
+
</S.ModalOverlay>
|
|
240
|
+
</TouchableWithoutFeedback>
|
|
241
|
+
</Modal>
|
|
242
|
+
)}
|
|
243
|
+
</S.Wrapper>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
NativeSelectPicker.displayName = "NativeSelectPicker"
|
|
249
|
+
|
|
250
|
+
export { NativeSelectPicker }
|
|
@@ -53,6 +53,7 @@ export const AllInlineVariants = {
|
|
|
53
53
|
type="error"
|
|
54
54
|
title="Optional headline here"
|
|
55
55
|
onClose={() => {}}
|
|
56
|
+
link={{ label: "Optional link" }}
|
|
56
57
|
>
|
|
57
58
|
Try to keep the copy length here to a maximum of 2 lines
|
|
58
59
|
</Notification>
|
|
@@ -60,6 +61,7 @@ export const AllInlineVariants = {
|
|
|
60
61
|
type="success"
|
|
61
62
|
title="Optional headline here"
|
|
62
63
|
onClose={() => {}}
|
|
64
|
+
link={{ label: "Optional link" }}
|
|
63
65
|
>
|
|
64
66
|
Try to keep the copy length here to a maximum of 2 lines
|
|
65
67
|
</Notification>
|
|
@@ -67,6 +69,7 @@ export const AllInlineVariants = {
|
|
|
67
69
|
type="warning"
|
|
68
70
|
title="Optional headline here"
|
|
69
71
|
onClose={() => {}}
|
|
72
|
+
link={{ label: "Optional link" }}
|
|
70
73
|
>
|
|
71
74
|
Try to keep the copy length here to a maximum of 2 lines
|
|
72
75
|
</Notification>
|
|
@@ -74,6 +77,7 @@ export const AllInlineVariants = {
|
|
|
74
77
|
type="info"
|
|
75
78
|
title="Optional headline here"
|
|
76
79
|
onClose={() => {}}
|
|
80
|
+
link={{ label: "Optional link" }}
|
|
77
81
|
>
|
|
78
82
|
Try to keep the copy length here to a maximum of 2 lines
|
|
79
83
|
</Notification>
|
|
@@ -278,30 +278,36 @@ export const Notification = React.forwardRef<View, NotificationProps>(
|
|
|
278
278
|
rootAlignCenter={isLarge}
|
|
279
279
|
{...(rest as ViewProps)}
|
|
280
280
|
>
|
|
281
|
-
{
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
colour={type}
|
|
286
|
-
aria-label={type}
|
|
287
|
-
/>
|
|
288
|
-
)}
|
|
289
|
-
<View style={{ flexDirection: "column", gap: 8, flex: 1 }}>
|
|
290
|
-
<Typography
|
|
291
|
-
token={systemNotifications.notifications.typography.default}
|
|
292
|
-
color={systemNotifications.notification.colour.text.default}
|
|
293
|
-
>
|
|
294
|
-
{children}
|
|
295
|
-
</Typography>
|
|
296
|
-
{link && (
|
|
297
|
-
<Link
|
|
298
|
-
href={link.href}
|
|
299
|
-
onPress={link.onPress}
|
|
300
|
-
weight="semiBold"
|
|
281
|
+
<View style={{ flexDirection: "column", flex: 1 }}>
|
|
282
|
+
{showIcon && (
|
|
283
|
+
<Icon
|
|
284
|
+
icon={IconComponent}
|
|
301
285
|
size="md"
|
|
286
|
+
colour={type}
|
|
287
|
+
aria-label={type}
|
|
288
|
+
/>
|
|
289
|
+
)}
|
|
290
|
+
<View style={{ flexDirection: "column", gap: 8, flex: 1 }}>
|
|
291
|
+
<Typography
|
|
292
|
+
token={systemNotifications.notifications.typography.default}
|
|
293
|
+
color={systemNotifications.notification.colour.text.default}
|
|
294
|
+
>
|
|
295
|
+
{children}
|
|
296
|
+
</Typography>
|
|
297
|
+
</View>
|
|
298
|
+
{link && (
|
|
299
|
+
<StyledLinkWrapper
|
|
300
|
+
linkPaddingLeft={parseTokenValue(sizeTokens.content.gap)}
|
|
302
301
|
>
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
<Link
|
|
303
|
+
href={link.href}
|
|
304
|
+
onPress={link.onPress}
|
|
305
|
+
weight="semiBold"
|
|
306
|
+
size="md"
|
|
307
|
+
>
|
|
308
|
+
{link.label}
|
|
309
|
+
</Link>
|
|
310
|
+
</StyledLinkWrapper>
|
|
305
311
|
)}
|
|
306
312
|
</View>
|
|
307
313
|
</StyledSystemRoot>
|
|
@@ -418,22 +424,23 @@ export const Notification = React.forwardRef<View, NotificationProps>(
|
|
|
418
424
|
>
|
|
419
425
|
{children}
|
|
420
426
|
</Typography>
|
|
427
|
+
|
|
428
|
+
{link && (
|
|
429
|
+
<StyledLinkWrapper
|
|
430
|
+
linkPaddingLeft={parseTokenValue(content.copy.spacing.gap)}
|
|
431
|
+
>
|
|
432
|
+
<Link
|
|
433
|
+
href={link.href}
|
|
434
|
+
onPress={link.onPress}
|
|
435
|
+
weight="semiBold"
|
|
436
|
+
size="md"
|
|
437
|
+
>
|
|
438
|
+
{link.label}
|
|
439
|
+
</Link>
|
|
440
|
+
</StyledLinkWrapper>
|
|
441
|
+
)}
|
|
421
442
|
</StyledInlineCopy>
|
|
422
443
|
</StyledCopyRow>
|
|
423
|
-
{link && (
|
|
424
|
-
<StyledLinkWrapper
|
|
425
|
-
linkPaddingLeft={parseTokenValue(content.copy.spacing.gap)}
|
|
426
|
-
>
|
|
427
|
-
<Link
|
|
428
|
-
href={link.href}
|
|
429
|
-
onPress={link.onPress}
|
|
430
|
-
weight="semiBold"
|
|
431
|
-
size="md"
|
|
432
|
-
>
|
|
433
|
-
{link.label}
|
|
434
|
-
</Link>
|
|
435
|
-
</StyledLinkWrapper>
|
|
436
|
-
)}
|
|
437
444
|
</StyledContents>
|
|
438
445
|
{onClose && (
|
|
439
446
|
<StyledCloseButton
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import { View, StyleSheet } from "react-native"
|
|
3
|
+
import { Home } from "@butternutbox/pawprint-icons/core"
|
|
3
4
|
import { TabNavigation } from "./TabNavigation"
|
|
4
5
|
import type { TabNavigationProps } from "./TabNavigation"
|
|
5
6
|
import { Typography } from "../../atoms/Typography"
|
|
@@ -149,6 +150,40 @@ export const States = {
|
|
|
149
150
|
)
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
const DOG_PHOTO_URL = "https://placedog.net/200/200"
|
|
154
|
+
|
|
155
|
+
export const WithIconsAndAvatars = {
|
|
156
|
+
name: "With Icons & Avatars",
|
|
157
|
+
render: () => (
|
|
158
|
+
<View style={styles.container}>
|
|
159
|
+
<TabNavigation defaultValue="home" layout="fixed">
|
|
160
|
+
<TabNavigation.List>
|
|
161
|
+
<TabNavigation.Tab value="home" icon={Home}>
|
|
162
|
+
Home
|
|
163
|
+
</TabNavigation.Tab>
|
|
164
|
+
<TabNavigation.Tab
|
|
165
|
+
value="dog"
|
|
166
|
+
avatar={{
|
|
167
|
+
src: DOG_PHOTO_URL,
|
|
168
|
+
alt: "Your dog",
|
|
169
|
+
size: "sm",
|
|
170
|
+
border: "sm"
|
|
171
|
+
}}
|
|
172
|
+
>
|
|
173
|
+
Your dog
|
|
174
|
+
</TabNavigation.Tab>
|
|
175
|
+
</TabNavigation.List>
|
|
176
|
+
<TabNavigation.Panel value="home">
|
|
177
|
+
<PanelBody>A leading icon sits before the label.</PanelBody>
|
|
178
|
+
</TabNavigation.Panel>
|
|
179
|
+
<TabNavigation.Panel value="dog">
|
|
180
|
+
<PanelBody>The Avatar atom can lead the label too.</PanelBody>
|
|
181
|
+
</TabNavigation.Panel>
|
|
182
|
+
</TabNavigation>
|
|
183
|
+
</View>
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
|
|
152
187
|
export const AnimatedContent = {
|
|
153
188
|
name: "Animated Content (fade)",
|
|
154
189
|
render: () => (
|