@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.
Files changed (182) hide show
  1. package/.turbo/turbo-build.log +15 -15
  2. package/CHANGELOG.md +16 -0
  3. package/COMPONENT_GUIDELINES.md +111 -4
  4. package/dist/index.cjs +12370 -1455
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +1110 -11
  7. package/dist/index.d.ts +1110 -11
  8. package/dist/index.js +12324 -1455
  9. package/dist/index.js.map +1 -1
  10. package/package.json +28 -9
  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.test.tsx +55 -0
  42. package/src/components/atoms/Input/Input.stories.tsx +129 -86
  43. package/src/components/atoms/Input/Input.test.tsx +306 -0
  44. package/src/components/atoms/Input/Input.tsx +9 -1
  45. package/src/components/atoms/Input/InputField.tsx +226 -74
  46. package/src/components/atoms/Link/Link.test.tsx +89 -0
  47. package/src/components/atoms/Logo/Logo.registry.ts +30 -5
  48. package/src/components/atoms/Logo/Logo.stories.tsx +108 -0
  49. package/src/components/atoms/Logo/Logo.test.tsx +56 -0
  50. package/src/components/atoms/Logo/assets/BCorp.tsx +113 -0
  51. package/src/components/atoms/Logo/assets/ButternutFavicon.tsx +33 -0
  52. package/src/components/atoms/Logo/assets/ButternutPrimary.tsx +294 -0
  53. package/src/components/atoms/Logo/assets/ButternutTabbedBottom.tsx +294 -0
  54. package/src/components/atoms/Logo/assets/ButternutTabbedTop.tsx +294 -0
  55. package/src/components/atoms/Logo/assets/ButternutWordmark.tsx +274 -0
  56. package/src/components/atoms/Logo/assets/PsiBufetFavicon.tsx +45 -0
  57. package/src/components/atoms/Logo/assets/PsiBufetPrimary.tsx +218 -0
  58. package/src/components/atoms/Logo/assets/PsiBufetTabbedBottom.tsx +218 -0
  59. package/src/components/atoms/Logo/assets/PsiBufetTabbedTop.tsx +218 -0
  60. package/src/components/atoms/Logo/assets/PsiBufetWordmark.tsx +195 -0
  61. package/src/components/atoms/Logo/assets/index.ts +11 -0
  62. package/src/components/atoms/NumberInput/NumberInput.stories.tsx +183 -0
  63. package/src/components/atoms/NumberInput/NumberInput.test.tsx +261 -0
  64. package/src/components/atoms/NumberInput/NumberInput.tsx +129 -0
  65. package/src/components/atoms/NumberInput/NumberInputField.tsx +77 -0
  66. package/src/components/atoms/NumberInput/index.ts +4 -0
  67. package/src/components/atoms/Spinner/Spinner.test.tsx +46 -0
  68. package/src/components/atoms/Spinner/Spinner.tsx +14 -5
  69. package/src/components/atoms/Switch/Switch.test.tsx +92 -0
  70. package/src/components/atoms/Switch/Switch.tsx +16 -13
  71. package/src/components/atoms/Tag/Tag.test.tsx +70 -0
  72. package/src/components/atoms/TextArea/TextArea.stories.tsx +303 -0
  73. package/src/components/atoms/TextArea/TextArea.test.tsx +416 -0
  74. package/src/components/atoms/TextArea/TextArea.tsx +171 -0
  75. package/src/components/atoms/TextArea/TextAreaField.tsx +304 -0
  76. package/src/components/atoms/TextArea/TextAreaLabel.tsx +103 -0
  77. package/src/components/atoms/TextArea/index.ts +6 -0
  78. package/src/components/atoms/Typography/Typography.test.tsx +94 -0
  79. package/src/components/atoms/index.ts +3 -0
  80. package/src/components/molecules/Accordion/Accordion.stories.tsx +177 -0
  81. package/src/components/molecules/Accordion/Accordion.test.tsx +185 -0
  82. package/src/components/molecules/Accordion/Accordion.tsx +284 -0
  83. package/src/components/molecules/Accordion/index.ts +6 -0
  84. package/src/components/molecules/Animated/Animated.stories.tsx +254 -0
  85. package/src/components/molecules/Animated/Animated.tsx +283 -0
  86. package/src/components/molecules/Animated/index.ts +10 -0
  87. package/src/components/molecules/ButtonDock/ButtonDock.test.tsx +83 -0
  88. package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +8 -14
  89. package/src/components/molecules/ButtonGroup/ButtonGroup.test.tsx +73 -0
  90. package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +25 -3
  91. package/src/components/molecules/Checkbox/Checkbox.stories.tsx +72 -0
  92. package/src/components/molecules/Checkbox/Checkbox.test.tsx +117 -0
  93. package/src/components/molecules/Checkbox/Checkbox.tsx +101 -95
  94. package/src/components/molecules/CopyField/CopyField.stories.tsx +313 -0
  95. package/src/components/molecules/CopyField/CopyField.test.tsx +431 -0
  96. package/src/components/molecules/CopyField/CopyField.tsx +156 -0
  97. package/src/components/molecules/CopyField/CopyFieldInput.tsx +127 -0
  98. package/src/components/molecules/CopyField/hooks/index.ts +1 -0
  99. package/src/components/molecules/CopyField/hooks/useCopyField.ts +25 -0
  100. package/src/components/molecules/CopyField/index.ts +4 -0
  101. package/src/components/molecules/DatePicker/DatePicker.stories.tsx +298 -0
  102. package/src/components/molecules/DatePicker/DatePicker.test.tsx +201 -0
  103. package/src/components/molecules/DatePicker/DatePicker.tsx +590 -0
  104. package/src/components/molecules/DatePicker/index.ts +2 -0
  105. package/src/components/molecules/Drawer/Drawer.stories.tsx +285 -0
  106. package/src/components/molecules/Drawer/Drawer.test.tsx +180 -0
  107. package/src/components/molecules/Drawer/Drawer.tsx +187 -0
  108. package/src/components/molecules/Drawer/DrawerBody.tsx +80 -0
  109. package/src/components/molecules/Drawer/DrawerClose.tsx +76 -0
  110. package/src/components/molecules/Drawer/DrawerContent.tsx +339 -0
  111. package/src/components/molecules/Drawer/DrawerContext.ts +19 -0
  112. package/src/components/molecules/Drawer/DrawerDescription.tsx +31 -0
  113. package/src/components/molecules/Drawer/DrawerDragContext.ts +11 -0
  114. package/src/components/molecules/Drawer/DrawerFooter.tsx +49 -0
  115. package/src/components/molecules/Drawer/DrawerFooterContext.ts +6 -0
  116. package/src/components/molecules/Drawer/DrawerGrabber.tsx +62 -0
  117. package/src/components/molecules/Drawer/DrawerHeader.tsx +244 -0
  118. package/src/components/molecules/Drawer/DrawerHeaderContext.ts +13 -0
  119. package/src/components/molecules/Drawer/DrawerOverlay.tsx +53 -0
  120. package/src/components/molecules/Drawer/DrawerTitle.tsx +32 -0
  121. package/src/components/molecules/Drawer/index.ts +12 -0
  122. package/src/components/molecules/FilterTab/FilterTab.stories.tsx +210 -0
  123. package/src/components/molecules/FilterTab/FilterTab.tsx +310 -0
  124. package/src/components/molecules/FilterTab/index.ts +2 -0
  125. package/src/components/molecules/MessageCard/MessageCard.stories.tsx +169 -0
  126. package/src/components/molecules/MessageCard/MessageCard.tsx +362 -0
  127. package/src/components/molecules/MessageCard/index.ts +10 -0
  128. package/src/components/molecules/Notification/Notification.stories.tsx +219 -0
  129. package/src/components/molecules/Notification/Notification.tsx +426 -0
  130. package/src/components/molecules/Notification/index.ts +2 -0
  131. package/src/components/molecules/NumberField/NumberField.stories.tsx +231 -0
  132. package/src/components/molecules/NumberField/NumberField.tsx +186 -0
  133. package/src/components/molecules/NumberField/NumberFieldInput.tsx +287 -0
  134. package/src/components/molecules/NumberField/index.ts +2 -0
  135. package/src/components/molecules/PasswordField/PasswordField.stories.tsx +362 -0
  136. package/src/components/molecules/PasswordField/PasswordField.test.tsx +369 -0
  137. package/src/components/molecules/PasswordField/PasswordField.tsx +194 -0
  138. package/src/components/molecules/PasswordField/PasswordFieldError.tsx +52 -0
  139. package/src/components/molecules/PasswordField/PasswordFieldInput.tsx +73 -0
  140. package/src/components/molecules/PasswordField/PasswordFieldRequirements.tsx +92 -0
  141. package/src/components/molecules/PasswordField/hooks/index.ts +2 -0
  142. package/src/components/molecules/PasswordField/hooks/usePasswordField.ts +113 -0
  143. package/src/components/molecules/PasswordField/index.ts +10 -0
  144. package/src/components/molecules/PictureSelector/PictureSelector.stories.tsx +243 -0
  145. package/src/components/molecules/PictureSelector/PictureSelector.tsx +313 -0
  146. package/src/components/molecules/PictureSelector/index.ts +5 -0
  147. package/src/components/molecules/Progress/Progress.stories.tsx +145 -0
  148. package/src/components/molecules/Progress/Progress.tsx +184 -0
  149. package/src/components/molecules/Progress/index.ts +2 -0
  150. package/src/components/molecules/Radio/Radio.test.tsx +104 -0
  151. package/src/components/molecules/Radio/Radio.tsx +1 -2
  152. package/src/components/molecules/SearchField/SearchField.stories.tsx +242 -0
  153. package/src/components/molecules/SearchField/SearchField.test.tsx +318 -0
  154. package/src/components/molecules/SearchField/SearchField.tsx +143 -0
  155. package/src/components/molecules/SearchField/SearchFieldInput.tsx +63 -0
  156. package/src/components/molecules/SearchField/hooks/index.ts +1 -0
  157. package/src/components/molecules/SearchField/hooks/useSearchField.ts +56 -0
  158. package/src/components/molecules/SearchField/index.ts +4 -0
  159. package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +31 -8
  160. package/src/components/molecules/SegmentedControl/SegmentedControl.test.tsx +141 -0
  161. package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +237 -23
  162. package/src/components/molecules/SelectField/SelectField.stories.tsx +320 -0
  163. package/src/components/molecules/SelectField/SelectField.test.tsx +254 -0
  164. package/src/components/molecules/SelectField/SelectField.tsx +236 -0
  165. package/src/components/molecules/SelectField/SelectFieldContent.tsx +85 -0
  166. package/src/components/molecules/SelectField/SelectFieldItem.tsx +133 -0
  167. package/src/components/molecules/SelectField/SelectFieldTrigger.tsx +170 -0
  168. package/src/components/molecules/SelectField/SelectFieldValue.tsx +31 -0
  169. package/src/components/molecules/SelectField/hooks/index.ts +2 -0
  170. package/src/components/molecules/SelectField/hooks/useSelectField.ts +84 -0
  171. package/src/components/molecules/SelectField/index.ts +10 -0
  172. package/src/components/molecules/Slider/Slider.test.tsx +102 -0
  173. package/src/components/molecules/Slider/Slider.tsx +293 -180
  174. package/src/components/molecules/Tooltip/Tooltip.stories.tsx +168 -0
  175. package/src/components/molecules/Tooltip/Tooltip.tsx +326 -0
  176. package/src/components/molecules/Tooltip/index.ts +2 -0
  177. package/src/components/molecules/index.ts +15 -0
  178. package/src/test-utils.tsx +20 -0
  179. package/tsconfig.json +1 -1
  180. package/tsup.config.ts +16 -2
  181. package/vitest.config.ts +114 -0
  182. package/vitest.setup.ts +16 -0
