@butternutbox/pawprint-native 0.0.1 → 0.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.
Files changed (187) hide show
  1. package/.turbo/turbo-build.log +15 -15
  2. package/CHANGELOG.md +30 -0
  3. package/COMPONENT_GUIDELINES.md +111 -4
  4. package/dist/index.cjs +12413 -1459
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +1111 -13
  7. package/dist/index.d.ts +1111 -13
  8. package/dist/index.js +12365 -1457
  9. package/dist/index.js.map +1 -1
  10. package/package.json +29 -11
  11. package/src/__mocks__/asset-stub.ts +1 -0
  12. package/src/__mocks__/emotion-native.tsx +18 -0
  13. package/src/__mocks__/react-native-gesture-handler.tsx +41 -0
  14. package/src/__mocks__/react-native-reanimated.tsx +79 -0
  15. package/src/__mocks__/react-native-safe-area-context.tsx +6 -0
  16. package/src/__mocks__/react-native-svg.tsx +27 -0
  17. package/src/__mocks__/react-native-worklets.tsx +11 -0
  18. package/src/__mocks__/react-native.tsx +338 -0
  19. package/src/__mocks__/rn-primitives/avatar.tsx +24 -0
  20. package/src/__mocks__/rn-primitives/checkbox.tsx +19 -0
  21. package/src/__mocks__/rn-primitives/select.tsx +116 -0
  22. package/src/__mocks__/rn-primitives/slider.tsx +40 -0
  23. package/src/__mocks__/rn-primitives/slot.tsx +30 -0
  24. package/src/__mocks__/rn-primitives/switch.tsx +24 -0
  25. package/src/__mocks__/rn-primitives/toggle.tsx +16 -0
  26. package/src/components/atoms/Avatar/Avatar.stories.tsx +57 -49
  27. package/src/components/atoms/Avatar/Avatar.test.tsx +269 -0
  28. package/src/components/atoms/Avatar/Avatar.tsx +68 -22
  29. package/src/components/atoms/Avatar/index.ts +1 -6
  30. package/src/components/atoms/Badge/Badge.stories.tsx +5 -29
  31. package/src/components/atoms/Badge/Badge.test.tsx +90 -0
  32. package/src/components/atoms/Button/Button.test.tsx +123 -0
  33. package/src/components/atoms/Button/Button.tsx +1 -1
  34. package/src/components/atoms/CarouselControls/CarouselControls.stories.tsx +217 -0
  35. package/src/components/atoms/CarouselControls/CarouselControls.tsx +127 -0
  36. package/src/components/atoms/CarouselControls/index.ts +2 -0
  37. package/src/components/atoms/Hint/Hint.test.tsx +36 -0
  38. package/src/components/atoms/Icon/Icon.test.tsx +98 -0
  39. package/src/components/atoms/Icon/Icon.tsx +5 -1
  40. package/src/components/atoms/IconButton/IconButton.test.tsx +101 -0
  41. package/src/components/atoms/Illustration/Illustration.stories.tsx +2 -2
  42. package/src/components/atoms/Illustration/Illustration.test.tsx +55 -0
  43. package/src/components/atoms/Illustration/Illustration.tsx +3 -3
  44. package/src/components/atoms/Input/Input.stories.tsx +129 -86
  45. package/src/components/atoms/Input/Input.test.tsx +306 -0
  46. package/src/components/atoms/Input/Input.tsx +9 -1
  47. package/src/components/atoms/Input/InputField.tsx +226 -74
  48. package/src/components/atoms/Link/Link.test.tsx +89 -0
  49. package/src/components/atoms/Link/Link.tsx +7 -6
  50. package/src/components/atoms/Logo/Logo.registry.ts +30 -5
  51. package/src/components/atoms/Logo/Logo.stories.tsx +108 -0
  52. package/src/components/atoms/Logo/Logo.test.tsx +56 -0
  53. package/src/components/atoms/Logo/assets/BCorp.tsx +113 -0
  54. package/src/components/atoms/Logo/assets/ButternutFavicon.tsx +33 -0
  55. package/src/components/atoms/Logo/assets/ButternutPrimary.tsx +294 -0
  56. package/src/components/atoms/Logo/assets/ButternutTabbedBottom.tsx +294 -0
  57. package/src/components/atoms/Logo/assets/ButternutTabbedTop.tsx +294 -0
  58. package/src/components/atoms/Logo/assets/ButternutWordmark.tsx +274 -0
  59. package/src/components/atoms/Logo/assets/PsiBufetFavicon.tsx +45 -0
  60. package/src/components/atoms/Logo/assets/PsiBufetPrimary.tsx +218 -0
  61. package/src/components/atoms/Logo/assets/PsiBufetTabbedBottom.tsx +218 -0
  62. package/src/components/atoms/Logo/assets/PsiBufetTabbedTop.tsx +218 -0
  63. package/src/components/atoms/Logo/assets/PsiBufetWordmark.tsx +195 -0
  64. package/src/components/atoms/Logo/assets/index.ts +11 -0
  65. package/src/components/atoms/NumberInput/NumberInput.stories.tsx +183 -0
  66. package/src/components/atoms/NumberInput/NumberInput.test.tsx +261 -0
  67. package/src/components/atoms/NumberInput/NumberInput.tsx +129 -0
  68. package/src/components/atoms/NumberInput/NumberInputField.tsx +77 -0
  69. package/src/components/atoms/NumberInput/index.ts +4 -0
  70. package/src/components/atoms/Spinner/Spinner.test.tsx +46 -0
  71. package/src/components/atoms/Spinner/Spinner.tsx +14 -5
  72. package/src/components/atoms/Switch/Switch.test.tsx +92 -0
  73. package/src/components/atoms/Switch/Switch.tsx +28 -17
  74. package/src/components/atoms/Tag/Tag.test.tsx +70 -0
  75. package/src/components/atoms/TextArea/TextArea.stories.tsx +303 -0
  76. package/src/components/atoms/TextArea/TextArea.test.tsx +416 -0
  77. package/src/components/atoms/TextArea/TextArea.tsx +171 -0
  78. package/src/components/atoms/TextArea/TextAreaField.tsx +304 -0
  79. package/src/components/atoms/TextArea/TextAreaLabel.tsx +103 -0
  80. package/src/components/atoms/TextArea/index.ts +6 -0
  81. package/src/components/atoms/Typography/Typography.test.tsx +94 -0
  82. package/src/components/atoms/index.ts +3 -0
  83. package/src/components/molecules/Accordion/Accordion.stories.tsx +177 -0
  84. package/src/components/molecules/Accordion/Accordion.test.tsx +185 -0
  85. package/src/components/molecules/Accordion/Accordion.tsx +284 -0
  86. package/src/components/molecules/Accordion/index.ts +6 -0
  87. package/src/components/molecules/Animated/Animated.stories.tsx +254 -0
  88. package/src/components/molecules/Animated/Animated.tsx +283 -0
  89. package/src/components/molecules/Animated/index.ts +10 -0
  90. package/src/components/molecules/ButtonDock/ButtonDock.stories.tsx +44 -25
  91. package/src/components/molecules/ButtonDock/ButtonDock.test.tsx +83 -0
  92. package/src/components/molecules/ButtonDock/ButtonDock.tsx +16 -13
  93. package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +48 -29
  94. package/src/components/molecules/ButtonGroup/ButtonGroup.test.tsx +73 -0
  95. package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +25 -3
  96. package/src/components/molecules/Checkbox/Checkbox.stories.tsx +72 -0
  97. package/src/components/molecules/Checkbox/Checkbox.test.tsx +117 -0
  98. package/src/components/molecules/Checkbox/Checkbox.tsx +101 -95
  99. package/src/components/molecules/CopyField/CopyField.stories.tsx +313 -0
  100. package/src/components/molecules/CopyField/CopyField.test.tsx +431 -0
  101. package/src/components/molecules/CopyField/CopyField.tsx +156 -0
  102. package/src/components/molecules/CopyField/CopyFieldInput.tsx +127 -0
  103. package/src/components/molecules/CopyField/hooks/index.ts +1 -0
  104. package/src/components/molecules/CopyField/hooks/useCopyField.ts +25 -0
  105. package/src/components/molecules/CopyField/index.ts +4 -0
  106. package/src/components/molecules/DatePicker/DatePicker.stories.tsx +298 -0
  107. package/src/components/molecules/DatePicker/DatePicker.test.tsx +201 -0
  108. package/src/components/molecules/DatePicker/DatePicker.tsx +590 -0
  109. package/src/components/molecules/DatePicker/index.ts +2 -0
  110. package/src/components/molecules/Drawer/Drawer.stories.tsx +285 -0
  111. package/src/components/molecules/Drawer/Drawer.test.tsx +180 -0
  112. package/src/components/molecules/Drawer/Drawer.tsx +187 -0
  113. package/src/components/molecules/Drawer/DrawerBody.tsx +80 -0
  114. package/src/components/molecules/Drawer/DrawerClose.tsx +76 -0
  115. package/src/components/molecules/Drawer/DrawerContent.tsx +339 -0
  116. package/src/components/molecules/Drawer/DrawerContext.ts +19 -0
  117. package/src/components/molecules/Drawer/DrawerDescription.tsx +31 -0
  118. package/src/components/molecules/Drawer/DrawerDragContext.ts +11 -0
  119. package/src/components/molecules/Drawer/DrawerFooter.tsx +49 -0
  120. package/src/components/molecules/Drawer/DrawerFooterContext.ts +6 -0
  121. package/src/components/molecules/Drawer/DrawerGrabber.tsx +62 -0
  122. package/src/components/molecules/Drawer/DrawerHeader.tsx +244 -0
  123. package/src/components/molecules/Drawer/DrawerHeaderContext.ts +13 -0
  124. package/src/components/molecules/Drawer/DrawerOverlay.tsx +53 -0
  125. package/src/components/molecules/Drawer/DrawerTitle.tsx +32 -0
  126. package/src/components/molecules/Drawer/index.ts +12 -0
  127. package/src/components/molecules/FilterTab/FilterTab.stories.tsx +210 -0
  128. package/src/components/molecules/FilterTab/FilterTab.tsx +310 -0
  129. package/src/components/molecules/FilterTab/index.ts +2 -0
  130. package/src/components/molecules/MessageCard/MessageCard.stories.tsx +169 -0
  131. package/src/components/molecules/MessageCard/MessageCard.tsx +362 -0
  132. package/src/components/molecules/MessageCard/index.ts +10 -0
  133. package/src/components/molecules/Notification/Notification.stories.tsx +219 -0
  134. package/src/components/molecules/Notification/Notification.tsx +426 -0
  135. package/src/components/molecules/Notification/index.ts +2 -0
  136. package/src/components/molecules/NumberField/NumberField.stories.tsx +231 -0
  137. package/src/components/molecules/NumberField/NumberField.tsx +186 -0
  138. package/src/components/molecules/NumberField/NumberFieldInput.tsx +287 -0
  139. package/src/components/molecules/NumberField/index.ts +2 -0
  140. package/src/components/molecules/PasswordField/PasswordField.stories.tsx +362 -0
  141. package/src/components/molecules/PasswordField/PasswordField.test.tsx +369 -0
  142. package/src/components/molecules/PasswordField/PasswordField.tsx +194 -0
  143. package/src/components/molecules/PasswordField/PasswordFieldError.tsx +53 -0
  144. package/src/components/molecules/PasswordField/PasswordFieldInput.tsx +73 -0
  145. package/src/components/molecules/PasswordField/PasswordFieldRequirements.tsx +95 -0
  146. package/src/components/molecules/PasswordField/hooks/index.ts +2 -0
  147. package/src/components/molecules/PasswordField/hooks/usePasswordField.ts +113 -0
  148. package/src/components/molecules/PasswordField/index.ts +10 -0
  149. package/src/components/molecules/PictureSelector/PictureSelector.stories.tsx +204 -0
  150. package/src/components/molecules/PictureSelector/PictureSelector.tsx +335 -0
  151. package/src/components/molecules/PictureSelector/index.ts +5 -0
  152. package/src/components/molecules/Progress/Progress.stories.tsx +145 -0
  153. package/src/components/molecules/Progress/Progress.tsx +184 -0
  154. package/src/components/molecules/Progress/index.ts +2 -0
  155. package/src/components/molecules/Radio/Radio.test.tsx +104 -0
  156. package/src/components/molecules/Radio/Radio.tsx +1 -2
  157. package/src/components/molecules/SearchField/SearchField.stories.tsx +242 -0
  158. package/src/components/molecules/SearchField/SearchField.test.tsx +318 -0
  159. package/src/components/molecules/SearchField/SearchField.tsx +143 -0
  160. package/src/components/molecules/SearchField/SearchFieldInput.tsx +63 -0
  161. package/src/components/molecules/SearchField/hooks/index.ts +1 -0
  162. package/src/components/molecules/SearchField/hooks/useSearchField.ts +56 -0
  163. package/src/components/molecules/SearchField/index.ts +4 -0
  164. package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +31 -8
  165. package/src/components/molecules/SegmentedControl/SegmentedControl.test.tsx +141 -0
  166. package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +237 -23
  167. package/src/components/molecules/SelectField/SelectField.stories.tsx +320 -0
  168. package/src/components/molecules/SelectField/SelectField.test.tsx +254 -0
  169. package/src/components/molecules/SelectField/SelectField.tsx +236 -0
  170. package/src/components/molecules/SelectField/SelectFieldContent.tsx +85 -0
  171. package/src/components/molecules/SelectField/SelectFieldItem.tsx +133 -0
  172. package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +170 -0
  173. package/src/components/molecules/SelectField/SelectFieldValue.tsx +31 -0
  174. package/src/components/molecules/SelectField/hooks/index.ts +2 -0
  175. package/src/components/molecules/SelectField/hooks/useSelectField.ts +84 -0
  176. package/src/components/molecules/SelectField/index.ts +10 -0
  177. package/src/components/molecules/Slider/Slider.test.tsx +102 -0
  178. package/src/components/molecules/Slider/Slider.tsx +293 -180
  179. package/src/components/molecules/Tooltip/Tooltip.stories.tsx +168 -0
  180. package/src/components/molecules/Tooltip/Tooltip.tsx +326 -0
  181. package/src/components/molecules/Tooltip/index.ts +2 -0
  182. package/src/components/molecules/index.ts +15 -0
  183. package/src/test-utils.tsx +20 -0
  184. package/tsconfig.json +1 -1
  185. package/tsup.config.ts +16 -2
  186. package/vitest.config.ts +114 -0
  187. package/vitest.setup.ts +16 -0
