@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,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,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
|
+
})
|