@altruist03/ui 0.1.0 → 0.2.1

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 (207) hide show
  1. package/assets/images/noData.webp +0 -0
  2. package/dist/index.js +15237 -650
  3. package/dist/index.js.map +1 -1
  4. package/dist/noData-XPRHKMFR.webp +0 -0
  5. package/package.json +21 -7
  6. package/src/ThemeProvider/ThemeContext.tsx +75 -0
  7. package/src/ThemeProvider/index.ts +7 -0
  8. package/src/assets/images/noData.webp +0 -0
  9. package/src/assets.d.ts +10 -0
  10. package/src/components/Accordion/Accordion.stories.tsx +64 -0
  11. package/src/components/Accordion/Accordion.tsx +219 -0
  12. package/src/components/Accordion/Accordion.types.ts +16 -0
  13. package/src/components/Avatar/Avatar.stories.tsx +304 -0
  14. package/src/components/Avatar/Avatar.tsx +211 -0
  15. package/src/components/Avatar/Avatar.types.ts +33 -0
  16. package/src/components/Badge/Badge.stories.tsx +143 -0
  17. package/src/components/Badge/Badge.tsx +138 -0
  18. package/src/components/Badge/Badge.types.ts +24 -0
  19. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
  20. package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
  21. package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
  22. package/src/components/Button/Button.stories.tsx +457 -0
  23. package/src/components/Button/Button.tsx +202 -0
  24. package/src/components/Button/Button.types.ts +18 -0
  25. package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
  26. package/src/components/Checkbox/Checkbox.tsx +352 -0
  27. package/src/components/Checkbox/Checkbox.types.ts +123 -0
  28. package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
  29. package/src/components/CloseButton/CloseButton.tsx +117 -0
  30. package/src/components/CloseButton/CloseButton.types.ts +15 -0
  31. package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
  32. package/src/components/DataTable/DataTable.stories.tsx +233 -0
  33. package/src/components/DataTable/DataTable.tsx +968 -0
  34. package/src/components/DataTable/DataTable.types.ts +49 -0
  35. package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
  36. package/src/components/DatePicker/DatePicker.tsx +669 -0
  37. package/src/components/DatePicker/DatePicker.types.ts +105 -0
  38. package/src/components/DatePicker/DateTimePicker.tsx +35 -0
  39. package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
  40. package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
  41. package/src/components/Drawer/Drawer.stories.tsx +243 -0
  42. package/src/components/Drawer/Drawer.tsx +196 -0
  43. package/src/components/Drawer/Drawer.types.ts +60 -0
  44. package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
  45. package/src/components/FileUpload/FileUpload.tsx +216 -0
  46. package/src/components/FileUpload/FileUpload.types.ts +80 -0
  47. package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
  48. package/src/components/FilterPopover/FilterPopover.tsx +310 -0
  49. package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
  50. package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
  51. package/src/components/ImageViewer/useImagePanRotate.js +102 -0
  52. package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
  53. package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
  54. package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
  55. package/src/components/Loader/Loader.stories.tsx +95 -0
  56. package/src/components/Loader/Loader.tsx +132 -0
  57. package/src/components/Loader/Loader.types.ts +6 -0
  58. package/src/components/Menu/Menu.tsx +143 -0
  59. package/src/components/Menu/Menu.types.ts +28 -0
  60. package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
  61. package/src/components/MenuButton/MenuButton.tsx +45 -0
  62. package/src/components/MenuButton/MenuButton.types.ts +21 -0
  63. package/src/components/Modal/Modal.stories.tsx +266 -0
  64. package/src/components/Modal/Modal.tsx +399 -0
  65. package/src/components/Modal/index.ts +2 -0
  66. package/src/components/Modal/types.ts +126 -0
  67. package/src/components/Pagination/Pagination.stories.tsx +167 -0
  68. package/src/components/Pagination/Pagination.tsx +266 -0
  69. package/src/components/Pagination/Pagination.types.ts +20 -0
  70. package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
  71. package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
  72. package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
  73. package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
  74. package/src/components/PhoneField/PhoneField.tsx +284 -0
  75. package/src/components/PhoneField/PhoneField.types.ts +89 -0
  76. package/src/components/PhoneField/countryCodes.ts +236 -0
  77. package/src/components/Progress/Progress.stories.tsx +83 -0
  78. package/src/components/Progress/Progress.tsx +91 -0
  79. package/src/components/Progress/Progress.types.ts +22 -0
  80. package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
  81. package/src/components/RadioButton/RadioButton.tsx +427 -0
  82. package/src/components/RadioButton/RadioButton.types.ts +117 -0
  83. package/src/components/ReportCard/ReportCard.tsx +183 -0
  84. package/src/components/ReportCard/ReportCard.types.ts +17 -0
  85. package/src/components/SectionIcon/SectionIcon.tsx +87 -0
  86. package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
  87. package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
  88. package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
  89. package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
  90. package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
  91. package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
  92. package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
  93. package/src/components/Selector/Selector.stories.tsx +382 -0
  94. package/src/components/Selector/Selector.tsx +1310 -0
  95. package/src/components/Selector/Selector.types.ts +71 -0
  96. package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
  97. package/src/components/Skeleton/Skeleton.tsx +20 -0
  98. package/src/components/Skeleton/Skeleton.types.ts +3 -0
  99. package/src/components/Spinner/Spinner.stories.tsx +84 -0
  100. package/src/components/Spinner/Spinner.tsx +79 -0
  101. package/src/components/Spinner/Spinner.types.ts +8 -0
  102. package/src/components/StatCard/StatCard.stories.tsx +125 -0
  103. package/src/components/StatCard/StatCard.tsx +167 -0
  104. package/src/components/StatCard/StatCard.types.ts +20 -0
  105. package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
  106. package/src/components/StatusBadge/StatusBadge.tsx +74 -0
  107. package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
  108. package/src/components/Stepper/Stepper.stories.tsx +85 -0
  109. package/src/components/Stepper/Stepper.tsx +138 -0
  110. package/src/components/Stepper/Stepper.types.ts +16 -0
  111. package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
  112. package/src/components/SubTabs/SubTabs.tsx +279 -0
  113. package/src/components/SubTabs/SubTabs.types.ts +44 -0
  114. package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
  115. package/src/components/SummaryCard/SummaryCard.tsx +141 -0
  116. package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
  117. package/src/components/Switch/Switch.stories.tsx +59 -0
  118. package/src/components/Switch/Switch.tsx +217 -0
  119. package/src/components/Switch/Switch.types.ts +22 -0
  120. package/src/components/Tabs/Tabs.stories.tsx +178 -0
  121. package/src/components/Tabs/Tabs.tsx +197 -0
  122. package/src/components/Tabs/Tabs.types.ts +34 -0
  123. package/src/components/TextField/TextField.stories.tsx +249 -0
  124. package/src/components/TextField/TextField.tsx +462 -0
  125. package/src/components/TextField/TextField.types.ts +141 -0
  126. package/src/components/Toast/Toast.stories.tsx +237 -0
  127. package/src/components/Toast/Toast.tsx +246 -0
  128. package/src/components/Toast/Toast.types.ts +31 -0
  129. package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
  130. package/src/components/ToggleButton/ToggleButton.tsx +267 -0
  131. package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
  132. package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
  133. package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
  134. package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
  135. package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
  136. package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
  137. package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
  138. package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
  139. package/src/components/index.ts +157 -0
  140. package/src/icons/ReportDollarIcon.tsx +71 -0
  141. package/src/icons/index.ts +27 -0
  142. package/src/index.ts +214 -0
  143. package/src/storybook/preview.tsx +64 -0
  144. package/src/theme/index.ts +4 -0
  145. package/src/theme/types.d.ts +1 -0
  146. package/src/themes/cpc.ts +4 -0
  147. package/src/themes/firebird.ts +4 -0
  148. package/src/tokens/brands/cpc/themes/dark.json +82 -0
  149. package/src/tokens/brands/cpc/themes/light.json +1 -0
  150. package/src/tokens/brands/default/brand.json +20 -0
  151. package/src/tokens/brands/firebird/themes/dark.json +28 -0
  152. package/src/tokens/brands/firebird/themes/light.json +20 -0
  153. package/src/tokens/button.ts +24 -0
  154. package/src/tokens/components/accordion.json +52 -0
  155. package/src/tokens/components/avatar.json +40 -0
  156. package/src/tokens/components/badge.json +41 -0
  157. package/src/tokens/components/breadcrumb.json +13 -0
  158. package/src/tokens/components/button.json +67 -0
  159. package/src/tokens/components/card.json +39 -0
  160. package/src/tokens/components/checkbox.json +64 -0
  161. package/src/tokens/components/closebutton.json +55 -0
  162. package/src/tokens/components/datatable.json +40 -0
  163. package/src/tokens/components/datepicker.json +99 -0
  164. package/src/tokens/components/drawer.json +23 -0
  165. package/src/tokens/components/field.json +29 -0
  166. package/src/tokens/components/fileupload.json +57 -0
  167. package/src/tokens/components/filterpopover.json +29 -0
  168. package/src/tokens/components/input.json +31 -0
  169. package/src/tokens/components/ipwhitelistfield.json +16 -0
  170. package/src/tokens/components/loader.json +15 -0
  171. package/src/tokens/components/menu.json +21 -0
  172. package/src/tokens/components/modal.json +25 -0
  173. package/src/tokens/components/pagination.json +45 -0
  174. package/src/tokens/components/phonefield.json +41 -0
  175. package/src/tokens/components/progress.json +12 -0
  176. package/src/tokens/components/radio.json +87 -0
  177. package/src/tokens/components/reportcard.json +17 -0
  178. package/src/tokens/components/selector.json +87 -0
  179. package/src/tokens/components/selectscroll.json +65 -0
  180. package/src/tokens/components/skeleton.json +6 -0
  181. package/src/tokens/components/stepper.json +38 -0
  182. package/src/tokens/components/subtabs.json +45 -0
  183. package/src/tokens/components/switch.json +55 -0
  184. package/src/tokens/components/tabs.json +38 -0
  185. package/src/tokens/components/textfield.json +59 -0
  186. package/src/tokens/components/togglebutton.json +48 -0
  187. package/src/tokens/components/uploaddropzone.json +61 -0
  188. package/src/tokens/drawer.ts +37 -0
  189. package/src/tokens/foundation/breakpoints.json +10 -0
  190. package/src/tokens/foundation/colors.json +122 -0
  191. package/src/tokens/foundation/opacity.json +16 -0
  192. package/src/tokens/foundation/radius.json +14 -0
  193. package/src/tokens/foundation/size.json +38 -0
  194. package/src/tokens/foundation/spacing.json +19 -0
  195. package/src/tokens/foundation/typography.json +43 -0
  196. package/src/tokens/index.ts +3665 -0
  197. package/src/tokens/modal.ts +42 -0
  198. package/src/tokens/semantic/color.json +148 -0
  199. package/src/tokens/semantic/field.json +28 -0
  200. package/src/tokens/semantic/opacity.json +5 -0
  201. package/src/tokens/semantic/size.json +722 -0
  202. package/src/tokens/semantic/spacing.json +27 -0
  203. package/src/tokens/semantic/typography.json +87 -0
  204. package/src/utils/scrollLock.ts +44 -0
  205. package/dist/index.css +0 -514
  206. package/dist/index.css.map +0 -1
  207. package/dist/index.d.ts +0 -289