@@ -0,0 +1,95 @@
1
+ import React from "react"
2
+ import { View, ViewProps } from "react-native"
3
+ import styled from "@emotion/native"
4
+ import { useTheme } from "@emotion/react"
5
+ import { Typography } from "../../atoms/Typography"
6
+ import { Icon } from "../../atoms/Icon"
7
+ import { Cancel, CheckCircle } from "@butternutbox/pawprint-icons/core"
8
+
9
+ export type PasswordRequirement = {
10
+ text: string
11
+ satisfied: boolean
12
+ }
13
+
14
+ type PasswordFieldRequirementsOwnProps = {
15
+ requirements: string[] | PasswordRequirement[]
16
+ children?: never
17
+ }
18
+
19
+ export type PasswordFieldRequirementsProps = PasswordFieldRequirementsOwnProps &
20
+ Omit<ViewProps, keyof PasswordFieldRequirementsOwnProps>
21
+
22
+ const parseTokenValue = (value: string): number => parseFloat(value)
23
+
24
+ const StyledRequirementsList = styled(View)<{ listGap: number }>(
25
+ ({ listGap }) => ({
26
+ gap: listGap
27
+ })
28
+ )
29
+
30
+ const StyledRequirementItem = styled(View)<{ itemGap: number }>(
31
+ ({ itemGap }) => ({
32
+ flexDirection: "row",
33
+ alignItems: "center",
34
+ gap: itemGap
35
+ })
36
+ )
37
+
38
+ /**
39
+ * Requirements list component for PasswordField.
40
+ * Displays a list of password requirements with validation icons.
41
+ *
42
+ * @param requirements - Array of requirement strings or objects with text and satisfied status
43
+ */
44
+ export const PasswordFieldRequirements = React.forwardRef<
45
+ View,
46
+ PasswordFieldRequirementsProps
47
+ >(({ requirements, ...rest }, ref) => {
48
+ const theme = useTheme()
49
+ const { listItem, list } = theme.tokens.components.validationList
50
+ const { typography } = theme.tokens.semantics
51
+
52
+ if (!requirements || requirements.length === 0) {
53
+ return null
54
+ }
55
+
56
+ // Normalize requirements to objects
57
+ const normalizedRequirements: PasswordRequirement[] = requirements.map(
58
+ (req) => (typeof req === "string" ? { text: req, satisfied: false } : req)
59
+ )
60
+
61
+ return (
62
+ <StyledRequirementsList
63
+ ref={ref}
64
+ listGap={parseTokenValue(list.spacing.gap)}
65
+ {...rest}
66
+ >
67
+ {normalizedRequirements.map((requirement, index) => {
68
+ const iconColour = requirement.satisfied
69
+ ? listItem.colour.icon.success
70
+ : listItem.colour.icon.error
71
+
72
+ return (
73
+ <StyledRequirementItem
74
+ key={index}
75
+ itemGap={parseTokenValue(listItem.spacing.gap)}
76
+ >
77
+ <Icon
78
+ icon={requirement.satisfied ? CheckCircle : Cancel}
79
+ size="xs"
80
+ customColour={iconColour}
81
+ />
82
+ <Typography
83
+ token={typography.body.medium.md}
84
+ color={listItem.colour.text.default}
85
+ >
86
+ {requirement.text}
87
+ </Typography>
88
+ </StyledRequirementItem>
89
+ )
90
+ })}
91
+ </StyledRequirementsList>
92
+ )
93
+ })
94
+
95
+ PasswordFieldRequirements.displayName = "PasswordField.Requirements"
@@ -0,0 +1,2 @@
1
+ export { usePasswordField } from "./usePasswordField"
2
+ export type { PasswordValidationRule } from "./usePasswordField"
@@ -0,0 +1,113 @@
1
+ import { useState, useMemo } from "react"
2
+ import type { PasswordFieldProps } from "../PasswordField"
3
+ import type { PasswordRequirement } from "../PasswordFieldRequirements"
4
+ import type { InputState } from "../../../atoms/Input/InputField"
5
+
6
+ export type PasswordValidationRule = {
7
+ test: (value: string) => boolean
8
+ message: string
9
+ }
10
+
11
+ type UsePasswordFieldOptions = {
12
+ initialValue?: string
13
+ onValueChange?: (value: string) => void
14
+ validationRules?: PasswordValidationRule[]
15
+ autoShowRequirements?: boolean
16
+ }
17
+
18
+ type UsePasswordFieldReturn = Pick<
19
+ PasswordFieldProps,
20
+ "value" | "onValueChange" | "state" | "requirements" | "showRequirements"
21
+ >
22
+
23
+ /**
24
+ * Hook for managing PasswordField state with built-in validation.
25
+ * Handles value, validation state, and requirements automatically.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * // Simple usage without validation
30
+ * const passwordProps = usePasswordField()
31
+ * <PasswordField {...passwordProps} label="Password" />
32
+ *
33
+ * // With validation rules
34
+ * const passwordProps = usePasswordField({
35
+ * validationRules: [
36
+ * { test: (v) => v.length >= 8, message: "At least 8 characters" },
37
+ * { test: (v) => /[A-Z]/.test(v), message: "One uppercase letter" },
38
+ * { test: (v) => /[a-z]/.test(v), message: "One lowercase letter" },
39
+ * { test: (v) => /\d/.test(v), message: "One number" }
40
+ * ]
41
+ * })
42
+ * <PasswordField {...passwordProps} label="Create Password" />
43
+ * ```
44
+ *
45
+ * @param {string} [initialValue=""] - Initial password value
46
+ * @param {(value: string) => void} [onValueChange] - Optional callback called after value changes
47
+ * @param {PasswordValidationRule[]} [validationRules] - Array of validation rules
48
+ * @param {boolean} [autoShowRequirements] - Whether to auto-show requirements (default: true when rules provided)
49
+ */
50
+ export function usePasswordField({
51
+ initialValue = "",
52
+ onValueChange: onValueChangeCallback,
53
+ validationRules = [],
54
+ autoShowRequirements
55
+ }: UsePasswordFieldOptions = {}): UsePasswordFieldReturn {
56
+ const [value, setValue] = useState(initialValue)
57
+ const hasValidationRules = validationRules.length > 0
58
+ const shouldAutoShow =
59
+ autoShowRequirements !== undefined
60
+ ? autoShowRequirements
61
+ : hasValidationRules
62
+
63
+ const handleValueChange: PasswordFieldProps["onValueChange"] = (newValue) => {
64
+ setValue(newValue)
65
+ onValueChangeCallback?.(newValue)
66
+ }
67
+
68
+ // Calculate validation state and requirements
69
+ const { state, requirements, showRequirements } = useMemo(() => {
70
+ if (!hasValidationRules) {
71
+ return {
72
+ state: "default" as InputState,
73
+ requirements: undefined,
74
+ showRequirements: false
75
+ }
76
+ }
77
+
78
+ if (!value) {
79
+ return {
80
+ state: "default" as InputState,
81
+ requirements: validationRules.map((rule) => ({
82
+ text: rule.message,
83
+ satisfied: false
84
+ })),
85
+ showRequirements: false
86
+ }
87
+ }
88
+
89
+ const requirements: PasswordRequirement[] = validationRules.map((rule) => ({
90
+ text: rule.message,
91
+ satisfied: rule.test(value)
92
+ }))
93
+
94
+ const allSatisfied = requirements.every((req) => req.satisfied)
95
+ const state: InputState = allSatisfied ? "success" : "error"
96
+
97
+ return {
98
+ state,
99
+ requirements,
100
+ showRequirements: shouldAutoShow && value.length > 0
101
+ }
102
+ }, [value, validationRules, hasValidationRules, shouldAutoShow])
103
+
104
+ return {
105
+ value,
106
+ onValueChange: handleValueChange,
107
+ ...(hasValidationRules && {
108
+ state,
109
+ requirements,
110
+ showRequirements
111
+ })
112
+ }
113
+ }
@@ -0,0 +1,10 @@
1
+ export { PasswordField } from "./PasswordField"
2
+ export type { PasswordFieldProps } from "./PasswordField"
3
+ export { PasswordFieldInput } from "./PasswordFieldInput"
4
+ export { PasswordFieldRequirements } from "./PasswordFieldRequirements"
5
+ export type {
6
+ PasswordRequirement,
7
+ PasswordFieldRequirementsProps
8
+ } from "./PasswordFieldRequirements"
9
+ export { usePasswordField } from "./hooks"
10
+ export type { PasswordValidationRule } from "./hooks"
@@ -0,0 +1,204 @@
1
+ import React from "react"
2
+ import { View, StyleSheet } from "react-native"
3
+ import { PictureSelector, type PictureSelectorProps } from "./PictureSelector"
4
+ import {
5
+ LargeStickPrimaryFilled,
6
+ EatingPrimaryFilled,
7
+ ExcitedPrimaryFilled,
8
+ DogWithBallPrimaryFilled
9
+ } from "@butternutbox/pawprint-illustrations/poses"
10
+
11
+ export default {
12
+ title: "Molecules/PictureSelector"
13
+ }
14
+
15
+ const BODY_CONDITION = [
16
+ {
17
+ value: "under",
18
+ illustration: LargeStickPrimaryFilled,
19
+ ariaLabel: "Underweight",
20
+ insightTitle: "Underweight",
21
+ insightDescription:
22
+ "Ribs and hip bones are visible with little fat covering."
23
+ },
24
+ {
25
+ value: "just",
26
+ illustration: EatingPrimaryFilled,
27
+ ariaLabel: "Just right",
28
+ insightTitle: "Just right",
29
+ insightDescription:
30
+ "A visible waistline, some fat cover, and you can feel their ribs easily."
31
+ },
32
+ {
33
+ value: "over",
34
+ illustration: ExcitedPrimaryFilled,
35
+ ariaLabel: "Overweight",
36
+ insightTitle: "Overweight",
37
+ insightDescription:
38
+ "No visible waistline and ribs are hard to feel under fat."
39
+ }
40
+ ]
41
+
42
+ export const Playground = (args: PictureSelectorProps) => (
43
+ <View style={styles.column}>
44
+ <PictureSelector {...args} />
45
+ </View>
46
+ )
47
+ Playground.args = {
48
+ label: "Body condition",
49
+ items: BODY_CONDITION,
50
+ defaultValue: "just",
51
+ itemAspectRatio: 1
52
+ }
53
+ Playground.argTypes = {
54
+ label: { control: { type: "text" }, description: "Optional headline." },
55
+ defaultValue: {
56
+ control: { type: "select" },
57
+ options: ["under", "just", "over"],
58
+ description: "Uncontrolled initial selected value."
59
+ },
60
+ itemWidth: {
61
+ control: { type: "number" },
62
+ description: "Fixed card width (px). Defaults to 100% (fluid)."
63
+ },
64
+ itemAspectRatio: {
65
+ control: { type: "number" },
66
+ description: "Card aspect ratio (width / height). Defaults to 1."
67
+ },
68
+ itemMinWidth: {
69
+ control: { type: "number" },
70
+ description: "Floor on each card's width (px)."
71
+ },
72
+ itemMinHeight: {
73
+ control: { type: "number" },
74
+ description: "Floor on each card's height (px)."
75
+ }
76
+ }
77
+
78
+ export const ThreeButtons = () => (
79
+ <View style={styles.column}>
80
+ <PictureSelector
81
+ label="Body condition"
82
+ items={BODY_CONDITION}
83
+ defaultValue="just"
84
+ />
85
+ </View>
86
+ )
87
+
88
+ export const FourButtons = () => (
89
+ <View style={styles.column}>
90
+ <PictureSelector
91
+ label="Activity level"
92
+ defaultValue="moderate"
93
+ items={[
94
+ {
95
+ value: "couch",
96
+ illustration: LargeStickPrimaryFilled,
97
+ ariaLabel: "Couch potato",
98
+ insightTitle: "Couch potato",
99
+ insightDescription: "Short walks and lots of naps."
100
+ },
101
+ {
102
+ value: "moderate",
103
+ illustration: EatingPrimaryFilled,
104
+ ariaLabel: "Moderate",
105
+ insightTitle: "Moderate",
106
+ insightDescription: "A daily walk and the occasional game of fetch."
107
+ },
108
+ {
109
+ value: "active",
110
+ illustration: DogWithBallPrimaryFilled,
111
+ ariaLabel: "Active",
112
+ insightTitle: "Active",
113
+ insightDescription: "Multiple walks a day and regular running."
114
+ },
115
+ {
116
+ value: "athlete",
117
+ illustration: ExcitedPrimaryFilled,
118
+ ariaLabel: "Athlete",
119
+ insightTitle: "Athlete",
120
+ insightDescription: "Long, fast runs, agility, or working most days."
121
+ }
122
+ ]}
123
+ />
124
+ </View>
125
+ )
126
+
127
+ export const WithoutLabel = () => (
128
+ <View style={styles.column}>
129
+ <PictureSelector items={BODY_CONDITION} defaultValue="just" />
130
+ </View>
131
+ )
132
+
133
+ export const WithoutInsight = () => (
134
+ <View style={styles.column}>
135
+ <PictureSelector
136
+ label="Body condition"
137
+ defaultValue="just"
138
+ items={BODY_CONDITION.map(
139
+ ({ insightTitle, insightDescription, ...rest }) => {
140
+ void insightTitle
141
+ void insightDescription
142
+ return rest
143
+ }
144
+ )}
145
+ />
146
+ </View>
147
+ )
148
+
149
+ export const WithDisabledItem = () => (
150
+ <View style={styles.column}>
151
+ <PictureSelector
152
+ label="Body condition"
153
+ defaultValue="just"
154
+ items={[
155
+ BODY_CONDITION[0],
156
+ BODY_CONDITION[1],
157
+ { ...BODY_CONDITION[2], disabled: true }
158
+ ]}
159
+ />
160
+ </View>
161
+ )
162
+
163
+ export const Controlled = () => {
164
+ const [selected, setSelected] = React.useState("just")
165
+ return (
166
+ <View style={styles.column}>
167
+ <PictureSelector
168
+ label={`Body condition (selected: ${selected})`}
169
+ items={BODY_CONDITION}
170
+ value={selected}
171
+ onValueChange={setSelected}
172
+ />
173
+ </View>
174
+ )
175
+ }
176
+
177
+ export const FixedItemWidth = () => (
178
+ <View style={styles.column}>
179
+ <PictureSelector
180
+ label="Fixed 96px cards"
181
+ items={BODY_CONDITION}
182
+ defaultValue="just"
183
+ itemWidth={96}
184
+ />
185
+ </View>
186
+ )
187
+
188
+ export const PortraitAspectRatio = () => (
189
+ <View style={styles.column}>
190
+ <PictureSelector
191
+ label="Portrait cards (aspect ratio 0.75)"
192
+ items={BODY_CONDITION}
193
+ defaultValue="just"
194
+ itemAspectRatio={0.75}
195
+ />
196
+ </View>
197
+ )
198
+
199
+ const styles = StyleSheet.create({
200
+ column: {
201
+ flexDirection: "column",
202
+ width: 358
203
+ }
204
+ })