@butternutbox/pawprint-native 0.0.1 → 0.1.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/COMPONENT_GUIDELINES.md +111 -4
- package/dist/index.cjs +12370 -1455
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1110 -11
- package/dist/index.d.ts +1110 -11
- package/dist/index.js +12324 -1455
- package/dist/index.js.map +1 -1
- package/package.json +28 -9
- package/src/__mocks__/asset-stub.ts +1 -0
- package/src/__mocks__/emotion-native.tsx +18 -0
- package/src/__mocks__/react-native-gesture-handler.tsx +41 -0
- package/src/__mocks__/react-native-reanimated.tsx +79 -0
- package/src/__mocks__/react-native-safe-area-context.tsx +6 -0
- package/src/__mocks__/react-native-svg.tsx +27 -0
- package/src/__mocks__/react-native-worklets.tsx +11 -0
- package/src/__mocks__/react-native.tsx +338 -0
- package/src/__mocks__/rn-primitives/avatar.tsx +24 -0
- package/src/__mocks__/rn-primitives/checkbox.tsx +19 -0
- package/src/__mocks__/rn-primitives/select.tsx +116 -0
- package/src/__mocks__/rn-primitives/slider.tsx +40 -0
- package/src/__mocks__/rn-primitives/slot.tsx +30 -0
- package/src/__mocks__/rn-primitives/switch.tsx +24 -0
- package/src/__mocks__/rn-primitives/toggle.tsx +16 -0
- package/src/components/atoms/Avatar/Avatar.stories.tsx +57 -49
- package/src/components/atoms/Avatar/Avatar.test.tsx +269 -0
- package/src/components/atoms/Avatar/Avatar.tsx +68 -22
- package/src/components/atoms/Avatar/index.ts +1 -6
- package/src/components/atoms/Badge/Badge.stories.tsx +5 -29
- package/src/components/atoms/Badge/Badge.test.tsx +90 -0
- package/src/components/atoms/Button/Button.test.tsx +123 -0
- package/src/components/atoms/Button/Button.tsx +1 -1
- package/src/components/atoms/CarouselControls/CarouselControls.stories.tsx +217 -0
- package/src/components/atoms/CarouselControls/CarouselControls.tsx +127 -0
- package/src/components/atoms/CarouselControls/index.ts +2 -0
- package/src/components/atoms/Hint/Hint.test.tsx +36 -0
- package/src/components/atoms/Icon/Icon.test.tsx +98 -0
- package/src/components/atoms/Icon/Icon.tsx +5 -1
- package/src/components/atoms/IconButton/IconButton.test.tsx +101 -0
- package/src/components/atoms/Illustration/Illustration.test.tsx +55 -0
- package/src/components/atoms/Input/Input.stories.tsx +129 -86
- package/src/components/atoms/Input/Input.test.tsx +306 -0
- package/src/components/atoms/Input/Input.tsx +9 -1
- package/src/components/atoms/Input/InputField.tsx +226 -74
- package/src/components/atoms/Link/Link.test.tsx +89 -0
- package/src/components/atoms/Logo/Logo.registry.ts +30 -5
- package/src/components/atoms/Logo/Logo.stories.tsx +108 -0
- package/src/components/atoms/Logo/Logo.test.tsx +56 -0
- package/src/components/atoms/Logo/assets/BCorp.tsx +113 -0
- package/src/components/atoms/Logo/assets/ButternutFavicon.tsx +33 -0
- package/src/components/atoms/Logo/assets/ButternutPrimary.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutTabbedBottom.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutTabbedTop.tsx +294 -0
- package/src/components/atoms/Logo/assets/ButternutWordmark.tsx +274 -0
- package/src/components/atoms/Logo/assets/PsiBufetFavicon.tsx +45 -0
- package/src/components/atoms/Logo/assets/PsiBufetPrimary.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetTabbedBottom.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetTabbedTop.tsx +218 -0
- package/src/components/atoms/Logo/assets/PsiBufetWordmark.tsx +195 -0
- package/src/components/atoms/Logo/assets/index.ts +11 -0
- package/src/components/atoms/NumberInput/NumberInput.stories.tsx +183 -0
- package/src/components/atoms/NumberInput/NumberInput.test.tsx +261 -0
- package/src/components/atoms/NumberInput/NumberInput.tsx +129 -0
- package/src/components/atoms/NumberInput/NumberInputField.tsx +77 -0
- package/src/components/atoms/NumberInput/index.ts +4 -0
- package/src/components/atoms/Spinner/Spinner.test.tsx +46 -0
- package/src/components/atoms/Spinner/Spinner.tsx +14 -5
- package/src/components/atoms/Switch/Switch.test.tsx +92 -0
- package/src/components/atoms/Switch/Switch.tsx +16 -13
- package/src/components/atoms/Tag/Tag.test.tsx +70 -0
- package/src/components/atoms/TextArea/TextArea.stories.tsx +303 -0
- package/src/components/atoms/TextArea/TextArea.test.tsx +416 -0
- package/src/components/atoms/TextArea/TextArea.tsx +171 -0
- package/src/components/atoms/TextArea/TextAreaField.tsx +304 -0
- package/src/components/atoms/TextArea/TextAreaLabel.tsx +103 -0
- package/src/components/atoms/TextArea/index.ts +6 -0
- package/src/components/atoms/Typography/Typography.test.tsx +94 -0
- package/src/components/atoms/index.ts +3 -0
- package/src/components/molecules/Accordion/Accordion.stories.tsx +177 -0
- package/src/components/molecules/Accordion/Accordion.test.tsx +185 -0
- package/src/components/molecules/Accordion/Accordion.tsx +284 -0
- package/src/components/molecules/Accordion/index.ts +6 -0
- package/src/components/molecules/Animated/Animated.stories.tsx +254 -0
- package/src/components/molecules/Animated/Animated.tsx +283 -0
- package/src/components/molecules/Animated/index.ts +10 -0
- package/src/components/molecules/ButtonDock/ButtonDock.test.tsx +83 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +8 -14
- package/src/components/molecules/ButtonGroup/ButtonGroup.test.tsx +73 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +25 -3
- package/src/components/molecules/Checkbox/Checkbox.stories.tsx +72 -0
- package/src/components/molecules/Checkbox/Checkbox.test.tsx +117 -0
- package/src/components/molecules/Checkbox/Checkbox.tsx +101 -95
- package/src/components/molecules/CopyField/CopyField.stories.tsx +313 -0
- package/src/components/molecules/CopyField/CopyField.test.tsx +431 -0
- package/src/components/molecules/CopyField/CopyField.tsx +156 -0
- package/src/components/molecules/CopyField/CopyFieldInput.tsx +127 -0
- package/src/components/molecules/CopyField/hooks/index.ts +1 -0
- package/src/components/molecules/CopyField/hooks/useCopyField.ts +25 -0
- package/src/components/molecules/CopyField/index.ts +4 -0
- package/src/components/molecules/DatePicker/DatePicker.stories.tsx +298 -0
- package/src/components/molecules/DatePicker/DatePicker.test.tsx +201 -0
- package/src/components/molecules/DatePicker/DatePicker.tsx +590 -0
- package/src/components/molecules/DatePicker/index.ts +2 -0
- package/src/components/molecules/Drawer/Drawer.stories.tsx +285 -0
- package/src/components/molecules/Drawer/Drawer.test.tsx +180 -0
- package/src/components/molecules/Drawer/Drawer.tsx +187 -0
- package/src/components/molecules/Drawer/DrawerBody.tsx +80 -0
- package/src/components/molecules/Drawer/DrawerClose.tsx +76 -0
- package/src/components/molecules/Drawer/DrawerContent.tsx +339 -0
- package/src/components/molecules/Drawer/DrawerContext.ts +19 -0
- package/src/components/molecules/Drawer/DrawerDescription.tsx +31 -0
- package/src/components/molecules/Drawer/DrawerDragContext.ts +11 -0
- package/src/components/molecules/Drawer/DrawerFooter.tsx +49 -0
- package/src/components/molecules/Drawer/DrawerFooterContext.ts +6 -0
- package/src/components/molecules/Drawer/DrawerGrabber.tsx +62 -0
- package/src/components/molecules/Drawer/DrawerHeader.tsx +244 -0
- package/src/components/molecules/Drawer/DrawerHeaderContext.ts +13 -0
- package/src/components/molecules/Drawer/DrawerOverlay.tsx +53 -0
- package/src/components/molecules/Drawer/DrawerTitle.tsx +32 -0
- package/src/components/molecules/Drawer/index.ts +12 -0
- package/src/components/molecules/FilterTab/FilterTab.stories.tsx +210 -0
- package/src/components/molecules/FilterTab/FilterTab.tsx +310 -0
- package/src/components/molecules/FilterTab/index.ts +2 -0
- package/src/components/molecules/MessageCard/MessageCard.stories.tsx +169 -0
- package/src/components/molecules/MessageCard/MessageCard.tsx +362 -0
- package/src/components/molecules/MessageCard/index.ts +10 -0
- package/src/components/molecules/Notification/Notification.stories.tsx +219 -0
- package/src/components/molecules/Notification/Notification.tsx +426 -0
- package/src/components/molecules/Notification/index.ts +2 -0
- package/src/components/molecules/NumberField/NumberField.stories.tsx +231 -0
- package/src/components/molecules/NumberField/NumberField.tsx +186 -0
- package/src/components/molecules/NumberField/NumberFieldInput.tsx +287 -0
- package/src/components/molecules/NumberField/index.ts +2 -0
- package/src/components/molecules/PasswordField/PasswordField.stories.tsx +362 -0
- package/src/components/molecules/PasswordField/PasswordField.test.tsx +369 -0
- package/src/components/molecules/PasswordField/PasswordField.tsx +194 -0
- package/src/components/molecules/PasswordField/PasswordFieldError.tsx +52 -0
- package/src/components/molecules/PasswordField/PasswordFieldInput.tsx +73 -0
- package/src/components/molecules/PasswordField/PasswordFieldRequirements.tsx +92 -0
- package/src/components/molecules/PasswordField/hooks/index.ts +2 -0
- package/src/components/molecules/PasswordField/hooks/usePasswordField.ts +113 -0
- package/src/components/molecules/PasswordField/index.ts +10 -0
- package/src/components/molecules/PictureSelector/PictureSelector.stories.tsx +243 -0
- package/src/components/molecules/PictureSelector/PictureSelector.tsx +313 -0
- package/src/components/molecules/PictureSelector/index.ts +5 -0
- package/src/components/molecules/Progress/Progress.stories.tsx +145 -0
- package/src/components/molecules/Progress/Progress.tsx +184 -0
- package/src/components/molecules/Progress/index.ts +2 -0
- package/src/components/molecules/Radio/Radio.test.tsx +104 -0
- package/src/components/molecules/Radio/Radio.tsx +1 -2
- package/src/components/molecules/SearchField/SearchField.stories.tsx +242 -0
- package/src/components/molecules/SearchField/SearchField.test.tsx +318 -0
- package/src/components/molecules/SearchField/SearchField.tsx +143 -0
- package/src/components/molecules/SearchField/SearchFieldInput.tsx +63 -0
- package/src/components/molecules/SearchField/hooks/index.ts +1 -0
- package/src/components/molecules/SearchField/hooks/useSearchField.ts +56 -0
- package/src/components/molecules/SearchField/index.ts +4 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +31 -8
- package/src/components/molecules/SegmentedControl/SegmentedControl.test.tsx +141 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +237 -23
- package/src/components/molecules/SelectField/SelectField.stories.tsx +320 -0
- package/src/components/molecules/SelectField/SelectField.test.tsx +254 -0
- package/src/components/molecules/SelectField/SelectField.tsx +236 -0
- package/src/components/molecules/SelectField/SelectFieldContent.tsx +85 -0
- package/src/components/molecules/SelectField/SelectFieldItem.tsx +133 -0
- package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +170 -0
- package/src/components/molecules/SelectField/SelectFieldValue.tsx +31 -0
- package/src/components/molecules/SelectField/hooks/index.ts +2 -0
- package/src/components/molecules/SelectField/hooks/useSelectField.ts +84 -0
- package/src/components/molecules/SelectField/index.ts +10 -0
- package/src/components/molecules/Slider/Slider.test.tsx +102 -0
- package/src/components/molecules/Slider/Slider.tsx +293 -180
- package/src/components/molecules/Tooltip/Tooltip.stories.tsx +168 -0
- package/src/components/molecules/Tooltip/Tooltip.tsx +326 -0
- package/src/components/molecules/Tooltip/index.ts +2 -0
- package/src/components/molecules/index.ts +15 -0
- package/src/test-utils.tsx +20 -0
- package/tsconfig.json +1 -1
- package/tsup.config.ts +16 -2
- package/vitest.config.ts +114 -0
- package/vitest.setup.ts +16 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, Pressable, ViewProps, PressableProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { useTheme } from "@emotion/react"
|
|
5
|
+
import { Icon, type PawprintIcon } from "../../atoms/Icon"
|
|
6
|
+
import { Typography } from "../../atoms/Typography"
|
|
7
|
+
|
|
8
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
type FilterTabSize = "sm" | "lg"
|
|
11
|
+
|
|
12
|
+
type FilterTabRootOwnProps = {
|
|
13
|
+
size?: FilterTabSize
|
|
14
|
+
multiple?: boolean
|
|
15
|
+
value?: string[]
|
|
16
|
+
defaultValue?: string[]
|
|
17
|
+
onValueChange?: (value: string[]) => void
|
|
18
|
+
children: React.ReactNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type FilterTabProps = FilterTabRootOwnProps &
|
|
22
|
+
Omit<ViewProps, keyof FilterTabRootOwnProps>
|
|
23
|
+
|
|
24
|
+
type FilterTabItemOwnProps = {
|
|
25
|
+
value: string
|
|
26
|
+
label?: string
|
|
27
|
+
icon?: PawprintIcon
|
|
28
|
+
leadingIcon?: PawprintIcon
|
|
29
|
+
trailingIcon?: PawprintIcon
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type FilterTabItemProps = FilterTabItemOwnProps &
|
|
34
|
+
Omit<PressableProps, keyof FilterTabItemOwnProps | "children">
|
|
35
|
+
|
|
36
|
+
// ─── Context ──────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
type FilterTabContextValue = {
|
|
39
|
+
size: FilterTabSize
|
|
40
|
+
selectedValues: string[]
|
|
41
|
+
toggle: (value: string) => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const FilterTabContext = React.createContext<FilterTabContextValue>({
|
|
45
|
+
size: "sm",
|
|
46
|
+
selectedValues: [],
|
|
47
|
+
toggle: () => {}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
53
|
+
|
|
54
|
+
// ─── Styled Components ────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
const StyledRoot = styled(View)<{ rootGap: number }>(({ rootGap }) => ({
|
|
57
|
+
flexDirection: "row",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
gap: rootGap,
|
|
60
|
+
flexWrap: "nowrap"
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
const StyledItem = styled(Pressable)<{
|
|
64
|
+
itemHeight: number
|
|
65
|
+
itemMinWidth: number
|
|
66
|
+
itemPaddingH: number
|
|
67
|
+
itemPaddingV: number
|
|
68
|
+
itemBorderRadius: number
|
|
69
|
+
itemBgColor: string
|
|
70
|
+
itemOpacity: number
|
|
71
|
+
isIconOnly: boolean
|
|
72
|
+
}>(
|
|
73
|
+
({
|
|
74
|
+
itemHeight,
|
|
75
|
+
itemMinWidth,
|
|
76
|
+
itemPaddingH,
|
|
77
|
+
itemPaddingV,
|
|
78
|
+
itemBorderRadius,
|
|
79
|
+
itemBgColor,
|
|
80
|
+
itemOpacity,
|
|
81
|
+
isIconOnly
|
|
82
|
+
}) => ({
|
|
83
|
+
flexDirection: "row",
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
justifyContent: "center",
|
|
86
|
+
height: itemHeight,
|
|
87
|
+
...(isIconOnly
|
|
88
|
+
? { width: itemHeight, maxWidth: itemHeight }
|
|
89
|
+
: { minWidth: itemMinWidth }),
|
|
90
|
+
paddingHorizontal: itemPaddingH,
|
|
91
|
+
paddingVertical: itemPaddingV,
|
|
92
|
+
borderRadius: itemBorderRadius,
|
|
93
|
+
backgroundColor: itemBgColor,
|
|
94
|
+
opacity: itemOpacity,
|
|
95
|
+
flexShrink: 0
|
|
96
|
+
})
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// ─── FilterTab.Item ───────────────────────────────────────────────────────────
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* An individual filter tab button. Must be used inside a `FilterTab` container.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} value - Unique identifier for this tab item.
|
|
105
|
+
* @param {string} [label] - Text label. Omit for icon-only mode.
|
|
106
|
+
* @param {PawprintIcon} [icon] - Icon for icon-only mode (requires accessibilityLabel).
|
|
107
|
+
* @param {PawprintIcon} [leadingIcon] - Optional icon before the label.
|
|
108
|
+
* @param {PawprintIcon} [trailingIcon] - Optional icon after the label.
|
|
109
|
+
* @param {boolean} [disabled=false] - Disables the tab (inactive state).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```tsx
|
|
113
|
+
* import { FilterTab } from "@butternutbox/pawprint-native"
|
|
114
|
+
*
|
|
115
|
+
* <FilterTab.Item value="wellness" label="Wellness" />
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
const FilterTabItem = React.forwardRef<View, FilterTabItemProps>(
|
|
119
|
+
(
|
|
120
|
+
{
|
|
121
|
+
value,
|
|
122
|
+
label,
|
|
123
|
+
icon: IconOnlyIcon,
|
|
124
|
+
leadingIcon,
|
|
125
|
+
trailingIcon,
|
|
126
|
+
disabled = false,
|
|
127
|
+
accessibilityLabel,
|
|
128
|
+
...rest
|
|
129
|
+
},
|
|
130
|
+
ref
|
|
131
|
+
) => {
|
|
132
|
+
const theme = useTheme()
|
|
133
|
+
const { size, selectedValues, toggle } = React.useContext(FilterTabContext)
|
|
134
|
+
const isSelected = selectedValues.includes(value)
|
|
135
|
+
const isIconOnly = !!IconOnlyIcon && !label
|
|
136
|
+
const isActive = isSelected && !disabled
|
|
137
|
+
|
|
138
|
+
const { tabItem } = theme.tokens.components.filterTabs
|
|
139
|
+
const { dimensions } = theme.tokens.semantics
|
|
140
|
+
const height = parseTokenValue(
|
|
141
|
+
size === "lg" ? tabItem.size.large.height : tabItem.size.small.height
|
|
142
|
+
)
|
|
143
|
+
const minWidth = parseTokenValue(tabItem.size.small.minWidth)
|
|
144
|
+
const iconColour = isActive
|
|
145
|
+
? tabItem.colour.icon.selected
|
|
146
|
+
: tabItem.colour.icon.unselected
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<StyledItem
|
|
150
|
+
ref={ref}
|
|
151
|
+
accessible
|
|
152
|
+
accessibilityRole="button"
|
|
153
|
+
accessibilityState={{ selected: isSelected, disabled }}
|
|
154
|
+
accessibilityLabel={isIconOnly ? accessibilityLabel : label}
|
|
155
|
+
disabled={disabled}
|
|
156
|
+
onPress={() => {
|
|
157
|
+
if (!disabled) toggle(value)
|
|
158
|
+
}}
|
|
159
|
+
itemHeight={height}
|
|
160
|
+
itemMinWidth={minWidth}
|
|
161
|
+
itemPaddingH={parseTokenValue(tabItem.spacing.horizontalPadding)}
|
|
162
|
+
itemPaddingV={parseTokenValue(tabItem.spacing.verticalPadding)}
|
|
163
|
+
itemBorderRadius={parseTokenValue(tabItem.borderRadius.default)}
|
|
164
|
+
itemBgColor={
|
|
165
|
+
isActive
|
|
166
|
+
? tabItem.colour.background.selected
|
|
167
|
+
: tabItem.colour.background.default
|
|
168
|
+
}
|
|
169
|
+
itemOpacity={
|
|
170
|
+
disabled ? parseFloat(tabItem.opacity.disabled.default) : 1
|
|
171
|
+
}
|
|
172
|
+
isIconOnly={isIconOnly}
|
|
173
|
+
style={{ gap: parseTokenValue(dimensions.spacing["2xs"]) }}
|
|
174
|
+
{...rest}
|
|
175
|
+
>
|
|
176
|
+
{isIconOnly && IconOnlyIcon && (
|
|
177
|
+
<Icon
|
|
178
|
+
icon={IconOnlyIcon}
|
|
179
|
+
size="md"
|
|
180
|
+
customColour={iconColour}
|
|
181
|
+
aria-label={accessibilityLabel}
|
|
182
|
+
/>
|
|
183
|
+
)}
|
|
184
|
+
{!isIconOnly && leadingIcon && (
|
|
185
|
+
<Icon
|
|
186
|
+
icon={leadingIcon}
|
|
187
|
+
size="md"
|
|
188
|
+
customColour={iconColour}
|
|
189
|
+
aria-label={label}
|
|
190
|
+
/>
|
|
191
|
+
)}
|
|
192
|
+
{!isIconOnly && label && (
|
|
193
|
+
<Typography
|
|
194
|
+
token={
|
|
195
|
+
isActive
|
|
196
|
+
? tabItem.typography.label.default
|
|
197
|
+
: tabItem.typography.label.inactive
|
|
198
|
+
}
|
|
199
|
+
color={
|
|
200
|
+
disabled
|
|
201
|
+
? tabItem.colour.text.inactive
|
|
202
|
+
: isActive
|
|
203
|
+
? tabItem.colour.text.selected
|
|
204
|
+
: tabItem.colour.text.default
|
|
205
|
+
}
|
|
206
|
+
>
|
|
207
|
+
{label}
|
|
208
|
+
</Typography>
|
|
209
|
+
)}
|
|
210
|
+
{!isIconOnly && trailingIcon && (
|
|
211
|
+
<Icon
|
|
212
|
+
icon={trailingIcon}
|
|
213
|
+
size="md"
|
|
214
|
+
customColour={iconColour}
|
|
215
|
+
aria-label={label}
|
|
216
|
+
/>
|
|
217
|
+
)}
|
|
218
|
+
</StyledItem>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
FilterTabItem.displayName = "FilterTab.Item"
|
|
224
|
+
|
|
225
|
+
// ─── FilterTab (Root) ─────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* A horizontal group of pill-shaped filter buttons. One or more tabs can be
|
|
229
|
+
* selected at a time to filter content. Supports controlled and uncontrolled
|
|
230
|
+
* selection.
|
|
231
|
+
*
|
|
232
|
+
* @param {"sm" | "lg"} [size="sm"] - Size of all tab items.
|
|
233
|
+
* @param {boolean} [multiple=true] - Allow multiple items to be selected. When false, only one item can be selected at a time.
|
|
234
|
+
* @param {string[]} [value] - Controlled selected tab values.
|
|
235
|
+
* @param {string[]} [defaultValue=[]] - Uncontrolled default selected values.
|
|
236
|
+
* @param {(value: string[]) => void} [onValueChange] - Fires when selection changes.
|
|
237
|
+
* @param {React.ReactNode} children - One or more `FilterTab.Item` elements.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```tsx
|
|
241
|
+
* import { FilterTab } from "@butternutbox/pawprint-native"
|
|
242
|
+
*
|
|
243
|
+
* <FilterTab size="sm" defaultValue={["all"]}>
|
|
244
|
+
* <FilterTab.Item value="all" label="All" />
|
|
245
|
+
* <FilterTab.Item value="wellness" label="Wellness" />
|
|
246
|
+
* </FilterTab>
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
const FilterTabRoot = React.forwardRef<View, FilterTabProps>(
|
|
250
|
+
(
|
|
251
|
+
{
|
|
252
|
+
size = "sm",
|
|
253
|
+
multiple = true,
|
|
254
|
+
value: controlledValue,
|
|
255
|
+
defaultValue = [],
|
|
256
|
+
onValueChange,
|
|
257
|
+
children,
|
|
258
|
+
...rest
|
|
259
|
+
},
|
|
260
|
+
ref
|
|
261
|
+
) => {
|
|
262
|
+
const theme = useTheme()
|
|
263
|
+
const { filterTabs } = theme.tokens.components
|
|
264
|
+
|
|
265
|
+
const isControlled = controlledValue !== undefined
|
|
266
|
+
const [internalValue, setInternalValue] =
|
|
267
|
+
React.useState<string[]>(defaultValue)
|
|
268
|
+
const selectedValues = isControlled ? controlledValue : internalValue
|
|
269
|
+
|
|
270
|
+
const toggle = React.useCallback(
|
|
271
|
+
(itemValue: string) => {
|
|
272
|
+
let next: string[]
|
|
273
|
+
if (multiple) {
|
|
274
|
+
next = selectedValues.includes(itemValue)
|
|
275
|
+
? selectedValues.filter((v) => v !== itemValue)
|
|
276
|
+
: [...selectedValues, itemValue]
|
|
277
|
+
} else {
|
|
278
|
+
next = selectedValues.includes(itemValue) ? [] : [itemValue]
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!isControlled) setInternalValue(next)
|
|
282
|
+
onValueChange?.(next)
|
|
283
|
+
},
|
|
284
|
+
[selectedValues, isControlled, onValueChange, multiple]
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
const contextValue = React.useMemo(
|
|
288
|
+
() => ({ size, selectedValues, toggle }),
|
|
289
|
+
[size, selectedValues, toggle]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<FilterTabContext.Provider value={contextValue}>
|
|
294
|
+
<StyledRoot
|
|
295
|
+
ref={ref}
|
|
296
|
+
rootGap={parseTokenValue(filterTabs.filterTab.spacing.gap)}
|
|
297
|
+
{...rest}
|
|
298
|
+
>
|
|
299
|
+
{children}
|
|
300
|
+
</StyledRoot>
|
|
301
|
+
</FilterTabContext.Provider>
|
|
302
|
+
)
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
FilterTabRoot.displayName = "FilterTab"
|
|
307
|
+
|
|
308
|
+
export const FilterTab = Object.assign(FilterTabRoot, {
|
|
309
|
+
Item: FilterTabItem
|
|
310
|
+
})
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, StyleSheet } from "react-native"
|
|
3
|
+
import {
|
|
4
|
+
MessageCard,
|
|
5
|
+
type MessageCardInsightProps,
|
|
6
|
+
type MessageCardBannerProps
|
|
7
|
+
} from "./MessageCard"
|
|
8
|
+
import { CheckCircle } from "@butternutbox/pawprint-icons/core"
|
|
9
|
+
import { AlsatianChefsHat } from "@butternutbox/pawprint-illustrations/breeds"
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
title: "Molecules/MessageCard"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const InsightPlayground = (args: MessageCardInsightProps) => (
|
|
16
|
+
<View style={styles.column}>
|
|
17
|
+
<MessageCard.Insight {...args} icon={CheckCircle}>
|
|
18
|
+
{args.children}
|
|
19
|
+
</MessageCard.Insight>
|
|
20
|
+
</View>
|
|
21
|
+
)
|
|
22
|
+
InsightPlayground.args = {
|
|
23
|
+
variant: "standalone",
|
|
24
|
+
title: "Healthy from top to tail",
|
|
25
|
+
children: "Studies show fresh food improves dogs' poos, skin, coat, and gut."
|
|
26
|
+
}
|
|
27
|
+
InsightPlayground.argTypes = {
|
|
28
|
+
variant: {
|
|
29
|
+
control: { type: "select" },
|
|
30
|
+
options: ["standalone", "supporting"],
|
|
31
|
+
description:
|
|
32
|
+
"`supporting` renders a flat top edge when attached to a related element above."
|
|
33
|
+
},
|
|
34
|
+
title: {
|
|
35
|
+
control: { type: "text" },
|
|
36
|
+
description: "Optional headline (only rendered for `standalone`)."
|
|
37
|
+
},
|
|
38
|
+
children: { control: { type: "text" }, description: "Body copy." }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const BannerPlayground = (args: MessageCardBannerProps) => (
|
|
42
|
+
<View style={styles.column}>
|
|
43
|
+
<MessageCard.Banner
|
|
44
|
+
{...args}
|
|
45
|
+
media={{ type: "illustration", illustration: AlsatianChefsHat }}
|
|
46
|
+
>
|
|
47
|
+
{args.children}
|
|
48
|
+
</MessageCard.Banner>
|
|
49
|
+
</View>
|
|
50
|
+
)
|
|
51
|
+
BannerPlayground.args = {
|
|
52
|
+
colourScheme: "primary",
|
|
53
|
+
title: "Short headline",
|
|
54
|
+
children: "Lorem ipsum dolor sit amet consectetur sit varius.",
|
|
55
|
+
linkLabel: "Find out more",
|
|
56
|
+
linkHref: "https://butternutbox.com"
|
|
57
|
+
}
|
|
58
|
+
BannerPlayground.argTypes = {
|
|
59
|
+
colourScheme: {
|
|
60
|
+
control: { type: "select" },
|
|
61
|
+
options: ["primary", "secondary"],
|
|
62
|
+
description:
|
|
63
|
+
"Background colour scheme. `primary` is saturated yellow; `secondary` is a lighter cream."
|
|
64
|
+
},
|
|
65
|
+
title: { control: { type: "text" }, description: "Optional headline." },
|
|
66
|
+
children: { control: { type: "text" }, description: "Body copy." },
|
|
67
|
+
linkLabel: { control: { type: "text" }, description: "CTA label." },
|
|
68
|
+
linkHref: { control: { type: "text" }, description: "CTA target URL." }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const Insight = () => (
|
|
72
|
+
<View style={styles.column}>
|
|
73
|
+
<MessageCard.Insight icon={CheckCircle} title="Healthy from top to tail">
|
|
74
|
+
Studies show fresh food improves dogs' poos, skin, coat, and gut.
|
|
75
|
+
</MessageCard.Insight>
|
|
76
|
+
</View>
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
export const InsightSupporting = () => (
|
|
80
|
+
<View style={styles.column}>
|
|
81
|
+
<MessageCard.Insight variant="supporting" icon={CheckCircle}>
|
|
82
|
+
Studies show fresh food improves dogs' poos, skin, coat, and gut.
|
|
83
|
+
</MessageCard.Insight>
|
|
84
|
+
</View>
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
export const BannerAlt = () => (
|
|
88
|
+
<View style={styles.column}>
|
|
89
|
+
<MessageCard.Banner
|
|
90
|
+
media={{ type: "illustration", illustration: AlsatianChefsHat }}
|
|
91
|
+
title="Short headline"
|
|
92
|
+
linkLabel="Find out more"
|
|
93
|
+
linkHref="https://butternutbox.com"
|
|
94
|
+
>
|
|
95
|
+
Lorem ipsum dolor sit amet consectetur sit varius.
|
|
96
|
+
</MessageCard.Banner>
|
|
97
|
+
</View>
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
export const BannerSecondary = () => (
|
|
101
|
+
<View style={styles.column}>
|
|
102
|
+
<MessageCard.Banner
|
|
103
|
+
colourScheme="secondary"
|
|
104
|
+
media={{ type: "illustration", illustration: AlsatianChefsHat }}
|
|
105
|
+
title="Short headline"
|
|
106
|
+
linkLabel="Find out more"
|
|
107
|
+
linkHref="https://butternutbox.com"
|
|
108
|
+
>
|
|
109
|
+
Lorem ipsum dolor sit amet consectetur sit varius.
|
|
110
|
+
</MessageCard.Banner>
|
|
111
|
+
</View>
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
export const BannerImage = () => (
|
|
115
|
+
<View style={styles.column}>
|
|
116
|
+
<MessageCard.Banner
|
|
117
|
+
media={{
|
|
118
|
+
type: "image",
|
|
119
|
+
source: {
|
|
120
|
+
uri: "https://images.unsplash.com/photo-1587300003388-59208cc962cb?w=600"
|
|
121
|
+
},
|
|
122
|
+
alt: "Fresh dog food ingredients"
|
|
123
|
+
}}
|
|
124
|
+
title="Short headline"
|
|
125
|
+
linkLabel="Find out more"
|
|
126
|
+
linkHref="https://butternutbox.com"
|
|
127
|
+
>
|
|
128
|
+
Lorem ipsum dolor sit amet consectetur sit varius.
|
|
129
|
+
</MessageCard.Banner>
|
|
130
|
+
</View>
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
export const AllVariants = () => (
|
|
134
|
+
<View style={styles.column}>
|
|
135
|
+
<MessageCard.Insight icon={CheckCircle} title="Healthy from top to tail">
|
|
136
|
+
Studies show fresh food improves dogs' poos, skin, coat, and gut.
|
|
137
|
+
</MessageCard.Insight>
|
|
138
|
+
<MessageCard.Banner
|
|
139
|
+
media={{ type: "illustration", illustration: AlsatianChefsHat }}
|
|
140
|
+
title="Short headline"
|
|
141
|
+
linkLabel="Find out more"
|
|
142
|
+
linkHref="https://butternutbox.com"
|
|
143
|
+
>
|
|
144
|
+
Lorem ipsum dolor sit amet consectetur sit varius.
|
|
145
|
+
</MessageCard.Banner>
|
|
146
|
+
<MessageCard.Banner
|
|
147
|
+
media={{
|
|
148
|
+
type: "image",
|
|
149
|
+
source: {
|
|
150
|
+
uri: "https://images.unsplash.com/photo-1587300003388-59208cc962cb?w=600"
|
|
151
|
+
},
|
|
152
|
+
alt: "Fresh dog food ingredients"
|
|
153
|
+
}}
|
|
154
|
+
title="Short headline"
|
|
155
|
+
linkLabel="Find out more"
|
|
156
|
+
linkHref="https://butternutbox.com"
|
|
157
|
+
>
|
|
158
|
+
Lorem ipsum dolor sit amet consectetur sit varius.
|
|
159
|
+
</MessageCard.Banner>
|
|
160
|
+
</View>
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
const styles = StyleSheet.create({
|
|
164
|
+
column: {
|
|
165
|
+
flexDirection: "column",
|
|
166
|
+
gap: 16,
|
|
167
|
+
width: 358
|
|
168
|
+
}
|
|
169
|
+
})
|