@@ -0,0 +1,92 @@
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 { colour, description, spacing } = theme.tokens.components.inputs
50
+
51
+ if (!requirements || requirements.length === 0) {
52
+ return null
53
+ }
54
+
55
+ // Normalize requirements to objects
56
+ const normalizedRequirements: PasswordRequirement[] = requirements.map(
57
+ (req) => (typeof req === "string" ? { text: req, satisfied: false } : req)
58
+ )
59
+
60
+ return (
61
+ <StyledRequirementsList
62
+ ref={ref}
63
+ listGap={parseTokenValue(spacing.description.leftPadding)}
64
+ {...rest}
65
+ >
66
+ {normalizedRequirements.map((requirement, index) => {
67
+ const iconColour = requirement.satisfied ? "success" : "error"
68
+
69
+ return (
70
+ <StyledRequirementItem
71
+ key={index}
72
+ itemGap={parseTokenValue(spacing.description.leftPadding)}
73
+ >
74
+ <Icon
75
+ icon={requirement.satisfied ? CheckCircle : Cancel}
76
+ size="sm"
77
+ colour={iconColour}
78
+ />
79
+ <Typography
80
+ token={description.text.default}
81
+ color={colour.label.default}
82
+ >
83
+ {requirement.text}
84
+ </Typography>
85
+ </StyledRequirementItem>
86
+ )
87
+ })}
88
+ </StyledRequirementsList>
89
+ )
90
+ })
91
+
92
+ 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,243 @@
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
+ import {
11
+ CheckCircle,
12
+ StarRate,
13
+ Female,
14
+ Male
15
+ } from "@butternutbox/pawprint-icons/core"
16
+
17
+ export default {
18
+ title: "Molecules/PictureSelector"
19
+ }
20
+
21
+ const BODY_CONDITION = [
22
+ {
23
+ value: "under",
24
+ illustration: LargeStickPrimaryFilled,
25
+ ariaLabel: "Underweight",
26
+ insightTitle: "Underweight",
27
+ insightDescription:
28
+ "Ribs and hip bones are visible with little fat covering."
29
+ },
30
+ {
31
+ value: "just",
32
+ illustration: EatingPrimaryFilled,
33
+ ariaLabel: "Just right",
34
+ insightTitle: "Just right",
35
+ insightDescription:
36
+ "A visible waistline, some fat cover, and you can feel their ribs easily."
37
+ },
38
+ {
39
+ value: "over",
40
+ illustration: ExcitedPrimaryFilled,
41
+ ariaLabel: "Overweight",
42
+ insightTitle: "Overweight",
43
+ insightDescription:
44
+ "No visible waistline and ribs are hard to feel under fat."
45
+ }
46
+ ]
47
+
48
+ export const Playground = (args: PictureSelectorProps) => (
49
+ <View style={styles.column}>
50
+ <PictureSelector {...args} />
51
+ </View>
52
+ )
53
+ Playground.args = {
54
+ label: "Body condition",
55
+ items: BODY_CONDITION,
56
+ defaultValue: "just",
57
+ itemAspectRatio: 1
58
+ }
59
+ Playground.argTypes = {
60
+ label: { control: { type: "text" }, description: "Optional headline." },
61
+ defaultValue: {
62
+ control: { type: "select" },
63
+ options: ["under", "just", "over"],
64
+ description: "Uncontrolled initial selected value."
65
+ },
66
+ itemWidth: {
67
+ control: { type: "number" },
68
+ description: "Fixed card width (px). Defaults to 100% (fluid)."
69
+ },
70
+ itemAspectRatio: {
71
+ control: { type: "number" },
72
+ description: "Card aspect ratio (width / height). Defaults to 1."
73
+ },
74
+ itemMinWidth: {
75
+ control: { type: "number" },
76
+ description: "Floor on each card's width (px)."
77
+ },
78
+ itemMinHeight: {
79
+ control: { type: "number" },
80
+ description: "Floor on each card's height (px)."
81
+ }
82
+ }
83
+
84
+ export const ThreeButtons = () => (
85
+ <View style={styles.column}>
86
+ <PictureSelector
87
+ label="Body condition"
88
+ items={BODY_CONDITION}
89
+ defaultValue="just"
90
+ />
91
+ </View>
92
+ )
93
+
94
+ export const FourButtons = () => (
95
+ <View style={styles.column}>
96
+ <PictureSelector
97
+ label="Activity level"
98
+ defaultValue="moderate"
99
+ items={[
100
+ {
101
+ value: "couch",
102
+ illustration: LargeStickPrimaryFilled,
103
+ ariaLabel: "Couch potato",
104
+ insightTitle: "Couch potato",
105
+ insightDescription: "Short walks and lots of naps."
106
+ },
107
+ {
108
+ value: "moderate",
109
+ illustration: EatingPrimaryFilled,
110
+ ariaLabel: "Moderate",
111
+ insightTitle: "Moderate",
112
+ insightDescription: "A daily walk and the occasional game of fetch."
113
+ },
114
+ {
115
+ value: "active",
116
+ illustration: DogWithBallPrimaryFilled,
117
+ ariaLabel: "Active",
118
+ insightTitle: "Active",
119
+ insightDescription: "Multiple walks a day and regular running."
120
+ },
121
+ {
122
+ value: "athlete",
123
+ illustration: ExcitedPrimaryFilled,
124
+ ariaLabel: "Athlete",
125
+ insightTitle: "Athlete",
126
+ insightDescription: "Long, fast runs, agility, or working most days."
127
+ }
128
+ ]}
129
+ />
130
+ </View>
131
+ )
132
+
133
+ export const WithoutLabel = () => (
134
+ <View style={styles.column}>
135
+ <PictureSelector items={BODY_CONDITION} defaultValue="just" />
136
+ </View>
137
+ )
138
+
139
+ export const WithoutInsight = () => (
140
+ <View style={styles.column}>
141
+ <PictureSelector
142
+ label="Body condition"
143
+ defaultValue="just"
144
+ items={BODY_CONDITION.map(
145
+ ({ insightTitle, insightDescription, ...rest }) => {
146
+ void insightTitle
147
+ void insightDescription
148
+ return rest
149
+ }
150
+ )}
151
+ />
152
+ </View>
153
+ )
154
+
155
+ export const WithIcons = () => (
156
+ <View style={styles.column}>
157
+ <PictureSelector
158
+ label="Sex"
159
+ defaultValue="female"
160
+ items={[
161
+ {
162
+ value: "female",
163
+ icon: Female,
164
+ ariaLabel: "Female"
165
+ },
166
+ {
167
+ value: "male",
168
+ icon: Male,
169
+ ariaLabel: "Male"
170
+ },
171
+ {
172
+ value: "either",
173
+ icon: StarRate,
174
+ ariaLabel: "Either",
175
+ insightTitle: "Either",
176
+ insightDescription: "We'll help you decide later."
177
+ },
178
+ {
179
+ value: "verified",
180
+ icon: CheckCircle,
181
+ ariaLabel: "Verified"
182
+ }
183
+ ]}
184
+ />
185
+ </View>
186
+ )
187
+
188
+ export const WithDisabledItem = () => (
189
+ <View style={styles.column}>
190
+ <PictureSelector
191
+ label="Body condition"
192
+ defaultValue="just"
193
+ items={[
194
+ BODY_CONDITION[0],
195
+ BODY_CONDITION[1],
196
+ { ...BODY_CONDITION[2], disabled: true }
197
+ ]}
198
+ />
199
+ </View>
200
+ )
201
+
202
+ export const Controlled = () => {
203
+ const [selected, setSelected] = React.useState("just")
204
+ return (
205
+ <View style={styles.column}>
206
+ <PictureSelector
207
+ label={`Body condition (selected: ${selected})`}
208
+ items={BODY_CONDITION}
209
+ value={selected}
210
+ onValueChange={setSelected}
211
+ />
212
+ </View>
213
+ )
214
+ }
215
+
216
+ export const FixedItemWidth = () => (
217
+ <View style={styles.column}>
218
+ <PictureSelector
219
+ label="Fixed 96px cards"
220
+ items={BODY_CONDITION}
221
+ defaultValue="just"
222
+ itemWidth={96}
223
+ />
224
+ </View>
225
+ )
226
+
227
+ export const PortraitAspectRatio = () => (
228
+ <View style={styles.column}>
229
+ <PictureSelector
230
+ label="Portrait cards (aspect ratio 0.75)"
231
+ items={BODY_CONDITION}
232
+ defaultValue="just"
233
+ itemAspectRatio={0.75}
234
+ />
235
+ </View>
236
+ )
237
+
238
+ const styles = StyleSheet.create({
239
+ column: {
240
+ flexDirection: "column",
241
+ width: 358
242
+ }
243
+ })