@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,73 @@
1
+ import React from "react"
2
+ import { screen } from "@testing-library/react"
3
+ import { describe, it, expect } from "vitest"
4
+ import { renderWithTheme } from "../../../test-utils"
5
+ import { ButtonGroup } from "./ButtonGroup"
6
+
7
+ describe("ButtonGroup", () => {
8
+ describe("when component is rendering", () => {
9
+ it("renders children buttons", () => {
10
+ renderWithTheme(
11
+ <ButtonGroup>
12
+ <button>Confirm</button>
13
+ <button>Cancel</button>
14
+ </ButtonGroup>
15
+ )
16
+ expect(screen.getByText("Confirm")).toBeInTheDocument()
17
+ expect(screen.getByText("Cancel")).toBeInTheDocument()
18
+ })
19
+ })
20
+
21
+ describe("when rendering layouts", () => {
22
+ it("renders stacked layout by default", () => {
23
+ renderWithTheme(
24
+ <ButtonGroup>
25
+ <button>Action</button>
26
+ </ButtonGroup>
27
+ )
28
+ expect(screen.getByText("Action")).toBeInTheDocument()
29
+ })
30
+
31
+ it("renders inline layout", () => {
32
+ renderWithTheme(
33
+ <ButtonGroup layout="inline">
34
+ <button>Action</button>
35
+ </ButtonGroup>
36
+ )
37
+ expect(screen.getByText("Action")).toBeInTheDocument()
38
+ })
39
+ })
40
+
41
+ describe("when rendering with description", () => {
42
+ it("renders description text", () => {
43
+ renderWithTheme(
44
+ <ButtonGroup description="Choose an option">
45
+ <button>Yes</button>
46
+ <button>No</button>
47
+ </ButtonGroup>
48
+ )
49
+ expect(screen.getByText("Choose an option")).toBeInTheDocument()
50
+ })
51
+
52
+ it("does not render description when not provided", () => {
53
+ renderWithTheme(
54
+ <ButtonGroup>
55
+ <button>Yes</button>
56
+ </ButtonGroup>
57
+ )
58
+ expect(screen.queryByText("Choose an option")).not.toBeInTheDocument()
59
+ })
60
+ })
61
+
62
+ describe("when using with ref", () => {
63
+ it("forwards ref", () => {
64
+ const ref = React.createRef<any>()
65
+ renderWithTheme(
66
+ <ButtonGroup ref={ref}>
67
+ <button>Action</button>
68
+ </ButtonGroup>
69
+ )
70
+ expect(ref.current).toBeTruthy()
71
+ })
72
+ })
73
+ })
@@ -26,7 +26,7 @@ const StyledGroupRoot = styled(View)<{
26
26
  }))
27
27
 
