@butternutbox/pawprint-native 0.8.0 → 0.10.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 +7 -7
- package/CHANGELOG.md +32 -0
- package/dist/index.cjs +1194 -1061
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -13
- package/dist/index.d.ts +18 -13
- package/dist/index.js +693 -561
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__mocks__/react-native.tsx +7 -0
- package/src/__mocks__/rn-primitives/select.tsx +58 -21
- package/src/components/atoms/Input/InputField.tsx +37 -6
- package/src/components/atoms/Input/index.ts +1 -1
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.stories.tsx +31 -75
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.test.tsx +1 -7
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.tsx +93 -121
- package/src/components/molecules/ProductListingCard/Badge.tsx +2 -2
- package/src/components/molecules/ProductListingCard/ProductListingCard.stories.tsx +65 -1
- package/src/components/molecules/ProductListingCard/ProductListingCard.tsx +38 -52
- package/src/components/molecules/SelectField/SelectField.stories.tsx +110 -5
- package/src/components/molecules/SelectField/SelectField.test.tsx +174 -2
- package/src/components/molecules/SelectField/SelectField.tsx +186 -30
- package/src/components/molecules/SelectField/SelectFieldContent.tsx +8 -12
- package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +43 -25
- package/src/components/molecules/SelectField/SelectFieldValue.tsx +17 -18
package/package.json
CHANGED
|
@@ -324,6 +324,13 @@ export const Platform = {
|
|
|
324
324
|
select: (obj: any) => obj.ios ?? obj.default
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
export const Keyboard = {
|
|
328
|
+
addListener: (_event: string, _handler: () => void) => ({
|
|
329
|
+
remove: () => {}
|
|
330
|
+
}),
|
|
331
|
+
dismiss: () => {}
|
|
332
|
+
}
|
|
333
|
+
|
|
327
334
|
// ─── Type exports (re-exported as empty for TS compat) ───────────────────
|
|
328
335
|
export type ViewProps = any
|
|
329
336
|
export type TextProps = any
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
|
|
3
|
+
const SelectCtx = React.createContext<{
|
|
4
|
+
open: boolean
|
|
5
|
+
setOpen: (open: boolean) => void
|
|
6
|
+
}>({ open: false, setOpen: () => {} })
|
|
7
|
+
|
|
3
8
|
export const Root = React.forwardRef<any, any>(
|
|
4
9
|
(
|
|
5
|
-
{
|
|
10
|
+
{
|
|
11
|
+
value,
|
|
12
|
+
defaultValue,
|
|
13
|
+
onValueChange,
|
|
14
|
+
disabled,
|
|
15
|
+
children,
|
|
16
|
+
onOpenChange,
|
|
17
|
+
...rest
|
|
18
|
+
},
|
|
6
19
|
ref
|
|
7
20
|
) => {
|
|
21
|
+
const [open, setOpenState] = React.useState(false)
|
|
8
22
|
const [internalValue, setInternalValue] = React.useState(
|
|
9
23
|
value ?? defaultValue ?? null
|
|
10
24
|
)
|
|
@@ -17,12 +31,19 @@ export const Root = React.forwardRef<any, any>(
|
|
|
17
31
|
}
|
|
18
32
|
}
|
|
19
33
|
|
|
34
|
+
const setOpen = (nextOpen: boolean) => {
|
|
35
|
+
setOpenState(nextOpen)
|
|
36
|
+
onOpenChange?.(nextOpen)
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
return (
|
|
21
|
-
<
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
40
|
+
<SelectCtx.Provider value={{ open, setOpen }}>
|
|
41
|
+
<div ref={ref} data-disabled={disabled} {...rest}>
|
|
42
|
+
{typeof children === "function"
|
|
43
|
+
? children({ value: currentValue, onChange: handleChange })
|
|
44
|
+
: children}
|
|
45
|
+
</div>
|
|
46
|
+
</SelectCtx.Provider>
|
|
26
47
|
)
|
|
27
48
|
}
|
|
28
49
|
)
|
|
@@ -30,11 +51,18 @@ Root.displayName = "SelectPrimitive.Root"
|
|
|
30
51
|
|
|
31
52
|
export const Trigger = React.forwardRef<any, any>(
|
|
32
53
|
({ asChild, children, ...rest }, ref) => {
|
|
54
|
+
const { open, setOpen } = React.useContext(SelectCtx)
|
|
55
|
+
const handlePress = () => setOpen(!open)
|
|
56
|
+
|
|
33
57
|
if (asChild && React.isValidElement(children)) {
|
|
34
|
-
return React.cloneElement(children, {
|
|
58
|
+
return React.cloneElement(children, {
|
|
59
|
+
ref,
|
|
60
|
+
onPress: handlePress,
|
|
61
|
+
...rest
|
|
62
|
+
} as any)
|
|
35
63
|
}
|
|
36
64
|
return (
|
|
37
|
-
<button ref={ref} role="button" {...rest}>
|
|
65
|
+
<button ref={ref} role="button" onClick={handlePress} {...rest}>
|
|
38
66
|
{children}
|
|
39
67
|
</button>
|
|
40
68
|
)
|
|
@@ -59,22 +87,20 @@ Value.displayName = "SelectPrimitive.Value"
|
|
|
59
87
|
export const Portal = ({ children }: any) => <>{children}</>
|
|
60
88
|
Portal.displayName = "SelectPrimitive.Portal"
|
|
61
89
|
|
|
62
|
-
export const Viewport = React.forwardRef<any, any>(
|
|
63
|
-
({ children, ...rest }, ref) => (
|
|
64
|
-
<div ref={ref} {...rest}>
|
|
65
|
-
{children}
|
|
66
|
-
</div>
|
|
67
|
-
)
|
|
68
|
-
)
|
|
69
|
-
Viewport.displayName = "SelectPrimitive.Viewport"
|
|
70
|
-
|
|
71
90
|
export const Overlay = React.forwardRef<any, any>(
|
|
72
|
-
({
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
({ onPress, closeOnPress = true, children, ...rest }, ref) => {
|
|
92
|
+
const { setOpen } = React.useContext(SelectCtx)
|
|
93
|
+
const handlePress = () => {
|
|
94
|
+
if (closeOnPress) setOpen(false)
|
|
95
|
+
onPress?.()
|
|
75
96
|
}
|
|
76
97
|
return (
|
|
77
|
-
<div
|
|
98
|
+
<div
|
|
99
|
+
ref={ref}
|
|
100
|
+
data-testid="select-overlay"
|
|
101
|
+
onClick={handlePress}
|
|
102
|
+
{...rest}
|
|
103
|
+
>
|
|
78
104
|
{children}
|
|
79
105
|
</div>
|
|
80
106
|
)
|
|
@@ -82,8 +108,19 @@ export const Overlay = React.forwardRef<any, any>(
|
|
|
82
108
|
)
|
|
83
109
|
Overlay.displayName = "SelectPrimitive.Overlay"
|
|
84
110
|
|
|
111
|
+
export const Viewport = React.forwardRef<any, any>(
|
|
112
|
+
({ children, ...rest }, ref) => (
|
|
113
|
+
<div ref={ref} {...rest}>
|
|
114
|
+
{children}
|
|
115
|
+
</div>
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
Viewport.displayName = "SelectPrimitive.Viewport"
|
|
119
|
+
|
|
85
120
|
export const Content = React.forwardRef<any, any>(
|
|
86
121
|
({ asChild, children, ...rest }, ref) => {
|
|
122
|
+
const { open } = React.useContext(SelectCtx)
|
|
123
|
+
if (!open) return null
|
|
87
124
|
if (asChild && React.isValidElement(children)) {
|
|
88
125
|
return React.cloneElement(children, { ref, ...rest } as any)
|
|
89
126
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Error as ErrorIcon,
|
|
14
14
|
CheckCircle as SuccessIcon
|
|
15
15
|
} from "@butternutbox/pawprint-icons/core"
|
|
16
|
+
import { resolveFont } from "../../../fonts"
|
|
16
17
|
import { Icon } from "../Icon"
|
|
17
18
|
|
|
18
19
|
export type InputState = "default" | "error" | "success"
|
|
@@ -61,18 +62,24 @@ const StyledInputWrapper = styled(Animated.View)<{
|
|
|
61
62
|
|
|
62
63
|
const StyledInput = styled(TextInput)(({ theme }) => {
|
|
63
64
|
const { colour, field } = theme.tokens.components.inputs
|
|
64
|
-
const
|
|
65
|
+
const typography = field.placeholder.default
|
|
66
|
+
const { fontFamily } = resolveFont(
|
|
67
|
+
typography.fontFamily,
|
|
68
|
+
typography.fontWeight
|
|
69
|
+
)
|
|
70
|
+
const fontSize = parseTokenValue(typography.fontSize)
|
|
71
|
+
const letterSpacing = typography.letterSpacing?.endsWith("%")
|
|
72
|
+
? (parseFloat(typography.letterSpacing) / 100) * fontSize
|
|
73
|
+
: parseFloat(typography.letterSpacing ?? "0")
|
|
65
74
|
return {
|
|
66
75
|
flex: 1,
|
|
67
76
|
minWidth: 0,
|
|
68
77
|
minHeight: 0,
|
|
69
78
|
padding: 0,
|
|
70
79
|
color: colour.field.text.default,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
lineHeight: parseTokenValue(placeholderTypography.lineHeight),
|
|
75
|
-
letterSpacing: parseTokenValue(placeholderTypography.letterSpacing),
|
|
80
|
+
fontFamily,
|
|
81
|
+
fontSize,
|
|
82
|
+
letterSpacing,
|
|
76
83
|
// Suppress the browser default focus outline on React Native Web —
|
|
77
84
|
// the focus indicator is rendered on the wrapper instead.
|
|
78
85
|
...({
|
|
@@ -275,3 +282,27 @@ export const InputField = React.forwardRef<TextInput, InputFieldProps>(
|
|
|
275
282
|
)
|
|
276
283
|
|
|
277
284
|
InputField.displayName = "Input.Field"
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Bare text input with design-system typography and colors applied.
|
|
288
|
+
* No container, border, or padding — use inside components that provide
|
|
289
|
+
* their own visual wrapper (e.g. SelectField trigger).
|
|
290
|
+
*/
|
|
291
|
+
export const InputText = React.forwardRef<TextInput, TextInputProps>(
|
|
292
|
+
({ placeholderTextColor, ...rest }, ref) => {
|
|
293
|
+
const theme = useTheme()
|
|
294
|
+
const { colour } = theme.tokens.components.inputs
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<StyledInput
|
|
298
|
+
ref={ref}
|
|
299
|
+
placeholderTextColor={
|
|
300
|
+
placeholderTextColor ?? colour.field.text.placeholder
|
|
301
|
+
}
|
|
302
|
+
{...rest}
|
|
303
|
+
/>
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
InputText.displayName = "Input.Text"
|
|
@@ -2,7 +2,7 @@ export { Input } from "./Input"
|
|
|
2
2
|
export type { InputProps } from "./Input"
|
|
3
3
|
export { InputLabel } from "./InputLabel"
|
|
4
4
|
export type { InputLabelProps } from "./InputLabel"
|
|
5
|
-
export { InputField } from "./InputField"
|
|
5
|
+
export { InputField, InputText } from "./InputField"
|
|
6
6
|
export type { InputFieldProps } from "./InputField"
|
|
7
7
|
export { InputDescription } from "./InputDescription"
|
|
8
8
|
export type { InputDescriptionProps } from "./InputDescription"
|
|
@@ -3,7 +3,8 @@ import { View, StyleSheet } from "react-native"
|
|
|
3
3
|
import { ProductDisplayCard } from "./ProductDisplayCard"
|
|
4
4
|
import type { ProductDisplayCardProps } from "./ProductDisplayCard"
|
|
5
5
|
|
|
6
|
-
const placeholderImageUrl =
|
|
6
|
+
const placeholderImageUrl =
|
|
7
|
+
"https://placehold.co/148x148/EEE/31343C?font=oswald&text=product"
|
|
7
8
|
|
|
8
9
|
const styles = StyleSheet.create({
|
|
9
10
|
column: { flexDirection: "column", gap: 24, padding: 16 }
|
|
@@ -13,11 +14,6 @@ export default {
|
|
|
13
14
|
title: "Molecules/ProductDisplayCard",
|
|
14
15
|
component: ProductDisplayCard,
|
|
15
16
|
argTypes: {
|
|
16
|
-
device: {
|
|
17
|
-
control: { type: "select" },
|
|
18
|
-
options: ["desktop", "mobile"],
|
|
19
|
-
description: "Device variant affecting sizing and typography"
|
|
20
|
-
},
|
|
21
17
|
title: {
|
|
22
18
|
control: { type: "text" },
|
|
23
19
|
description: "Product title"
|
|
@@ -38,10 +34,6 @@ export default {
|
|
|
38
34
|
control: { type: "boolean" },
|
|
39
35
|
description: "Show/hide quantity picker"
|
|
40
36
|
},
|
|
41
|
-
showImageQuantityBadge: {
|
|
42
|
-
control: { type: "boolean" },
|
|
43
|
-
description: "Show/hide quantity badge overlay on image"
|
|
44
|
-
},
|
|
45
37
|
disableQuantityButtons: {
|
|
46
38
|
control: { type: "boolean" },
|
|
47
39
|
description: "Disable quantity buttons"
|
|
@@ -50,14 +42,6 @@ export default {
|
|
|
50
42
|
control: { type: "boolean" },
|
|
51
43
|
description: "Hide quantity buttons"
|
|
52
44
|
},
|
|
53
|
-
showIncrementButton: {
|
|
54
|
-
control: { type: "boolean" },
|
|
55
|
-
description: "Show/hide increment button"
|
|
56
|
-
},
|
|
57
|
-
showDecrementButton: {
|
|
58
|
-
control: { type: "boolean" },
|
|
59
|
-
description: "Show/hide decrement button"
|
|
60
|
-
},
|
|
61
45
|
incrementDisabled: {
|
|
62
46
|
control: { type: "boolean" },
|
|
63
47
|
description: "Disable increment button"
|
|
@@ -100,7 +84,6 @@ export const Playground = (args: ProductDisplayCardProps) => (
|
|
|
100
84
|
</View>
|
|
101
85
|
)
|
|
102
86
|
Playground.args = {
|
|
103
|
-
device: "mobile",
|
|
104
87
|
title: "Recipe Name",
|
|
105
88
|
subtext: "+£0.00/pouch",
|
|
106
89
|
showSubtext: true,
|
|
@@ -127,20 +110,6 @@ export const Default = () => (
|
|
|
127
110
|
</View>
|
|
128
111
|
)
|
|
129
112
|
|
|
130
|
-
export const Desktop = () => (
|
|
131
|
-
<View style={styles.column}>
|
|
132
|
-
<ProductDisplayCard
|
|
133
|
-
title="Recipe Name"
|
|
134
|
-
subtext="+£0.00/pouch"
|
|
135
|
-
showSubtext
|
|
136
|
-
quantity={1}
|
|
137
|
-
showQuantityPicker
|
|
138
|
-
showBanner={false}
|
|
139
|
-
image={placeholderImageUrl}
|
|
140
|
-
/>
|
|
141
|
-
</View>
|
|
142
|
-
)
|
|
143
|
-
|
|
144
113
|
export const Mobile = () => (
|
|
145
114
|
<View style={styles.column}>
|
|
146
115
|
<ProductDisplayCard
|
|
@@ -202,32 +171,6 @@ export const NoSubtext = () => (
|
|
|
202
171
|
</View>
|
|
203
172
|
)
|
|
204
173
|
|
|
205
|
-
export const WithImageQuantityBadge = () => (
|
|
206
|
-
<View style={styles.column}>
|
|
207
|
-
<ProductDisplayCard
|
|
208
|
-
title="Gobble Gobble Turkey"
|
|
209
|
-
subtext="+£0.50/pouch"
|
|
210
|
-
showSubtext
|
|
211
|
-
quantity={4}
|
|
212
|
-
showQuantityPicker={false}
|
|
213
|
-
showImageQuantityBadge
|
|
214
|
-
showBanner={false}
|
|
215
|
-
image={placeholderImageUrl}
|
|
216
|
-
/>
|
|
217
|
-
|
|
218
|
-
<ProductDisplayCard
|
|
219
|
-
title="Salmon Delight"
|
|
220
|
-
subtext="+£0.25/pouch"
|
|
221
|
-
showSubtext
|
|
222
|
-
quantity={2}
|
|
223
|
-
showQuantityPicker
|
|
224
|
-
showImageQuantityBadge
|
|
225
|
-
showBanner={false}
|
|
226
|
-
image={placeholderImageUrl}
|
|
227
|
-
/>
|
|
228
|
-
</View>
|
|
229
|
-
)
|
|
230
|
-
|
|
231
174
|
export const AllVariants = () => (
|
|
232
175
|
<View style={styles.column}>
|
|
233
176
|
<ProductDisplayCard
|
|
@@ -263,27 +206,40 @@ export const AllVariants = () => (
|
|
|
263
206
|
</View>
|
|
264
207
|
)
|
|
265
208
|
|
|
266
|
-
export const
|
|
209
|
+
export const WithMinQuantity = () => (
|
|
267
210
|
<View style={styles.column}>
|
|
268
211
|
<ProductDisplayCard
|
|
269
|
-
title="Recipe
|
|
270
|
-
subtext="+£
|
|
212
|
+
title="Bulk Recipe Pack"
|
|
213
|
+
subtext="+£2.50/pouch"
|
|
271
214
|
showSubtext
|
|
272
|
-
quantity={
|
|
215
|
+
quantity={5}
|
|
216
|
+
minQuantity={5}
|
|
273
217
|
showQuantityPicker
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
<ProductDisplayCard
|
|
279
|
-
title="Premium Recipe"
|
|
280
|
-
subtext="+£0.50/pouch"
|
|
281
|
-
showSubtext
|
|
282
|
-
quantity={2}
|
|
283
|
-
showQuantityPicker
|
|
284
|
-
showImageQuantityBadge
|
|
285
|
-
borderless
|
|
218
|
+
showBanner
|
|
219
|
+
bannerType="info"
|
|
220
|
+
showBannerIcon={false}
|
|
221
|
+
banner="Minimum quantity: 5 pouches required"
|
|
286
222
|
image={placeholderImageUrl}
|
|
287
223
|
/>
|
|
288
224
|
</View>
|
|
289
225
|
)
|
|
226
|
+
|
|
227
|
+
export const WithImagePress = () => {
|
|
228
|
+
const [pressed, setPressed] = React.useState(false)
|
|
229
|
+
return (
|
|
230
|
+
<View style={styles.column}>
|
|
231
|
+
<ProductDisplayCard
|
|
232
|
+
title="Recipe Name"
|
|
233
|
+
subtext="+£0.00/pouch"
|
|
234
|
+
showSubtext
|
|
235
|
+
quantity={1}
|
|
236
|
+
showQuantityPicker
|
|
237
|
+
onImagePress={() => setPressed(true)}
|
|
238
|
+
showBanner={pressed}
|
|
239
|
+
bannerType="success"
|
|
240
|
+
banner="Image pressed!"
|
|
241
|
+
image={placeholderImageUrl}
|
|
242
|
+
/>
|
|
243
|
+
</View>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
@@ -95,13 +95,7 @@ describe("ProductDisplayCard (Native)", () => {
|
|
|
95
95
|
expect(screen.queryByText("Test Banner")).toBeFalsy()
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
-
it("renders
|
|
99
|
-
renderWithTheme(<ProductDisplayCard title="Test Product" />)
|
|
100
|
-
|
|
101
|
-
expect(screen.getByText("Test Product")).toBeTruthy()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it("renders with mobile device variant when specified", () => {
|
|
98
|
+
it("renders as a mobile card", () => {
|
|
105
99
|
renderWithTheme(<ProductDisplayCard title="Test Product" />)
|
|
106
100
|
|
|
107
101
|
expect(screen.getByText("Test Product")).toBeTruthy()
|