@@ -0,0 +1,254 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Checkbox, CheckboxGroup } from './Checkbox';
3
+ import { fn } from '@storybook/test';
4
+
5
+ const meta: Meta<typeof CheckboxGroup> = {
6
+ title: 'Design System/Checkbox',
7
+ component: CheckboxGroup,
8
+ tags: [],
9
+ decorators: [
10
+ (Story) => (
11
+ <div style={{ padding: '24px', maxWidth: '600px' }}>
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ args: {
17
+ label: 'Heading',
18
+ options: [
19
+ { label: 'Pick up', value: 'pickup' },
20
+ { label: 'Delivery', value: 'delivery' },
21
+ ],
22
+ direction: 'horizontal',
23
+ size: 'md',
24
+ disabled: false,
25
+ onChange: fn(),
26
+ } as any,
27
+ argTypes: {
28
+ label: {
29
+ control: { type: 'text' },
30
+ description: 'The heading/label displayed above the checkbox group',
31
+ },
32
+ direction: {
33
+ control: { type: 'radio' },
34
+ options: ['horizontal', 'vertical'],
35
+ description: 'Layout direction of the checkboxes',
36
+ },
37
+ size: {
38
+ control: { type: 'select' },
39
+ options: ['sm', 'md', 'lg'],
40
+ description: 'Size variant: sm, md, or lg',
41
+ },
42
+ disabled: {
43
+ control: { type: 'boolean' },
44
+ description: 'Disable all checkboxes in the group',
45
+ },
46
+ value: {
47
+ control: { type: 'object' },
48
+ description: 'Selected values (controlled mode)',
49
+ },
50
+ defaultValue: {
51
+ control: { type: 'object' },
52
+ description: 'Default selected values (uncontrolled mode)',
53
+ },
54
+ onChange: {
55
+ action: 'onChange',
56
+ description: 'Callback fired when selection changes',
57
+ },
58
+ },
59
+ };
60
+
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof CheckboxGroup>;
64
+
65
+ export const Playground: Story = {
66
+ render: (args) => <CheckboxGroup {...args} />,
67
+ args: {
68
+ label: 'Heading',
69
+ options: [
70
+ { label: 'Pick up', value: 'pickup' },
71
+ { label: 'Delivery', value: 'delivery' },
72
+ ],
73
+ direction: 'horizontal',
74
+ size: 'md',
75
+ },
76
+ };
77
+
78
+ export const Showcase: Story = {
79
+ render: () => (
80
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '32px' }}>
81
+ {/* Basic Horizontal Checkbox Group */}
82
+ <div>
83
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
84
+ Basic Checkboxes (Horizontal)
85
+ </h3>
86
+ <CheckboxGroup
87
+ label="Heading"
88
+ options={[
89
+ { label: 'Pick up', value: 'pickup' },
90
+ { label: 'Delivery', value: 'delivery' },
91
+ ]}
92
+ direction="horizontal"
93
+ />
94
+ </div>
95
+
96
+ {/* Basic Vertical Checkbox Group */}
97
+ <div>
98
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
99
+ Basic Checkboxes (Vertical)
100
+ </h3>
101
+ <CheckboxGroup
102
+ label="Preferences"
103
+ options={[
104
+ { label: 'Email Notifications', value: 'email' },
105
+ { label: 'SMS Notifications', value: 'sms' },
106
+ { label: 'Push Notifications', value: 'push' },
107
+ ]}
108
+ direction="vertical"
109
+ />
110
+ </div>
111
+
112
+ {/* Checkbox Group with Default Selection */}
113
+ <div>
114
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
115
+ With Default Selection
116
+ </h3>
117
+ <CheckboxGroup
118
+ label="Services"
119
+ options={[
120
+ { label: 'Pick up', value: 'pickup' },
121
+ { label: 'Delivery', value: 'delivery' },
122
+ { label: 'In-store', value: 'instore' },
123
+ ]}
124
+ defaultValue={['pickup']}
125
+ direction="horizontal"
126
+ />
127
+ </div>
128
+
129
+ {/* Disabled Checkbox Group */}
130
+ <div>
131
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
132
+ Disabled Checkboxes
133
+ </h3>
134
+ <CheckboxGroup
135
+ label="Heading"
136
+ options={[
137
+ { label: 'Pick up', value: 'pickup' },
138
+ { label: 'Delivery', value: 'delivery' },
139
+ ]}
140
+ disabled
141
+ direction="horizontal"
142
+ />
143
+ </div>
144
+
145
+ {/* Checkbox Group with Individual Disabled Options */}
146
+ <div>
147
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
148
+ With Individual Disabled Options
149
+ </h3>
150
+ <CheckboxGroup
151
+ label="Notification Preferences"
152
+ options={[
153
+ { label: 'Email', value: 'email' },
154
+ { label: 'SMS', value: 'sms', disabled: true },
155
+ { label: 'Push', value: 'push' },
156
+ ]}
157
+ direction="horizontal"
158
+ />
159
+ </div>
160
+
161
+ {/* Individual Checkbox Examples */}
162
+ <div>
163
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
164
+ Individual Checkboxes
165
+ </h3>
166
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
167
+ <Checkbox label="Selected" checked name="individual" />
168
+ <Checkbox label="Unselected" name="individual" />
169
+ <Checkbox label="Disabled Selected" checked disabled name="individual" />
170
+ <Checkbox label="Disabled Unselected" disabled name="individual" />
171
+ <Checkbox label="Indeterminate" indeterminate name="individual" />
172
+ </div>
173
+ </div>
174
+
175
+ {/* Multiple Selections */}
176
+ <div>
177
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
178
+ Multiple Selections
179
+ </h3>
180
+ <CheckboxGroup
181
+ label="Select All That Apply"
182
+ options={[
183
+ { label: 'Option 1', value: 'opt1' },
184
+ { label: 'Option 2', value: 'opt2' },
185
+ { label: 'Option 3', value: 'opt3' },
186
+ { label: 'Option 4', value: 'opt4' },
187
+ ]}
188
+ defaultValue={['opt1', 'opt3']}
189
+ direction="vertical"
190
+ />
191
+ </div>
192
+
193
+ {/* Without Label */}
194
+ <div>
195
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
196
+ Without Heading Label
197
+ </h3>
198
+ <CheckboxGroup
199
+ options={[
200
+ { label: 'Option A', value: 'a' },
201
+ { label: 'Option B', value: 'b' },
202
+ { label: 'Option C', value: 'c' },
203
+ ]}
204
+ direction="horizontal"
205
+ />
206
+ </div>
207
+
208
+ {/* Size variants: sm, md, lg */}
209
+ <div>
210
+ <h3 style={{ fontSize: '18px', fontWeight: 'bold', marginBottom: '16px' }}>
211
+ Size Variants (sm, md, lg)
212
+ </h3>
213
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
214
+ <div>
215
+ <h4 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '8px' }}>Small (sm)</h4>
216
+ <CheckboxGroup
217
+ label="Choose options"
218
+ options={[
219
+ { label: 'Option 1', value: '1' },
220
+ { label: 'Option 2', value: '2' },
221
+ ]}
222
+ size="sm"
223
+ direction="horizontal"
224
+ />
225
+ </div>
226
+ <div>
227
+ <h4 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '8px' }}>Medium (md)</h4>
228
+ <CheckboxGroup
229
+ label="Choose options"
230
+ options={[
231
+ { label: 'Option 1', value: '1' },
232
+ { label: 'Option 2', value: '2' },
233
+ ]}
234
+ size="md"
235
+ direction="horizontal"
236
+ />
237
+ </div>
238
+ <div>
239
+ <h4 style={{ fontSize: '14px', fontWeight: 600, marginBottom: '8px' }}>Large (lg)</h4>
240
+ <CheckboxGroup
241
+ label="Choose options"
242
+ options={[
243
+ { label: 'Option 1', value: '1' },
244
+ { label: 'Option 2', value: '2' },
245
+ ]}
246
+ size="lg"
247
+ direction="horizontal"
248
+ />
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ ),
254
+ };
@@ -0,0 +1,352 @@
1
+ import React, { useRef, useEffect, useId } from 'react';
2
+ import type { CheckboxProps, CheckboxGroupProps } from './Checkbox.types';
3
+ import { getCheckboxTokensFromResolved } from '../../tokens';
4
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
5
+
6
+ const CheckIcon = ({ size }: { size: number }) => (
7
+ <svg
8
+ width={size}
9
+ height={size}
10
+ viewBox="0 0 12 12"
11
+ fill="none"
12
+ aria-hidden="true"
13
+ style={{ display: 'block' }}
14
+ >
15
+ <path
16
+ d="M2.5 6L5 8.5L9.5 3.5"
17
+ stroke="currentColor"
18
+ strokeWidth="1.75"
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ </svg>
23
+ );
24
+
25
+ const IndeterminateIcon = ({ size }: { size: number }) => (
26
+ <svg
27
+ width={size}
28
+ height={size}
29
+ viewBox="0 0 12 12"
30
+ fill="none"
31
+ aria-hidden="true"
32
+ style={{ display: 'block' }}
33
+ >
34
+ <path d="M2.5 6H9.5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
35
+ </svg>
36
+ );
37
+
38
+ export const Checkbox = ({
39
+ label,
40
+ labelPlacement = 'end',
41
+ size = 'md',
42
+ checked,
43
+ defaultChecked,
44
+ disabled = false,
45
+ indeterminate = false,
46
+ name,
47
+ id,
48
+ value,
49
+ onChange,
50
+ onBlur,
51
+ onFocus,
52
+ helperText,
53
+ error,
54
+ }: CheckboxProps) => {
55
+ const generatedId = useId();
56
+ const checkboxId = id || `${name || 'checkbox'}-${value || generatedId}`;
57
+ const checkboxRef = useRef<HTMLInputElement>(null);
58
+ const [isHovered, setIsHovered] = React.useState(false);
59
+ const [isFocused, setIsFocused] = React.useState(false);
60
+ const [internalChecked, setInternalChecked] = React.useState(
61
+ checked !== undefined ? checked : defaultChecked || false
62
+ );
63
+
64
+ const resolvedTokens = useTokens();
65
+ const tokens = getCheckboxTokensFromResolved(resolvedTokens);
66
+
67
+ const checkboxSize =
68
+ size === 'sm' ? tokens.checkboxSizeSm
69
+ : size === 'lg' ? tokens.checkboxSizeLg
70
+ : tokens.checkboxSizeMd;
71
+ const checkboxBorderRadius =
72
+ size === 'sm' ? tokens.checkboxBorderRadiusSm
73
+ : size === 'lg' ? tokens.checkboxBorderRadiusLg
74
+ : tokens.checkboxBorderRadiusMd;
75
+ const labelFontSize =
76
+ size === 'sm' ? tokens.textFontSizeSm
77
+ : size === 'lg' ? tokens.textFontSizeLg
78
+ : tokens.textFontSizeMd;
79
+ const iconSize =
80
+ size === 'sm' ? 10
81
+ : size === 'lg' ? 14
82
+ : 12;
83
+
84
+ useEffect(() => {
85
+ if (checkboxRef.current) {
86
+ checkboxRef.current.indeterminate = indeterminate;
87
+ }
88
+ }, [indeterminate]);
89
+
90
+ useEffect(() => {
91
+ if (checked !== undefined) {
92
+ setInternalChecked(checked);
93
+ }
94
+ }, [checked]);
95
+
96
+ const isChecked = checked !== undefined ? checked : internalChecked;
97
+ const isActive = isChecked || indeterminate;
98
+
99
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
100
+ const newChecked = e.target.checked;
101
+ if (checked === undefined) {
102
+ setInternalChecked(newChecked);
103
+ }
104
+ onChange?.(e);
105
+ };
106
+
107
+ const isControlled = checked !== undefined;
108
+
109
+ const inputProps: React.InputHTMLAttributes<HTMLInputElement> = {
110
+ className: 'ds-checkbox-input',
111
+ type: 'checkbox',
112
+ onChange: handleChange,
113
+ name,
114
+ id: checkboxId,
115
+ value,
116
+ disabled,
117
+ 'aria-checked': indeterminate ? 'mixed' : isChecked,
118
+ };
119
+
120
+ if (isControlled) {
121
+ inputProps.checked = checked;
122
+ } else if (defaultChecked !== undefined) {
123
+ inputProps.defaultChecked = defaultChecked;
124
+ } else {
125
+ inputProps.checked = internalChecked;
126
+ }
127
+
128
+ const boxBorderColor =
129
+ isActive || (isHovered && !disabled) ? tokens.checkedBorderColor : tokens.checkboxColorInactive;
130
+
131
+ const boxStyle: React.CSSProperties = {
132
+ width: checkboxSize,
133
+ height: checkboxSize,
134
+ minWidth: checkboxSize,
135
+ borderRadius: checkboxBorderRadius,
136
+ border: `1.5px solid ${boxBorderColor}`,
137
+ backgroundColor: isActive ? tokens.checkboxColorActive : tokens.checkboxBackgroundInactive,
138
+ color: '#FFFFFF',
139
+ display: 'inline-flex',
140
+ alignItems: 'center',
141
+ justifyContent: 'center',
142
+ transition: 'border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease',
143
+ boxShadow:
144
+ isFocused ? `0 0 0 2px ${tokens.checkedBorderColor}33`
145
+ : isActive ? 'none'
146
+ : 'inset 0 1px 2px rgba(15, 23, 42, 0.04)',
147
+ pointerEvents: 'none',
148
+ opacity: disabled ? 0.7 : 1,
149
+ };
150
+
151
+ const labelStyle: React.CSSProperties = {
152
+ fontSize: labelFontSize,
153
+ fontWeight: tokens.textFontWeight,
154
+ color: disabled ? tokens.textColorInactive : tokens.textColorActive,
155
+ userSelect: 'none',
156
+ cursor: disabled ? 'not-allowed' : 'pointer',
157
+ margin: 0,
158
+ lineHeight: '1.35',
159
+ };
160
+
161
+ const controlRowStyle: React.CSSProperties = {
162
+ display: 'inline-flex',
163
+ alignItems: 'center',
164
+ flexDirection: labelPlacement === 'start' ? 'row-reverse' : 'row',
165
+ gap: tokens.textMarginLeft,
166
+ width: 'fit-content',
167
+ maxWidth: '100%',
168
+ cursor: disabled ? 'not-allowed' : 'pointer',
169
+ opacity: disabled ? 0.6 : 1,
170
+ };
171
+
172
+ return (
173
+ <div
174
+ className="ds-checkbox"
175
+ style={{
176
+ display: 'flex',
177
+ flexDirection: 'column',
178
+ alignItems: 'flex-start',
179
+ width: 'fit-content',
180
+ maxWidth: '100%',
181
+ }}
182
+ >
183
+ <label
184
+ htmlFor={checkboxId}
185
+ style={controlRowStyle}
186
+ onMouseEnter={() => setIsHovered(true)}
187
+ onMouseLeave={() => setIsHovered(false)}
188
+ >
189
+ <span
190
+ className="ds-checkbox-control"
191
+ style={{
192
+ position: 'relative',
193
+ display: 'inline-flex',
194
+ width: checkboxSize,
195
+ height: checkboxSize,
196
+ flexShrink: 0,
197
+ }}
198
+ >
199
+ <input
200
+ ref={checkboxRef}
201
+ {...inputProps}
202
+ onFocus={(e) => {
203
+ setIsFocused(true);
204
+ onFocus?.(e);
205
+ }}
206
+ onBlur={(e) => {
207
+ setIsFocused(false);
208
+ onBlur?.(e);
209
+ }}
210
+ style={{
211
+ position: 'absolute',
212
+ inset: 0,
213
+ width: '100%',
214
+ height: '100%',
215
+ margin: 0,
216
+ opacity: 0,
217
+ appearance: 'none',
218
+ WebkitAppearance: 'none',
219
+ cursor: disabled ? 'not-allowed' : 'pointer',
220
+ zIndex: 1,
221
+ }}
222
+ />
223
+ <span className="ds-checkbox-box" style={boxStyle} aria-hidden="true">
224
+ {indeterminate ?
225
+ <IndeterminateIcon size={iconSize} />
226
+ : isChecked ?
227
+ <CheckIcon size={iconSize} />
228
+ : null}
229
+ </span>
230
+ </span>
231
+ {label ?
232
+ <span style={labelStyle}>{label}</span>
233
+ : null}
234
+ </label>
235
+ {helperText != null && helperText !== '' && (
236
+ <div
237
+ style={{
238
+ marginTop: tokens.helperTextMarginTop,
239
+ marginLeft: `calc(${checkboxSize} + ${tokens.textMarginLeft})`,
240
+ fontSize: tokens.helperTextFontSize,
241
+ color: error ? tokens.helperTextErrorColor : tokens.helperTextColor,
242
+ fontWeight: tokens.helperTextFontWeight,
243
+ display: 'flex',
244
+ alignItems: 'flex-start',
245
+ gap: 4,
246
+ }}
247
+ role={error ? 'alert' : undefined}
248
+ >
249
+ <span>{helperText}</span>
250
+ </div>
251
+ )}
252
+ </div>
253
+ );
254
+ };
255
+
256
+ export const CheckboxGroup = ({
257
+ label,
258
+ options,
259
+ value,
260
+ defaultValue,
261
+ name,
262
+ disabled = false,
263
+ direction = 'horizontal',
264
+ size = 'md',
265
+ columns,
266
+ className,
267
+ onChange,
268
+ }: CheckboxGroupProps) => {
269
+ const groupName = name || `checkbox-group-${Math.random().toString(36).substr(2, 9)}`;
270
+ const [internalValue, setInternalValue] = React.useState<string[]>(
271
+ value !== undefined ? value : defaultValue || []
272
+ );
273
+
274
+ const resolvedTokens = useTokens();
275
+ const tokens = getCheckboxTokensFromResolved(resolvedTokens);
276
+
277
+ React.useEffect(() => {
278
+ if (value !== undefined) {
279
+ setInternalValue(value);
280
+ }
281
+ }, [value]);
282
+
283
+ const currentValue = value !== undefined ? value : internalValue;
284
+
285
+ const handleChange = (optionValue: string, checked: boolean) => {
286
+ let newValues: string[];
287
+ if (checked) {
288
+ newValues = [...currentValue, optionValue];
289
+ } else {
290
+ newValues = currentValue.filter((v) => v !== optionValue);
291
+ }
292
+
293
+ if (value === undefined) {
294
+ setInternalValue(newValues);
295
+ }
296
+ onChange?.(newValues);
297
+ };
298
+
299
+ const groupLayoutStyle: React.CSSProperties =
300
+ columns && columns > 1 ?
301
+ {
302
+ display: 'grid',
303
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
304
+ columnGap: tokens.groupGapHorizontal,
305
+ rowGap: tokens.groupGapVertical,
306
+ alignItems: 'start',
307
+ justifyItems: 'start',
308
+ }
309
+ : {
310
+ display: 'flex',
311
+ flexDirection: direction === 'horizontal' ? 'row' : 'column',
312
+ gap: direction === 'horizontal' ? tokens.groupGapHorizontal : tokens.groupGapVertical,
313
+ flexWrap: direction === 'horizontal' ? 'wrap' : 'nowrap',
314
+ alignItems: direction === 'horizontal' ? 'center' : 'flex-start',
315
+ };
316
+
317
+ return (
318
+ <div
319
+ className={className ? `ds-checkbox-group ${className}` : 'ds-checkbox-group'}
320
+ style={{ width: '100%' }}
321
+ >
322
+ {label && (
323
+ <span
324
+ style={{
325
+ display: 'block',
326
+ fontSize: tokens.headingFontSize,
327
+ fontWeight: tokens.headingFontWeight,
328
+ color: tokens.headingColor,
329
+ marginBottom: tokens.headingMarginBottom,
330
+ lineHeight: 1.4,
331
+ }}
332
+ >
333
+ {label}
334
+ </span>
335
+ )}
336
+ <div style={groupLayoutStyle}>
337
+ {options.map((option) => (
338
+ <Checkbox
339
+ key={option.value}
340
+ label={option.label}
341
+ value={option.value}
342
+ name={groupName}
343
+ size={size}
344
+ checked={currentValue.includes(option.value)}
345
+ disabled={disabled || option.disabled}
346
+ onChange={(e) => handleChange(option.value, e.target.checked)}
347
+ />
348
+ ))}
349
+ </div>
350
+ </div>
351
+ );
352
+ };