@butternutbox/pawprint-native 1.1.2 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butternutbox/pawprint-native",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "ButternutBox Pawprint Design System - React Native Components",
6
6
  "main": "./dist/index.cjs",
@@ -56,17 +56,13 @@ const StyledRoot = styled(View)<{
56
56
  * ```
57
57
  *
58
58
  * @param illustration - **(required)** Illustration component
59
- <<<<<<< Updated upstream
60
59
  * @param size - *(optional)* Size variant: sm (default), lg, xl, xxl, xxxl
61
- =======
62
- * @param size - *(optional)* Size variant: sm (default), lg, xl
63
60
  * @param align - *(optional)* Flexbox alignItems (default: center)
64
61
  * @param justify - *(optional)* Flexbox justifyContent (default: center)
65
62
  * @param flexDirection - *(optional)* Flexbox flex direction
66
63
  * @param flexWrap - *(optional)* Flexbox wrap
67
64
  * @param alignContent - *(optional)* Flexbox alignContent
68
65
  * @param alignSelf - *(optional)* Flexbox alignSelf
69
- >>>>>>> Stashed changes
70
66
  * @param aria-label - *(optional)* Accessible label
71
67
  */
72
68
  const Illustration = React.forwardRef<View, IllustrationProps>(
@@ -0,0 +1,304 @@
1
+ import React from "react"
2
+ import { View, ScrollView, StyleSheet } from "react-native"
3
+ import {
4
+ FloatingActionButton,
5
+ type FloatingActionButtonProps
6
+ } from "./FloatingActionButton"
7
+ import { Typography } from "../../atoms/Typography"
8
+ import { Button } from "../../atoms/Button"
9
+ import { Basket } from "@butternutbox/pawprint-icons/core"
10
+
11
+ const Message = () => (
12
+ <>
13
+ <Typography variant="body" size="lg" weight="bold">
14
+ Free
15
+ </Typography>{" "}
16
+ gift in your next box
17
+ </>
18
+ )
19
+
20
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
21
+ ? Omit<T, K>
22
+ : never
23
+
24
+ // Applies the design pairing: with the `close` icon button the link reads
25
+ // "See more" (so there aren't two close actions) and the icon button dismisses;
26
+ // otherwise the link reads "Close" and dismisses.
27
+ const DismissibleFab = (
28
+ props: DistributiveOmit<
29
+ FloatingActionButtonProps,
30
+ "linkLabel" | "linkHref" | "onLinkPress"
31
+ >
32
+ ) => {
33
+ const [dismissed, setDismissed] = React.useState(false)
34
+ const dismiss = () => setDismissed(true)
35
+
36
+ if (dismissed) {
37
+ return (
38
+ <Button variant="outlined" onPress={() => setDismissed(false)}>
39
+ Show again
40
+ </Button>
41
+ )
42
+ }
43
+
44
+ if (props.iconButtonIcon === "close") {
45
+ return (
46
+ <FloatingActionButton
47
+ {...props}
48
+ linkLabel="See more"
49
+ onLinkPress={() => console.warn("See more pressed")}
50
+ onIconButtonPress={dismiss}
51
+ />
52
+ )
53
+ }
54
+
55
+ return (
56
+ <FloatingActionButton {...props} linkLabel="Close" onLinkPress={dismiss} />
57
+ )
58
+ }
59
+
60
+ export default {
61
+ title: "Molecules/FloatingActionButton",
62
+ component: FloatingActionButton,
63
+ argTypes: {
64
+ variant: {
65
+ control: { type: "select" },
66
+ options: ["primary", "secondary"],
67
+ description:
68
+ "Colour scheme. `primary` is pink with a white icon container; `secondary` is white with a cream icon container."
69
+ },
70
+ children: {
71
+ control: { type: "text" },
72
+ description: "Message copy."
73
+ },
74
+ linkLabel: {
75
+ control: { type: "text" },
76
+ description:
77
+ "Link text (required). Pair with `linkHref` or `onLinkPress`."
78
+ },
79
+ linkHref: {
80
+ control: { type: "text" },
81
+ description: "URL opened via `Linking` when pressed."
82
+ },
83
+ iconButtonIcon: {
84
+ control: { type: "select" },
85
+ options: ["chevronUp", "chevronRight", "close"],
86
+ description:
87
+ "Icon button (required). Limited to `chevronUp`, `chevronRight` and `close`."
88
+ },
89
+ iconButtonVariant: {
90
+ control: { type: "select" },
91
+ options: ["filled", "outlined", "text"],
92
+ description:
93
+ "Icon button style. Defaults to `filled` for the chevrons and `text` for `close`."
94
+ },
95
+ iconButtonLabel: {
96
+ control: { type: "text" },
97
+ description: "Accessible label for the icon button."
98
+ },
99
+ animation: {
100
+ control: { type: "select" },
101
+ options: [undefined, "shine", "nudge"],
102
+ description:
103
+ "Optional attention animation (off by default). Runs 3 times after a short delay, then stops. Disabled with reduced motion."
104
+ },
105
+ icon: {
106
+ control: false,
107
+ description:
108
+ "Icon in the left container. Accepts any Pawprint icon; defaults to `Gift`. See the CustomIcon story."
109
+ }
110
+ }
111
+ }
112
+
113
+ export const Playground = (args: FloatingActionButtonProps) => (
114
+ <View style={styles.container}>
115
+ <FloatingActionButton
116
+ {...args}
117
+ onLinkPress={() => console.warn("Link pressed")}
118
+ />
119
+ </View>
120
+ )
121
+ Playground.args = {
122
+ variant: "primary",
123
+ children: "Free gift in your next box",
124
+ linkLabel: "Close",
125
+ iconButtonIcon: "chevronUp",
126
+ iconButtonLabel: "View offer",
127
+ onIconButtonPress: () => console.warn("Icon button pressed")
128
+ }
129
+
130
+ export const Primary = () => (
131
+ <View style={styles.container}>
132
+ <DismissibleFab
133
+ variant="primary"
134
+ iconButtonIcon="chevronUp"
135
+ iconButtonLabel="View offer"
136
+ >
137
+ <Message />
138
+ </DismissibleFab>
139
+ </View>
140
+ )
141
+
142
+ export const Secondary = () => (
143
+ <View style={styles.container}>
144
+ <DismissibleFab
145
+ variant="secondary"
146
+ iconButtonIcon="close"
147
+ iconButtonLabel="Dismiss"
148
+ >
149
+ <Message />
150
+ </DismissibleFab>
151
+ </View>
152
+ )
153
+
154
+ export const AllVariants = () => (
155
+ <View style={[styles.container, styles.column]}>
156
+ {(["primary", "secondary"] as const).map((variant) =>
157
+ (["chevronUp", "chevronRight", "close"] as const).map(
158
+ (iconButtonIcon) => (
159
+ <DismissibleFab
160
+ key={`${variant}-${iconButtonIcon}`}
161
+ variant={variant}
162
+ iconButtonIcon={iconButtonIcon}
163
+ iconButtonLabel={
164
+ iconButtonIcon === "close" ? "Dismiss" : "View offer"
165
+ }
166
+ >
167
+ <Message />
168
+ </DismissibleFab>
169
+ )
170
+ )
171
+ )}
172
+ </View>
173
+ )
174
+
175
+ export const CustomIcon = () => (
176
+ <View style={styles.container}>
177
+ <DismissibleFab
178
+ icon={Basket}
179
+ iconButtonIcon="chevronRight"
180
+ iconButtonLabel="Go to basket"
181
+ >
182
+ Your box is ready to check out
183
+ </DismissibleFab>
184
+ </View>
185
+ )
186
+
187
+ export const LinkOpensAction = () => {
188
+ const [count, setCount] = React.useState(0)
189
+ const [dismissed, setDismissed] = React.useState(false)
190
+ return (
191
+ <View style={[styles.container, styles.column]}>
192
+ {dismissed ? (
193
+ <Button variant="outlined" onPress={() => setDismissed(false)}>
194
+ Show again
195
+ </Button>
196
+ ) : (
197
+ <FloatingActionButton
198
+ linkLabel="See more"
199
+ onLinkPress={() => setCount((c) => c + 1)}
200
+ iconButtonIcon="close"
201
+ iconButtonLabel="Dismiss"
202
+ onIconButtonPress={() => setDismissed(true)}
203
+ >
204
+ <Message />
205
+ </FloatingActionButton>
206
+ )}
207
+ <Typography size="sm">Link pressed {count} times</Typography>
208
+ </View>
209
+ )
210
+ }
211
+
212
+ // The Storybook decorator wraps stories in a ScrollView, where `flex: 1` has
213
+ // no height to fill — so the mock screen gets a fixed height to keep the inner
214
+ // ScrollView scrolling beneath the pinned FAB.
215
+ export const PinnedBottomRight = () => (
216
+ <View style={styles.screen}>
217
+ <ScrollView nestedScrollEnabled contentContainerStyle={styles.column}>
218
+ {Array.from({ length: 20 }, (_, i) => (
219
+ <Typography key={i}>Screen content line {i + 1}</Typography>
220
+ ))}
221
+ </ScrollView>
222
+ <View style={styles.pinned}>
223
+ <DismissibleFab iconButtonIcon="chevronUp" iconButtonLabel="View offer">
224
+ <Message />
225
+ </DismissibleFab>
226
+ </View>
227
+ </View>
228
+ )
229
+
230
+ export const IconButtonStyles = () => (
231
+ <View style={[styles.container, styles.column]}>
232
+ {(["filled", "outlined", "text"] as const).map((iconButtonVariant) =>
233
+ (["chevronUp", "chevronRight", "close"] as const).map(
234
+ (iconButtonIcon) => (
235
+ <DismissibleFab
236
+ key={`${iconButtonVariant}-${iconButtonIcon}`}
237
+ variant="secondary"
238
+ iconButtonIcon={iconButtonIcon}
239
+ iconButtonVariant={iconButtonVariant}
240
+ iconButtonLabel={`${iconButtonIcon} ${iconButtonVariant}`}
241
+ >
242
+ <Message />
243
+ </DismissibleFab>
244
+ )
245
+ )
246
+ )}
247
+ </View>
248
+ )
249
+
250
+ export const Animations = () => {
251
+ const [run, setRun] = React.useState(0)
252
+ return (
253
+ <View key={run} style={[styles.container, styles.column]}>
254
+ <Typography weight="semiBold">Shine</Typography>
255
+ <DismissibleFab
256
+ animation="shine"
257
+ iconButtonIcon="chevronUp"
258
+ iconButtonLabel="View offer"
259
+ >
260
+ <Message />
261
+ </DismissibleFab>
262
+ <DismissibleFab
263
+ animation="shine"
264
+ variant="secondary"
265
+ iconButtonIcon="close"
266
+ iconButtonLabel="Dismiss"
267
+ >
268
+ <Message />
269
+ </DismissibleFab>
270
+ <Typography weight="semiBold">Nudge</Typography>
271
+ <DismissibleFab
272
+ animation="nudge"
273
+ iconButtonIcon="chevronUp"
274
+ iconButtonLabel="View offer"
275
+ >
276
+ <Message />
277
+ </DismissibleFab>
278
+ <DismissibleFab
279
+ animation="nudge"
280
+ variant="secondary"
281
+ iconButtonIcon="chevronRight"
282
+ iconButtonLabel="View offer"
283
+ >
284
+ <Message />
285
+ </DismissibleFab>
286
+ <Button variant="outlined" onPress={() => setRun((r) => r + 1)}>
287
+ Replay
288
+ </Button>
289
+ </View>
290
+ )
291
+ }
292
+
293
+ const styles = StyleSheet.create({
294
+ container: { padding: 20 },
295
+ column: { flexDirection: "column", gap: 20 },
296
+ screen: { height: 600, padding: 16 },
297
+ pinned: {
298
+ position: "absolute",
299
+ right: 16,
300
+ bottom: 16,
301
+ left: 16,
302
+ alignItems: "flex-end"
303
+ }
304
+ })