28
28
  const StyledButtonRow = styled(View)<{
29
- rowDirection: "row-reverse" | "column"
29
+ rowDirection: "row" | "column"
30
30
  rowAlign?: "center"
31
31
  rowGap: number
32
32
  }>(({ rowDirection, rowAlign, rowGap }) => ({
@@ -36,6 +36,14 @@ const StyledButtonRow = styled(View)<{
36
36
  ...(rowAlign ? { alignItems: rowAlign } : {})
37
37
  }))
38
38
 
39
+ const StyledChildSlot = styled(View)<{ slotInline: boolean }>(
40
+ ({ slotInline }) => ({
41
+ ...(slotInline
42
+ ? { flex: 1, minWidth: 0 }
43
+ : { width: "100%", alignSelf: "stretch" })
44
+ })
45
+ )
46
+
39
47
  /**
40
48
  * ButtonGroup arranges 1 or 2 buttons in a stacked or inline layout
41
49
  * with an optional description below.
@@ -67,7 +75,7 @@ export const ButtonGroup = React.forwardRef<View, ButtonGroupProps>(
67
75
  {...rest}
68
76
  >
69
77
  <StyledButtonRow
70
- rowDirection={isInline ? "row-reverse" : "column"}
78
+ rowDirection={isInline ? "row" : "column"}
71
79
  rowAlign={isInline ? "center" : undefined}
72
80
  rowGap={parseTokenValue(
73
81
  isInline
@@ -75,7 +83,21 @@ export const ButtonGroup = React.forwardRef<View, ButtonGroupProps>(
75
83
  : buttonGroup.spacing.stacked.gap
76
84
  )}
77
85
  >
78
- {children}
86
+ {React.Children.map(children, (child, index) => {
87
+ if (!React.isValidElement(child)) return child
88
+ return (
89
+ <StyledChildSlot key={index} slotInline={isInline}>
90
+ {React.cloneElement(
91
+ child as React.ReactElement<{
92
+ fullWidth?: boolean
93
+ }>,
94
+ {
95
+ fullWidth: true
96
+ }
97
+ )}
98
+ </StyledChildSlot>
99
+ )
100
+ })}
79
101
  </StyledButtonRow>
80
102
  {description && (
81
103
  <Typography
@@ -1,10 +1,17 @@
1
1
  import React from "react"
2
2
  import { View, StyleSheet } from "react-native"
3
+ import {
4
+ LabradorChefsHat,
5
+ CockerSpanielChefsHat,
6
+ CockapooBowl
7
+ } from "@butternutbox/pawprint-illustrations/breeds"
3
8
  import { Checkbox } from "./Checkbox"
4
9
  import type { CheckboxProps } from "./Checkbox"
5
10
  import { CheckboxGroup } from "./CheckboxGroup"
6
11
  import { Typography } from "../../atoms/Typography"
7
12
 
13
+ const ILLUSTRATION_SIZE = 72
14
+
8
15
  export default {
9
16
  title: "Molecules/Checkbox",
10
17
  component: Checkbox,
@@ -109,6 +116,71 @@ export const Tile = () => (
109
116
  </View>
110
117
  )
111
118
 
119
+ export const WithIllustration = () => (
120
+ <View style={styles.column}>
121
+ <View style={styles.section}>
122
+ <Typography size="sm" weight="semiBold" color="tertiary">
123
+ Vertical
124
+ </Typography>
125
+ <CheckboxGroup orientation="vertical">
126
+ <Checkbox
127
+ variant="tile"
128
+ label="Chicken"
129
+ subText="Tender & tasty"
130
+ illustration={
131
+ <LabradorChefsHat
132
+ width={ILLUSTRATION_SIZE}
133
+ height={ILLUSTRATION_SIZE}
134
+ />
135
+ }
136
+ />
137
+ <Checkbox
138
+ variant="tile"
139
+ label="Treats"
140
+ subText="Tasty rewards"
141
+ illustration={
142
+ <CockerSpanielChefsHat
143
+ width={ILLUSTRATION_SIZE}
144
+ height={ILLUSTRATION_SIZE}
145
+ />
146
+ }
147
+ />
148
+ <Checkbox
149
+ variant="tile"
150
+ label="Dry Food"
151
+ subText="Crunchy kibble"
152
+ illustration={
153
+ <CockapooBowl
154
+ width={ILLUSTRATION_SIZE}
155
+ height={ILLUSTRATION_SIZE}
156
+ />
157
+ }
158
+ />
159
+ </CheckboxGroup>
160
+ </View>
161
+
162
+ <View style={styles.section}>
163
+ <Typography size="sm" weight="semiBold" color="tertiary">
164
+ Selected
165
+ </Typography>
166
+ <CheckboxGroup orientation="vertical">
167
+ <Checkbox
168
+ variant="tile"
169
+ label="Chicken"
170
+ subText="Tender & tasty"
171
+ defaultChecked
172
+ illustration={
173
+ <LabradorChefsHat
174
+ width={ILLUSTRATION_SIZE}
175
+ height={ILLUSTRATION_SIZE}
176
+ />
177
+ }
178
+ />
179
+ </CheckboxGroup>
180
+ </View>
181
+ </View>
182
+ )
183
+
112
184
  export const AllStates = () => (
113
185
  <View style={styles.column}>
114
186
  {(["standalone", "tile"] as const).map((variant) => (
@@ -0,0 +1,117 @@
1
+ import React from "react"
2
+ import { screen } from "@testing-library/react"
3
+ import userEvent from "@testing-library/user-event"
4
+ import { describe, it, expect, vi } from "vitest"
5
+ import { renderWithTheme } from "../../../test-utils"
6
+ import { Checkbox } from "./Checkbox"
7
+
8
+ describe("Checkbox", () => {
9
+ describe("when component is rendering", () => {
10
+ it("renders label text", () => {
11
+ renderWithTheme(<Checkbox label="Accept terms" />)
12
+ expect(screen.getByText("Accept terms")).toBeInTheDocument()
13
+ })
14
+
15
+ it("renders label and subText", () => {
16
+ renderWithTheme(
17
+ <Checkbox label="Accept terms" subText="Required to proceed" />
18
+ )
19
+ expect(screen.getByText("Accept terms")).toBeInTheDocument()
20
+ expect(screen.getByText("Required to proceed")).toBeInTheDocument()
21
+ })
22
+ })
23
+
24
+ describe("when rendering variants", () => {
25
+ it("renders standalone variant", () => {
26
+ renderWithTheme(<Checkbox variant="standalone" label="Standalone" />)
27
+ expect(screen.getByText("Standalone")).toBeInTheDocument()
28
+ })
29
+
30
+ it("renders tile variant", () => {
31
+ renderWithTheme(<Checkbox variant="tile" label="Tile" />)
32
+ expect(screen.getByText("Tile")).toBeInTheDocument()
33
+ })
34
+
35
+ it("renders tile variant with illustration", () => {
36
+ renderWithTheme(
37
+ <Checkbox
38
+ variant="tile"
39
+ label="With illustration"
40
+ illustration={<span data-testid="illustration">🐶</span>}
41
+ />
42
+ )
43
+ expect(screen.getByTestId("illustration")).toBeInTheDocument()
44
+ })
45
+ })
46
+
47
+ describe("when component is uncontrolled", () => {
48
+ it("toggles checked state when clicked", async () => {
49
+ const user = userEvent.setup()
50
+ renderWithTheme(<Checkbox label="Toggle" />)
51
+
52
+ const checkbox = screen.getByRole("checkbox")
53
+ expect(checkbox).toHaveAttribute("aria-checked", "false")
54
+
55
+ await user.click(checkbox)
56
+ expect(checkbox).toHaveAttribute("aria-checked", "true")
57
+
58
+ await user.click(checkbox)
59
+ expect(checkbox).toHaveAttribute("aria-checked", "false")
60
+ })
61
+
62
+ it("can be initially checked via defaultChecked", () => {
63
+ renderWithTheme(<Checkbox label="Checked" defaultChecked />)
64
+
65
+ const checkbox = screen.getByRole("checkbox")
66
+ expect(checkbox).toHaveAttribute("aria-checked", "true")
67
+ })
68
+ })
69
+
70
+ describe("when component is controlled", () => {
71
+ it("calls onCheckedChange when clicked", async () => {
72
+ const user = userEvent.setup()
73
+ const onCheckedChange = vi.fn()
74
+ renderWithTheme(
75
+ <Checkbox
76
+ label="Controlled"
77
+ checked={false}
78
+ onCheckedChange={onCheckedChange}
79
+ />
80
+ )
81
+
82
+ await user.click(screen.getByRole("checkbox"))
83
+ expect(onCheckedChange).toHaveBeenCalledWith(true)
84
+ })
85
+
86
+ it("reflects controlled checked state", () => {
87
+ renderWithTheme(
88
+ <Checkbox label="Checked" checked={true} onCheckedChange={() => {}} />
89
+ )
90
+ expect(screen.getByRole("checkbox")).toHaveAttribute(
91
+ "aria-checked",
92
+ "true"
93
+ )
94
+ })
95
+ })
96
+
97
+ describe("when component is disabled", () => {
98
+ it("prevents interaction when disabled", async () => {
99
+ const user = userEvent.setup()
100
+ const onCheckedChange = vi.fn()
101
+ renderWithTheme(
102
+ <Checkbox label="Disabled" disabled onCheckedChange={onCheckedChange} />
103
+ )
104
+
105
+ await user.click(screen.getByRole("checkbox"))
106
+ expect(onCheckedChange).not.toHaveBeenCalled()
107
+ })
108
+ })
109
+
110
+ describe("when using with ref", () => {
111
+ it("forwards ref", () => {
112
+ const ref = React.createRef<any>()
113
+ renderWithTheme(<Checkbox ref={ref} label="Ref test" />)
114
+ expect(ref.current).toBeTruthy()
115
+ })
116
+ })
117
+ })
@@ -1,5 +1,5 @@
1
1
  import React from "react"
2
- import { View, PressableProps } from "react-native"
2
+ import { View, Pressable, PressableProps } from "react-native"
3
3
  import styled from "@emotion/native"
4
4
  import { useTheme } from "@emotion/react"
5
5
  import * as CheckboxPrimitive from "@rn-primitives/checkbox"
@@ -27,7 +27,7 @@ export type CheckboxProps = CheckboxOwnProps &
27
27
 
28
28
  const parseTokenValue = (value: string): number => parseFloat(value)
29
29
 
30
- const StyledRoot = styled(CheckboxPrimitive.Root)<{
30
+ const StyledRootPressable = styled(Pressable)<{
31
31
  rootFlexAlign: string
32
32
  rootGap: number
33
33
  rootOpacity: number
@@ -169,109 +169,115 @@ export const Checkbox = React.forwardRef<View, CheckboxProps>(
169
169
  const controlSize = parseTokenValue(checkbox.size.control.default)
170
170
 
171
171
  return (
172
- <StyledRoot
173
- ref={ref}
172
+ <CheckboxPrimitive.Root
174
173
  checked={isChecked}
175
174
  onCheckedChange={handleCheckedChange}
176
175
  disabled={disabled}
177
- rootFlexAlign={isTile ? "center" : "flex-start"}
178
- rootGap={parseTokenValue(
179
- isTile ? checkbox.checkboxTile.spacing.gap : checkbox.spacing.gap
180
- )}
181
- rootOpacity={
182
- disabled ? parseFloat(theme.tokens.primitives.opacity["40"]) : 1
183
- }
184
- rootPaddingVertical={
185
- isTile
186
- ? parseTokenValue(checkbox.checkboxTile.spacing.verticalPadding)
187
- : undefined
188
- }
189
- rootPaddingHorizontal={
190
- isTile
191
- ? parseTokenValue(checkbox.checkboxTile.spacing.horizontalPadding)
192
- : undefined
193
- }
194
- rootMaxWidth={
195
- isTile
196
- ? parseTokenValue(checkbox.checkboxTile.size.maxWidth)
197
- : undefined
198
- }
199
- rootBgColor={
200
- isTile
201
- ? isChecked
202
- ? checkbox.checkboxTile.colour.background.selected
203
- : checkbox.checkboxTile.colour.background.default
204
- : undefined
205
- }
206
- rootBorderWidth={
207
- isTile
208
- ? parseTokenValue(checkbox.checkboxTile.borderWidth.default)
209
- : undefined
210
- }
211
- rootBorderColor={
212
- isTile
213
- ? isChecked
214
- ? checkbox.checkboxTile.colour.border.selected
215
- : checkbox.checkboxTile.colour.border.default
216
- : undefined
217
- }
218
- rootBorderRadius={
219
- isTile
220
- ? parseTokenValue(checkbox.checkboxTile.borderRadius.default)
221
- : undefined
222
- }
223
- style={typeof style === "function" ? undefined : style}
224
- {...rest}
176
+ asChild
225
177
  >
226
- <StyledControl
227
- controlSize={controlSize}
228
- controlBorderWidth={parseTokenValue(checkbox.control.border.default)}
229
- controlBorderColor={
230
- isChecked
231
- ? checkbox.colour.background.selected
232
- : checkbox.colour.border.default
178
+ <StyledRootPressable
179
+ ref={ref}
180
+ rootFlexAlign={isTile ? "center" : "flex-start"}
181
+ rootGap={parseTokenValue(
182
+ isTile ? checkbox.checkboxTile.spacing.gap : checkbox.spacing.gap
183
+ )}
184
+ rootOpacity={
185
+ disabled ? parseFloat(theme.tokens.primitives.opacity["40"]) : 1
186
+ }
187
+ rootPaddingVertical={
188
+ isTile
189
+ ? parseTokenValue(checkbox.checkboxTile.spacing.verticalPadding)
190
+ : undefined
191
+ }
192
+ rootPaddingHorizontal={
193
+ isTile
194
+ ? parseTokenValue(checkbox.checkboxTile.spacing.horizontalPadding)
195
+ : undefined
196
+ }
197
+ rootMaxWidth={
198
+ isTile
199
+ ? parseTokenValue(checkbox.checkboxTile.size.maxWidth)
200
+ : undefined
201
+ }
202
+ rootBgColor={
203
+ isTile
204
+ ? isChecked
205
+ ? checkbox.checkboxTile.colour.background.selected
206
+ : checkbox.checkboxTile.colour.background.default
207
+ : undefined
233
208
  }
234
- controlBorderRadius={parseTokenValue(dimensions.borderRadius.xs)}
235
- controlBgColor={
236
- isChecked ? checkbox.colour.background.selected : "transparent"
209
+ rootBorderWidth={
210
+ isTile
211
+ ? parseTokenValue(checkbox.checkboxTile.borderWidth.default)
212
+ : undefined
237
213
  }
214
+ rootBorderColor={
215
+ isTile
216
+ ? isChecked
217
+ ? checkbox.checkboxTile.colour.border.selected
218
+ : checkbox.checkboxTile.colour.border.default
219
+ : undefined
220
+ }
221
+ rootBorderRadius={
222
+ isTile
223
+ ? parseTokenValue(checkbox.checkboxTile.borderRadius.default)
224
+ : undefined
225
+ }
226
+ style={typeof style === "function" ? undefined : style}
227
+ {...rest}
238
228
  >
239
- <CheckboxPrimitive.Indicator>
240
- <Icon icon={Check} size="xs" colour="alt" />
241
- </CheckboxPrimitive.Indicator>
242
- </StyledControl>
229
+ <StyledControl
230
+ controlSize={controlSize}
231
+ controlBorderWidth={parseTokenValue(
232
+ checkbox.control.border.default
233
+ )}
234
+ controlBorderColor={
235
+ isChecked
236
+ ? checkbox.colour.background.selected
237
+ : checkbox.colour.border.default
238
+ }
239
+ controlBorderRadius={parseTokenValue(dimensions.borderRadius.xs)}
240
+ controlBgColor={
241
+ isChecked ? checkbox.colour.background.selected : "transparent"
242
+ }
243
+ >
244
+ <CheckboxPrimitive.Indicator>
245
+ <Icon icon={Check} size="xs" colour="alt" />
246
+ </CheckboxPrimitive.Indicator>
247
+ </StyledControl>
243
248
 
244
- <StyledContent
245
- contentGap={parseTokenValue(checkbox.spacing.content.gap)}
246
- >
247
- {label && (
248
- <Typography
249
- token={checkbox.typography.label}
250
- color={checkbox.colour.text.title}
251
- >
252
- {label}
253
- </Typography>
254
- )}
255
- {subText && (
256
- <Typography
257
- token={checkbox.typography.subText}
258
- color={checkbox.colour.text.subtext}
249
+ <StyledContent
250
+ contentGap={parseTokenValue(checkbox.spacing.content.gap)}
251
+ >
252
+ {label && (
253
+ <Typography
254
+ token={checkbox.typography.label}
255
+ color={checkbox.colour.text.title}
256
+ >
257
+ {label}
258
+ </Typography>
259
+ )}
260
+ {subText && (
261
+ <Typography
262
+ token={checkbox.typography.subText}
263
+ color={checkbox.colour.text.subtext}
264
+ >
265
+ {subText}
266
+ </Typography>
267
+ )}
268
+ </StyledContent>
269
+
270
+ {isTile && illustration && (
271
+ <StyledIllustration
272
+ illustrationSize={parseTokenValue(
273
+ checkbox.checkboxTile.size.illustration.default
274
+ )}
259
275
  >
260
- {subText}
261
- </Typography>
276
+ {illustration}
277
+ </StyledIllustration>
262
278
  )}
263
- </StyledContent>
264
-
265
- {isTile && illustration && (
266
- <StyledIllustration
267
- illustrationSize={parseTokenValue(
268
- checkbox.checkboxTile.size.illustration.default
269
- )}
270
- >
271
- {illustration}
272
- </StyledIllustration>
273
- )}
274
- </StyledRoot>
279
+ </StyledRootPressable>
280
+ </CheckboxPrimitive.Root>
275
281
  )
276
282
  }
277
283
  )