@dhasdk/simple-ui 1.0.7 → 1.0.8

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 (227) hide show
  1. package/.babelrc +12 -0
  2. package/.storybook/main.ts +35 -0
  3. package/.storybook/preview.ts +4 -0
  4. package/BAKpostcss.config.jsBAK +15 -0
  5. package/BAKtailwind.config.mjsBAK +99 -0
  6. package/README.md +464 -16
  7. package/coverage/storybook/coverage-storybook.json +32411 -0
  8. package/coverage/storybook/lcov-report/Accordion.tsx.html +805 -0
  9. package/coverage/storybook/lcov-report/Badge.tsx.html +346 -0
  10. package/coverage/storybook/lcov-report/Breadcrumbs.tsx.html +742 -0
  11. package/coverage/storybook/lcov-report/Button.tsx.html +448 -0
  12. package/coverage/storybook/lcov-report/ButtonGroup.tsx.html +403 -0
  13. package/coverage/storybook/lcov-report/Card.tsx.html +292 -0
  14. package/coverage/storybook/lcov-report/CharacterCounter.tsx.html +253 -0
  15. package/coverage/storybook/lcov-report/CheckBox.tsx.html +1555 -0
  16. package/coverage/storybook/lcov-report/DatePicker.tsx.html +826 -0
  17. package/coverage/storybook/lcov-report/Input.tsx.html +1012 -0
  18. package/coverage/storybook/lcov-report/List.tsx.html +364 -0
  19. package/coverage/storybook/lcov-report/Modal.tsx.html +745 -0
  20. package/coverage/storybook/lcov-report/Pill.tsx.html +358 -0
  21. package/coverage/storybook/lcov-report/Search.tsx.html +997 -0
  22. package/coverage/storybook/lcov-report/SearchContent.tsx.html +235 -0
  23. package/coverage/storybook/lcov-report/SectionHeader.tsx.html +358 -0
  24. package/coverage/storybook/lcov-report/Select.tsx.html +1012 -0
  25. package/coverage/storybook/lcov-report/Shield.tsx.html +802 -0
  26. package/coverage/storybook/lcov-report/SideBarNav.tsx.html +490 -0
  27. package/coverage/storybook/lcov-report/Skeleton.tsx.html +394 -0
  28. package/coverage/storybook/lcov-report/Slider.tsx.html +385 -0
  29. package/coverage/storybook/lcov-report/Status.tsx.html +322 -0
  30. package/coverage/storybook/lcov-report/Tabs.tsx.html +610 -0
  31. package/coverage/storybook/lcov-report/Toggle.tsx.html +373 -0
  32. package/coverage/storybook/lcov-report/Tooltip.tsx.html +496 -0
  33. package/coverage/storybook/lcov-report/base.css +224 -0
  34. package/coverage/storybook/lcov-report/block-navigation.js +87 -0
  35. package/coverage/storybook/lcov-report/favicon.png +0 -0
  36. package/coverage/storybook/lcov-report/index.html +476 -0
  37. package/coverage/storybook/lcov-report/prettify.css +1 -0
  38. package/coverage/storybook/lcov-report/prettify.js +2 -0
  39. package/coverage/storybook/lcov-report/sort-arrow-sprite.png +0 -0
  40. package/coverage/storybook/lcov-report/sorter.js +196 -0
  41. package/coverage/storybook/lcov.info +2312 -0
  42. package/dist/README.md +1815 -0
  43. package/eslint.config.mjs +13 -0
  44. package/package.json +6 -7
  45. package/project.json +11 -0
  46. package/src/assets/img/Frame.svg +5 -0
  47. package/src/assets/img/backArrowRight.svg +10 -0
  48. package/src/assets/img/bc-separator.png +0 -0
  49. package/src/assets/img/calendar.png +0 -0
  50. package/src/assets/img/calendar.svg +4 -0
  51. package/src/assets/img/check.svg +5 -0
  52. package/src/assets/img/check_box.svg +10 -0
  53. package/src/assets/img/check_box_empty.svg +10 -0
  54. package/src/assets/img/check_box_fill.svg +10 -0
  55. package/src/assets/img/check_box_fill_empty.svg +10 -0
  56. package/src/assets/img/chevron-down-white.svg +2 -0
  57. package/src/assets/img/chevron-down.svg +2 -0
  58. package/src/assets/img/chevron-left.svg +1 -0
  59. package/src/assets/img/chevron-right-light.svg +4 -0
  60. package/src/assets/img/chevron-right.svg +3 -0
  61. package/src/assets/img/chevron-up-white.svg +1 -0
  62. package/src/assets/img/chevron-up.svg +1 -0
  63. package/src/assets/img/clock.svg +6 -0
  64. package/src/assets/img/close.svg +1 -0
  65. package/src/assets/img/close2.svg +6 -0
  66. package/src/assets/img/closeModal.svg +10 -0
  67. package/src/assets/img/close_icon_dark.svg +10 -0
  68. package/src/assets/img/close_small.svg +3 -0
  69. package/src/assets/img/emergency_home.svg +10 -0
  70. package/src/assets/img/first-aid-kit.svg +7 -0
  71. package/src/assets/img/heartbeat.svg +4 -0
  72. package/src/assets/img/home-gray.svg +3 -0
  73. package/src/assets/img/home.svg +3 -0
  74. package/src/assets/img/hospital.jpg +0 -0
  75. package/src/assets/img/indeterminate_check_box.svg +10 -0
  76. package/src/assets/img/indeterminate_check_box_fill.svg +10 -0
  77. package/src/assets/img/info_24_ 1d4ed8.svg +3 -0
  78. package/src/assets/img/info_24_ 2c6441.svg +3 -0
  79. package/src/assets/img/marker_check_by_default.svg +10 -0
  80. package/src/assets/img/marker_check_by_default_fill.svg +10 -0
  81. package/src/assets/img/minus-accordion.svg +5 -0
  82. package/src/assets/img/minus.svg +3 -0
  83. package/src/assets/img/open.svg +1 -0
  84. package/src/assets/img/pill-white.svg +7 -0
  85. package/src/assets/img/pill.svg +5 -0
  86. package/src/assets/img/plus-accordion.svg +5 -0
  87. package/src/assets/img/plus.svg +4 -0
  88. package/src/assets/img/prescription.svg +6 -0
  89. package/src/assets/img/search.svg +10 -0
  90. package/src/assets/img/search_icon_light.svg +10 -0
  91. package/src/assets/img/separator.svg +3 -0
  92. package/src/assets/img/stethoscope-white.svg +8 -0
  93. package/src/assets/img/stethoscope.svg +8 -0
  94. package/src/assets/img/thumb_up.svg +10 -0
  95. package/src/assets/img/vector.svg +3 -0
  96. package/src/assets/img/warning-badge-disabled.svg +11 -0
  97. package/src/assets/img/warning-badge-green.svg +11 -0
  98. package/src/assets/img/warning-badge-red.svg +11 -0
  99. package/src/assets/img/warning-badge-yellow.svg +11 -0
  100. package/src/assets/img/warning.svg +10 -0
  101. package/src/global.d.ts +13 -0
  102. package/{index.d.ts → src/index.ts} +13 -5
  103. package/src/lib/Accordian--Accordian.stories.tsx +312 -0
  104. package/src/lib/Accordion.spec.tsx +384 -0
  105. package/src/lib/Accordion.tsx +240 -0
  106. package/src/lib/AppointmentPicker.spec.tsx +138 -0
  107. package/src/lib/AppointmentPicker.tsx +97 -0
  108. package/src/lib/Badge--Badge.stories.tsx +60 -0
  109. package/src/lib/Badge.spec.tsx +70 -0
  110. package/src/lib/Badge.tsx +87 -0
  111. package/src/lib/Breadcrumbs-Breadcrumbs.stories.tsx +114 -0
  112. package/src/lib/Breadcrumbs.spec.tsx +218 -0
  113. package/src/lib/Breadcrumbs.tsx +219 -0
  114. package/src/lib/Button--Button.stories.tsx +220 -0
  115. package/src/lib/Button.spec.tsx +241 -0
  116. package/src/lib/Button.tsx +121 -0
  117. package/src/lib/ButtonGroup--ButtonGroup.stories.tsx +129 -0
  118. package/src/lib/ButtonGroup.spec.tsx +89 -0
  119. package/src/lib/ButtonGroup.tsx +107 -0
  120. package/src/lib/Card--Card.stories.tsx +113 -0
  121. package/src/lib/Card.spec.tsx +112 -0
  122. package/src/lib/Card.tsx +69 -0
  123. package/src/lib/CharacterCounter--CharacterCounter.stories.tsx +169 -0
  124. package/src/lib/CharacterCounter.spec.tsx +123 -0
  125. package/src/lib/CharacterCounter.tsx +56 -0
  126. package/src/lib/CheckBox--CheckBox.stories.tsx +107 -0
  127. package/src/lib/CheckBox.spec.tsx +412 -0
  128. package/src/lib/CheckBox.tsx +491 -0
  129. package/src/lib/DatePicker--DatePicker.stories.tsx +228 -0
  130. package/src/lib/DatePicker.spec.tsx +424 -0
  131. package/src/lib/DatePicker.tsx +247 -0
  132. package/src/lib/Input--Input.stories.tsx +449 -0
  133. package/src/lib/Input.spec.tsx +281 -0
  134. package/src/lib/Input.tsx +309 -0
  135. package/src/lib/List--List.stories.tsx +157 -0
  136. package/src/lib/List.spec.tsx +211 -0
  137. package/src/lib/List.tsx +93 -0
  138. package/src/lib/Modal--Modal.stories.tsx +454 -0
  139. package/src/lib/Modal.spec.tsx +202 -0
  140. package/src/lib/Modal.tsx +220 -0
  141. package/src/lib/Pill--Pill.stories.tsx +98 -0
  142. package/src/lib/Pill.spec.tsx +103 -0
  143. package/src/lib/Pill.tsx +91 -0
  144. package/src/lib/ProgressBar.spec.tsx +106 -0
  145. package/src/lib/ProgressBar.tsx +112 -0
  146. package/src/lib/RadioGroup.spec.tsx +84 -0
  147. package/src/lib/RadioGroup.tsx +74 -0
  148. package/src/lib/RadioIcon.tsx +13 -0
  149. package/src/lib/Search--Search.stories.tsx +67 -0
  150. package/src/lib/Search.spec.tsx +182 -0
  151. package/src/lib/Search.tsx +304 -0
  152. package/src/lib/SearchContent.tsx +51 -0
  153. package/src/lib/SectionHeader--SectionHeader.stories.tsx +98 -0
  154. package/src/lib/SectionHeader.spec.tsx +60 -0
  155. package/src/lib/SectionHeader.tsx +91 -0
  156. package/src/lib/Select--Select.stories.tsx +387 -0
  157. package/src/lib/Select.spec.tsx +493 -0
  158. package/src/lib/Select.tsx +311 -0
  159. package/src/lib/Shield--Shield.stories.tsx +196 -0
  160. package/src/lib/Shield.spec.tsx +275 -0
  161. package/src/lib/Shield.tsx +239 -0
  162. package/src/lib/SideBarNav--SideBarNav.stories.tsx +136 -0
  163. package/src/lib/SideBarNav.spec.tsx +178 -0
  164. package/src/lib/SideBarNav.tsx +135 -0
  165. package/src/lib/Skeleton--Skeleton.stories.tsx +77 -0
  166. package/src/lib/Skeleton.module.css +16 -0
  167. package/src/lib/Skeleton.spec.tsx +83 -0
  168. package/src/lib/Skeleton.tsx +103 -0
  169. package/src/lib/SkipLink.spec.tsx +76 -0
  170. package/src/lib/SkipLink.tsx +48 -0
  171. package/src/lib/Slider--Slider.stories.tsx +108 -0
  172. package/src/lib/Slider.module.css +109 -0
  173. package/src/lib/Slider.spec.tsx +67 -0
  174. package/src/lib/Slider.tsx +101 -0
  175. package/src/lib/Status--Status.stories.tsx +93 -0
  176. package/src/lib/Status.spec.tsx +118 -0
  177. package/src/lib/Status.tsx +79 -0
  178. package/src/lib/Tabs--Tabs.stories.tsx +294 -0
  179. package/src/lib/Tabs.spec.tsx +249 -0
  180. package/src/lib/Tabs.tsx +188 -0
  181. package/src/lib/Tester.spec.tsx +17 -0
  182. package/src/lib/Toggle--Toggle.stories.tsx +162 -0
  183. package/src/lib/Toggle.spec.tsx +122 -0
  184. package/src/lib/Toggle.tsx +96 -0
  185. package/src/lib/Tooltip--Tooltip.stories.tsx +315 -0
  186. package/src/lib/Tooltip.spec.tsx +307 -0
  187. package/src/lib/Tooltip.tsx +137 -0
  188. package/src/lib/bak-simple-ui.stories.tsx-bak +24 -0
  189. package/src/styles.css +190 -0
  190. package/tsconfig.json +25 -0
  191. package/tsconfig.lib.json +42 -0
  192. package/tsconfig.spec.json +29 -0
  193. package/tsconfig.storybook.json +36 -0
  194. package/vite.config.mts +87 -0
  195. package/vitest.setup.ts +12 -0
  196. package/index.css +0 -1
  197. package/index.js +0 -35
  198. package/index.mjs +0 -4981
  199. package/lib/Accordion.d.ts +0 -36
  200. package/lib/AppointmentPicker.d.ts +0 -21
  201. package/lib/Badge.d.ts +0 -11
  202. package/lib/Breadcrumbs.d.ts +0 -13
  203. package/lib/Button.d.ts +0 -15
  204. package/lib/ButtonGroup.d.ts +0 -8
  205. package/lib/Card.d.ts +0 -11
  206. package/lib/CharacterCounter.d.ts +0 -11
  207. package/lib/CheckBox.d.ts +0 -30
  208. package/lib/DatePicker.d.ts +0 -7
  209. package/lib/Input.d.ts +0 -16
  210. package/lib/List.d.ts +0 -22
  211. package/lib/Modal.d.ts +0 -18
  212. package/lib/Pill.d.ts +0 -13
  213. package/lib/ProgressBar.d.ts +0 -19
  214. package/lib/RadioGroup.d.ts +0 -15
  215. package/lib/Search.d.ts +0 -26
  216. package/lib/SearchContent.d.ts +0 -6
  217. package/lib/SectionHeader.d.ts +0 -18
  218. package/lib/Select.d.ts +0 -19
  219. package/lib/Shield.d.ts +0 -12
  220. package/lib/SideBarNav.d.ts +0 -21
  221. package/lib/Skeleton.d.ts +0 -15
  222. package/lib/SkipLink.d.ts +0 -22
  223. package/lib/Slider.d.ts +0 -14
  224. package/lib/Status.d.ts +0 -10
  225. package/lib/Tabs.d.ts +0 -23
  226. package/lib/Toggle.d.ts +0 -11
  227. package/lib/Tooltip.d.ts +0 -14
@@ -0,0 +1,107 @@
1
+ import { Meta } from '@storybook/react';
2
+ import { CheckBox, CheckBoxGroup, CheckBoxProps } from './CheckBox';
3
+
4
+ // import imagePath from 'src/assets/img/pill.svg';
5
+ const imagePath = 'src/assets/img/hospital.jpg';
6
+ // imagePath: 'src/assets/img/heartbeat.svg',
7
+
8
+ // Meta object - defines basic storybook options for this story
9
+ export default {
10
+ title: 'Components/CheckBox',
11
+ component: CheckBox,
12
+ argTypes: {
13
+ variant: {
14
+ control: 'select',
15
+ options: ['','']
16
+ },
17
+ },
18
+ args: {
19
+ // label: 'Button', // set default argument values
20
+ },
21
+ parameters: {
22
+ layout: 'centered', // options are 'centered', 'fullscreen', and 'padded' (default value)
23
+ backgrounds: { default: 'light' }, // options are light, medium, or dark
24
+ },
25
+ } as Meta<CheckBoxProps>;
26
+
27
+ // Define "Default" story
28
+ export const Default = {
29
+ args: {
30
+ ariaLabel: 'test label',
31
+ alt: 'image alt',
32
+ children: 'CheckBox 1'
33
+ }
34
+ };
35
+
36
+ export const Group = () => {
37
+ // const [searchResults, setSearchResults] = useState<DataSearchResults>();
38
+
39
+ return (
40
+ <>
41
+ <div className='mb-4'>Note: 'level' prop does not take effect w/o use of the CheckBoxContainer component.</div>
42
+ <CheckBox level={0}>Group 1</CheckBox>
43
+ <CheckBox level={1}>g1, item 1</CheckBox>
44
+ <CheckBox level={1}>g1, item 2</CheckBox>
45
+ <CheckBox level={0}>Group 2</CheckBox>
46
+ <CheckBox level={1}>g2, item 1</CheckBox>
47
+ <CheckBox level={1}>g2, item 2</CheckBox>
48
+ <CheckBox level={2}>g2, item 2, item 1</CheckBox>
49
+ </>
50
+ );
51
+ };
52
+
53
+
54
+ export const GroupContainer = () => {
55
+ // const [searchResults, setSearchResults] = useState<DataSearchResults>();
56
+
57
+ return (
58
+ <CheckBoxGroup>
59
+ <CheckBox level={0}>Group 1</CheckBox>
60
+ <CheckBox level={1}>g1, item 1</CheckBox>
61
+ <CheckBox level={1}>g1, item 2</CheckBox>
62
+ <CheckBox level={0}>Group 2</CheckBox>
63
+ <CheckBox level={1}>g2, item 1</CheckBox>
64
+ <CheckBox level={1}>g2, item 2</CheckBox>
65
+ <CheckBox level={2}>g2, item 2, item 1</CheckBox>
66
+ </CheckBoxGroup>
67
+ );
68
+ };
69
+
70
+
71
+ export const GroupContainer2 = () => {
72
+ // const [searchResults, setSearchResults] = useState<DataSearchResults>();
73
+
74
+ return (
75
+ <CheckBoxGroup>
76
+ <CheckBox level={0}>Group 1</CheckBox>
77
+ <CheckBox level={1}>g1, item 1</CheckBox>
78
+ <CheckBox level={1}>g1, item 2</CheckBox>
79
+ <CheckBox level={2}>g1, item 2, item 1</CheckBox>
80
+ <CheckBox level={2}>g1, item 2, item 2</CheckBox>
81
+ <CheckBox level={0}>Group 2</CheckBox>
82
+ <CheckBox level={1}>g2, item 1</CheckBox>
83
+ <CheckBox level={1}>g2, item 2</CheckBox>
84
+ <CheckBox level={2}>g2, item 2, item 1</CheckBox>
85
+ </CheckBoxGroup>
86
+ );
87
+ };
88
+
89
+
90
+ export const GroupContainer3 = () => {
91
+ // const [searchResults, setSearchResults] = useState<DataSearchResults>();
92
+
93
+ return (
94
+ <CheckBoxGroup>
95
+ <CheckBox level={0}>Group 1</CheckBox>
96
+ <CheckBox level={1}>g1, item 1</CheckBox>
97
+ <CheckBox level={1}>g1, item 2</CheckBox>
98
+ <CheckBox level={2}>g1, item 2, item 1</CheckBox>
99
+ <CheckBox level={2}>g1, item 2, item 2</CheckBox>
100
+ <CheckBox level={1}>g1, item 3</CheckBox>
101
+ <CheckBox level={0}>Group 2</CheckBox>
102
+ <CheckBox level={1}>g2, item 1</CheckBox>
103
+ <CheckBox level={1}>g2, item 2</CheckBox>
104
+ <CheckBox level={2}>g2, item 2, item 1</CheckBox>
105
+ </CheckBoxGroup>
106
+ );
107
+ };
@@ -0,0 +1,412 @@
1
+ import React, { createRef } from "react";
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { describe, it, expect, vi } from "vitest";
5
+ import { axe } from "vitest-axe";
6
+
7
+ import { CheckBox, CheckBoxGroup } from "./CheckBox";
8
+
9
+ // add jest-dom / vitest-dom matchers
10
+ // expect.extend(toHaveNoViolations);
11
+
12
+ describe("CheckBox component", () => {
13
+ it("should render unchecked by default and have no accessibility violations", async () => {
14
+ render(<main><CheckBox ariaLabel="accept terms">Accept terms</CheckBox></main>);
15
+
16
+ // find checkbox input
17
+ const input = screen.getByRole("checkbox", { name: "accept terms" });
18
+ expect(input).toBeInTheDocument();
19
+ expect(input).not.toBeChecked();
20
+
21
+ // icon img alt should reflect 'unchecked'
22
+ const img = screen.getByAltText("unchecked") as HTMLImageElement;
23
+ expect(img).toBeInTheDocument();
24
+ expect(img.src).toContain("check_box_empty.svg");
25
+
26
+ // axe accessibility scan
27
+ const results = await axe(document.body);
28
+ expect(results).toHaveNoViolations();
29
+ });
30
+
31
+ it("toggles to checked on click and calls setStatusUpdate", async () => {
32
+ const mockSet = vi.fn();
33
+ render(
34
+ <CheckBox
35
+ ariaLabel="my box"
36
+ setStatusUpdate={mockSet}
37
+ index={0}
38
+ >
39
+ Label
40
+ </CheckBox>
41
+ );
42
+
43
+ const input = screen.getByRole("checkbox", { name: "my box" });
44
+ // click to check
45
+ await userEvent.click(input);
46
+ expect(input).toBeChecked();
47
+
48
+ // after toggle, the internal effect should call setStatusUpdate('checked', 0)
49
+ expect(mockSet).toHaveBeenCalledWith("checked", 0);
50
+
51
+ // the icon src should update to checked icon
52
+ const img = screen.getByAltText("checked") as HTMLImageElement;
53
+ expect(img.src).toContain("check_box.svg");
54
+ });
55
+
56
+
57
+ it("uses marker icon when checked with marker prop", async () => {
58
+ render(
59
+ <CheckBox
60
+ ariaLabel="marker box"
61
+ marker={true}
62
+ setStatusUpdate={vi.fn()}
63
+ index={0}
64
+ >
65
+ Marker
66
+ </CheckBox>
67
+ );
68
+
69
+ const input = screen.getByRole("checkbox", { name: "marker box" }) as HTMLInputElement;
70
+ // check it
71
+ await userEvent.click(input);
72
+ expect(input).toBeChecked();
73
+
74
+ // marker (unchecked) initially shows regular empty – marker only kicks in on checked
75
+ // now that it's checked, we expect the marker icon
76
+ const img = screen.getByAltText("checked") as HTMLImageElement;
77
+ expect(img.src).toContain("marker_check_by_default.svg");
78
+ });
79
+
80
+ it("uses filled marker icon when checked with marker and fill props", async () => {
81
+ render(
82
+ <CheckBox
83
+ ariaLabel="filled marker box"
84
+ marker={true}
85
+ fill={true}
86
+ setStatusUpdate={vi.fn()}
87
+ index={0}
88
+ >
89
+ Filled Marker
90
+ </CheckBox>
91
+ );
92
+
93
+ const input = screen.getByRole("checkbox", { name: "filled marker box" }) as HTMLInputElement;
94
+ // check it
95
+ await userEvent.click(input);
96
+ expect(input).toBeChecked();
97
+
98
+ // with fill and marker, we expect the filled marker icon
99
+ const img = screen.getByAltText("checked") as HTMLImageElement;
100
+ expect(img.src).toContain("marker_check_by_default_fill.svg");
101
+ });
102
+
103
+ });
104
+
105
+
106
+ // CHECKBOX GROUP COMPONENT --------------------------
107
+
108
+ describe("CheckBoxGroup component", () => {
109
+
110
+ const Siblings = (props: { bridgeParent?: boolean }) => (
111
+ <CheckBoxGroup bridgeParent>
112
+ <CheckBox level={0} ariaLabel="item A">A</CheckBox>
113
+ <CheckBox level={0} ariaLabel="item B">B</CheckBox>
114
+ <CheckBox level={1} ariaLabel="item BA">BA</CheckBox>
115
+ <CheckBox level={1} ariaLabel="item BB">BB</CheckBox>
116
+ <CheckBox level={2} ariaLabel="item BBA">BBA</CheckBox>
117
+ <CheckBox level={2} ariaLabel="item BBB">BBB</CheckBox>
118
+ <CheckBox level={2} ariaLabel="item BBC">BBC</CheckBox>
119
+ <CheckBox level={1} ariaLabel="item BC">BC</CheckBox>
120
+ <CheckBox level={0} ariaLabel="item C">C</CheckBox>
121
+ <CheckBox level={1} ariaLabel="item CA">CA</CheckBox>
122
+ <CheckBox level={2} ariaLabel="item CAA">CAA</CheckBox>
123
+ <CheckBox level={2} ariaLabel="item CAB">CAB</CheckBox>
124
+ <CheckBox level={2} ariaLabel="item CAC">CAC</CheckBox>
125
+ </CheckBoxGroup>
126
+ );
127
+
128
+
129
+ const SiblingsFill = (props: { bridgeParent?: boolean }) => (
130
+ <CheckBoxGroup bridgeParent fill>
131
+ <CheckBox level={0} ariaLabel="item A">A</CheckBox>
132
+ <CheckBox level={0} ariaLabel="item B">B</CheckBox>
133
+ <CheckBox level={1} ariaLabel="item BA">BA</CheckBox>
134
+ <CheckBox level={1} ariaLabel="item BB">BB</CheckBox>
135
+ <CheckBox level={2} ariaLabel="item BBA">BBA</CheckBox>
136
+ <CheckBox level={2} ariaLabel="item BBB">BBB</CheckBox>
137
+ <CheckBox level={2} ariaLabel="item BBC">BBC</CheckBox>
138
+ <CheckBox level={1} ariaLabel="item BC">BC</CheckBox>
139
+ <CheckBox level={0} ariaLabel="item C">C</CheckBox>
140
+ <CheckBox level={1} ariaLabel="item CA">CA</CheckBox>
141
+ <CheckBox level={2} ariaLabel="item CAA">CAA</CheckBox>
142
+ <CheckBox level={2} ariaLabel="item CAB">CAB</CheckBox>
143
+ <CheckBox level={2} ariaLabel="item CAC">CAC</CheckBox>
144
+ </CheckBoxGroup>
145
+ );
146
+
147
+
148
+
149
+ /**
150
+ * Build a tiny hierarchy:
151
+ * - parent (level 0)
152
+ * - child 1 (level 1)
153
+ * - child 2 (level 1)
154
+ */
155
+ const Parent = () => (
156
+ <CheckBoxGroup>
157
+ <CheckBox level={0} ariaLabel="parent">Parent</CheckBox>
158
+ <CheckBox level={1} ariaLabel="child 1">Child 1</CheckBox>
159
+ <CheckBox level={1} ariaLabel="child 2">Child 2</CheckBox>
160
+ </CheckBoxGroup>
161
+ );
162
+
163
+ it("checks all children when parent is clicked", async () => {
164
+ render(<Parent />);
165
+
166
+ const parentInput = screen.getByRole("checkbox", { name: "parent" });
167
+ const child1 = screen.getByRole("checkbox", { name: "child 1" });
168
+ const child2 = screen.getByRole("checkbox", { name: "child 2" });
169
+
170
+ // click parent → should check parent and both children
171
+ await userEvent.click(parentInput);
172
+ expect(parentInput).toBeChecked();
173
+ expect(child1).toBeChecked();
174
+ expect(child2).toBeChecked();
175
+ });
176
+
177
+ it("sets parent to indeterminate if only one child is checked", async () => {
178
+ render(<Parent />);
179
+
180
+ const parentInput = screen.getByRole("checkbox", { name: "parent" }) as HTMLInputElement;
181
+ const child1 = screen.getByRole("checkbox", { name: "child 1" });
182
+ const child2 = screen.getByRole("checkbox", { name: "child 2" });
183
+
184
+ // click only the first child
185
+ await userEvent.click(child1);
186
+ expect(child1).toBeChecked();
187
+ expect(child2).not.toBeChecked();
188
+
189
+ // parent should now be indeterminate
190
+ expect(parentInput.indeterminate).toBe(true);
191
+ });
192
+
193
+ it("unchecks all children when parent is clicked twice", async () => {
194
+ render(<Parent />);
195
+
196
+ const parentInput = screen.getByRole("checkbox", { name: "parent" });
197
+ const child1 = screen.getByRole("checkbox", { name: "child 1" });
198
+ const child2 = screen.getByRole("checkbox", { name: "child 2" });
199
+
200
+ // first click → all checked
201
+ await userEvent.click(parentInput);
202
+ expect(child1).toBeChecked();
203
+ expect(child2).toBeChecked();
204
+
205
+ // second click → all unchecked
206
+ await userEvent.click(parentInput);
207
+ expect(parentInput).not.toBeChecked();
208
+ expect(child1).not.toBeChecked();
209
+ expect(child2).not.toBeChecked();
210
+ });
211
+
212
+
213
+ // Uses SIBLINGS
214
+ it("only toggles the clicked checkbox among siblings at the same level", async () => {
215
+ render(<Siblings />);
216
+
217
+ const [a, b, ba, bb, bba, bbb, bbc, bc, c, ca, caa, cab, cac] = ["item A", "item B", "item BA",
218
+ "item BB", "item BBA", "item BBB", "item BBC", "item BC", "item C",
219
+ "item CA", "item CAA", "item CAB", "item CAC"].map(name =>
220
+ screen.getByRole("checkbox", { name }) as HTMLInputElement
221
+ );
222
+
223
+ // click A → only A becomes checked
224
+ await userEvent.click(a);
225
+ expect(a).toBeChecked(); // Checked
226
+ expect(b).not.toBeChecked();
227
+ expect(ba).not.toBeChecked();
228
+ expect(bb).not.toBeChecked();
229
+ expect(bba).not.toBeChecked();
230
+ expect(bbb).not.toBeChecked();
231
+ expect(bbc).not.toBeChecked();
232
+ expect(bc).not.toBeChecked();
233
+ expect(c).not.toBeChecked();
234
+
235
+ // click B → A remains checked, B toggles on, C stays off
236
+ await userEvent.click(b);
237
+ expect(a).toBeChecked(); // Checked
238
+ expect(b).toBeChecked(); // Checked
239
+ expect(ba).toBeChecked(); // Checked
240
+ expect(bb).toBeChecked(); // Checked
241
+ expect(bba).toBeChecked(); // Checked
242
+ expect(bbb).toBeChecked(); // Checked
243
+ expect(bbc).toBeChecked(); // Checked
244
+ expect(bc).toBeChecked(); // Checked
245
+ expect(c).not.toBeChecked();
246
+
247
+ // click A again → A toggles off, B stays on, C stays off
248
+ await userEvent.click(a);
249
+ expect(a).not.toBeChecked();
250
+ expect(b).toBeChecked(); // Checked
251
+ expect(ba).toBeChecked(); // Checked
252
+ expect(bb).toBeChecked(); // Checked
253
+ expect(bba).toBeChecked(); // Checked
254
+ expect(bbb).toBeChecked(); // Checked
255
+ expect(bbc).toBeChecked(); // Checked
256
+ expect(bc).toBeChecked(); // Checked
257
+ expect(c).not.toBeChecked();
258
+
259
+ // click BB → A remains unchecked, BB toggles off
260
+ // click BB → BB and its descendants are unchecked; B should become indeterminate
261
+ await userEvent.click(bb);
262
+
263
+ // first grab everything as HTMLInputElements
264
+ const bInput = b as HTMLInputElement;
265
+
266
+ expect(a).not.toBeChecked();
267
+
268
+ // instead of expecting B to be unchecked, we assert that:
269
+ // • it's not checked,
270
+ // • but it *is* indeterminate
271
+ expect(bInput.checked).toBe(false);
272
+ expect(bInput.indeterminate).toBe(true);
273
+
274
+ // BA stays checked
275
+ expect(ba).toBeChecked();
276
+
277
+ // BB itself is now unchecked
278
+ expect(bb).not.toBeChecked();
279
+
280
+ // its children (BBA, BBB, BBC) are all unchecked
281
+ expect(bba).not.toBeChecked();
282
+ expect(bbb).not.toBeChecked();
283
+ expect(bbc).not.toBeChecked();
284
+
285
+ // BC (sibling-child of B) remains checked
286
+ expect(bc).toBeChecked();
287
+
288
+ expect(c).not.toBeChecked();
289
+
290
+
291
+ await userEvent.click(caa);
292
+ expect(c).not.toBeChecked();
293
+ expect(ca).not.toBeChecked();
294
+ expect(caa).toBeChecked();
295
+ expect(cab).not.toBeChecked();
296
+ expect(cac).not.toBeChecked();
297
+ });
298
+
299
+
300
+ it("only toggles the clicked checkbox among siblings at the same level - fill variant", async () => {
301
+ render(<SiblingsFill />);
302
+
303
+ const [a, b, ba, bb, bba, bbb, bbc, bc, c, ca, caa, cab, cac] = ["item A", "item B", "item BA",
304
+ "item BB", "item BBA", "item BBB", "item BBC", "item BC", "item C",
305
+ "item CA", "item CAA", "item CAB", "item CAC"].map(name =>
306
+ screen.getByRole("checkbox", { name }) as HTMLInputElement
307
+ );
308
+
309
+ // click A → only A becomes checked
310
+ await userEvent.click(a);
311
+ expect(a).toBeChecked(); // Checked
312
+ expect(b).not.toBeChecked();
313
+ expect(ba).not.toBeChecked();
314
+ expect(bb).not.toBeChecked();
315
+ expect(bba).not.toBeChecked();
316
+ expect(bbb).not.toBeChecked();
317
+ expect(bbc).not.toBeChecked();
318
+ expect(bc).not.toBeChecked();
319
+ expect(c).not.toBeChecked();
320
+
321
+ // click B → A remains checked, B toggles on, C stays off
322
+ await userEvent.click(b);
323
+ expect(a).toBeChecked(); // Checked
324
+ expect(b).toBeChecked(); // Checked
325
+ expect(ba).toBeChecked(); // Checked
326
+ expect(bb).toBeChecked(); // Checked
327
+ expect(bba).toBeChecked(); // Checked
328
+ expect(bbb).toBeChecked(); // Checked
329
+ expect(bbc).toBeChecked(); // Checked
330
+ expect(bc).toBeChecked(); // Checked
331
+ expect(c).not.toBeChecked();
332
+
333
+ // click A again → A toggles off, B stays on, C stays off
334
+ await userEvent.click(a);
335
+ expect(a).not.toBeChecked();
336
+ expect(b).toBeChecked(); // Checked
337
+ expect(ba).toBeChecked(); // Checked
338
+ expect(bb).toBeChecked(); // Checked
339
+ expect(bba).toBeChecked(); // Checked
340
+ expect(bbb).toBeChecked(); // Checked
341
+ expect(bbc).toBeChecked(); // Checked
342
+ expect(bc).toBeChecked(); // Checked
343
+ expect(c).not.toBeChecked();
344
+
345
+ // click BB → A remains unchecked, BB toggles off
346
+ // click BB → BB and its descendants are unchecked; B should become indeterminate
347
+ await userEvent.click(bb);
348
+
349
+ // first grab everything as HTMLInputElements
350
+ const bInput = b as HTMLInputElement;
351
+
352
+ expect(a).not.toBeChecked();
353
+
354
+ // instead of expecting B to be unchecked, we assert that:
355
+ // • it's not checked,
356
+ // • but it *is* indeterminate
357
+ expect(bInput.checked).toBe(false);
358
+ expect(bInput.indeterminate).toBe(true);
359
+
360
+ // BA stays checked
361
+ expect(ba).toBeChecked();
362
+
363
+ // BB itself is now unchecked
364
+ expect(bb).not.toBeChecked();
365
+
366
+ // its children (BBA, BBB, BBC) are all unchecked
367
+ expect(bba).not.toBeChecked();
368
+ expect(bbb).not.toBeChecked();
369
+ expect(bbc).not.toBeChecked();
370
+
371
+ // BC (sibling-child of B) remains checked
372
+ expect(bc).toBeChecked();
373
+
374
+ expect(c).not.toBeChecked();
375
+
376
+
377
+ await userEvent.click(caa);
378
+ expect(c).not.toBeChecked();
379
+ expect(ca).not.toBeChecked();
380
+ expect(caa).toBeChecked();
381
+ expect(cab).not.toBeChecked();
382
+ expect(cac).not.toBeChecked();
383
+ });
384
+
385
+ // Uses SIBLINGS
386
+ it("with bridgeParent=true still treats siblings independently", async () => {
387
+ render(<Siblings bridgeParent={true} />);
388
+
389
+ const [a, b, c] = ["item A", "item B", "item C"].map(name =>
390
+ screen.getByRole("checkbox", { name }) as HTMLInputElement
391
+ );
392
+
393
+ // click C → only C becomes checked
394
+ await userEvent.click(c);
395
+ expect(a).not.toBeChecked();
396
+ expect(b).not.toBeChecked();
397
+ expect(c).toBeChecked();
398
+
399
+ // click B → C remains checked, B toggles on, A stays off
400
+ await userEvent.click(b);
401
+ expect(a).not.toBeChecked();
402
+ expect(b).toBeChecked();
403
+ expect(c).toBeChecked();
404
+
405
+ // click B again → B toggles off, C stays on
406
+ await userEvent.click(b);
407
+ expect(a).not.toBeChecked();
408
+ expect(b).not.toBeChecked();
409
+ expect(c).toBeChecked();
410
+
411
+ });
412
+ });