@altruist03/ui 0.0.0-stage → 0.2.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 (205) hide show
  1. package/assets/images/noData.webp +0 -0
  2. package/dist/index.js +15318 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/noData-XPRHKMFR.webp +0 -0
  5. package/package.json +51 -4
  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/README.md +0 -3
@@ -0,0 +1,427 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import type { RadioButtonProps, RadioButtonGroupProps } from './RadioButton.types';
3
+ import { getRadioTokensFromResolved } from '../../tokens';
4
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
5
+ // import { Icon as FirebirdIcon } from "firebird-icon-lib";
6
+
7
+ export const RadioButton = ({
8
+ label,
9
+ description,
10
+ size = 'md',
11
+ value,
12
+ checked,
13
+ disabled = false,
14
+ name,
15
+ id,
16
+ onChange,
17
+ onBlur,
18
+ onFocus,
19
+ }: RadioButtonProps) => {
20
+ const radioId = id || `${name || 'radio'}-${value}`;
21
+ const resolvedTokens = useTokens();
22
+ const tokens = getRadioTokensFromResolved(resolvedTokens);
23
+ const sizeKey = size as 'sm' | 'md' | 'lg';
24
+ const sizeTokens = (resolvedTokens?.components as any)?.radio?.size?.[sizeKey];
25
+ const radioSize = sizeTokens?.radioSize ?? tokens.radioSize;
26
+ const labelFontSize = sizeTokens?.labelFontSize ?? tokens.labelFontSize;
27
+ const dotSize = sizeTokens?.dotSize ?? tokens.radioDotSize;
28
+
29
+ const [focused, setFocused] = useState(false);
30
+
31
+ const handleFocus = useCallback(() => setFocused(true), []);
32
+ const handleBlur = useCallback(() => setFocused(false), []);
33
+
34
+ const containerStyle: React.CSSProperties = {
35
+ display: 'flex',
36
+ alignItems: description ? 'flex-start' : 'center',
37
+ position: 'relative',
38
+ margin: 0,
39
+ marginBottom: tokens.labelMarginBottom,
40
+ cursor: disabled ? 'not-allowed' : 'pointer',
41
+ userSelect: 'none',
42
+ };
43
+
44
+ // Hidden native input for accessibility
45
+ const inputStyle: React.CSSProperties = {
46
+ position: 'absolute',
47
+ opacity: 0,
48
+ cursor: disabled ? 'not-allowed' : 'pointer',
49
+ width: 0,
50
+ height: 0,
51
+ };
52
+
53
+ // Custom checkmark outer circle - always show border ring, fill with background
54
+ const checkmarkStyle: React.CSSProperties = {
55
+ position: 'relative',
56
+ flexShrink: 0,
57
+ width: radioSize,
58
+ height: radioSize,
59
+ borderRadius: '50%',
60
+ backgroundColor: tokens.radioBackground,
61
+ border: `${tokens.radioStrokeWidth} solid ${tokens.radioColorInactive}`,
62
+ boxSizing: 'border-box',
63
+ display: 'flex',
64
+ alignItems: 'center',
65
+ justifyContent: 'center',
66
+ marginRight: tokens.labelMarginLeft,
67
+ transition: 'border-color 0.2s ease, box-shadow 0.2s ease',
68
+ boxShadow:
69
+ focused && !disabled && tokens.radioFocusRing !== 'none' ? tokens.radioFocusRing : undefined,
70
+ opacity: disabled ? 0.6 : 1,
71
+ };
72
+
73
+ // Inner dot (visible only when checked) - uses tokenized dot color and size
74
+ const dotStyle: React.CSSProperties = {
75
+ position: 'absolute',
76
+ top: '50%',
77
+ left: '50%',
78
+ transform: 'translate(-50%, -50%)',
79
+ width: dotSize,
80
+ height: dotSize,
81
+ borderRadius: '50%',
82
+ background: tokens.radioColorDot,
83
+ display: checked ? 'block' : 'none',
84
+ };
85
+
86
+ const textStyle: React.CSSProperties = {
87
+ fontFamily: tokens.labelFontFamily,
88
+ fontSize: labelFontSize,
89
+ fontWeight: tokens.labelFontWeight,
90
+ color: checked ? tokens.labelColorActive : tokens.labelColorInactive,
91
+ userSelect: 'none',
92
+ transition: 'color 0.2s ease',
93
+ };
94
+
95
+ const labelContainerStyle: React.CSSProperties = {
96
+ display: 'flex',
97
+ flexDirection: 'column',
98
+ };
99
+
100
+ const descriptionStyle: React.CSSProperties = {
101
+ fontFamily: tokens.labelFontFamily,
102
+ fontSize: (() => {
103
+ if (typeof labelFontSize === 'string') {
104
+ const numValue = parseInt(labelFontSize.replace('px', ''), 10);
105
+ return isNaN(numValue) ? '11px' : `${Math.round(numValue * 0.786)}px`;
106
+ }
107
+ return `${Math.round((labelFontSize as number) * 0.786)}px`;
108
+ })(),
109
+ fontWeight: tokens.labelFontWeight,
110
+ color: checked ? tokens.labelColorActive : tokens.labelColorInactive,
111
+ opacity: 0.8,
112
+ marginTop: '2px',
113
+ userSelect: 'none',
114
+ };
115
+
116
+ return (
117
+ <label htmlFor={radioId} style={containerStyle}>
118
+ <input
119
+ type="radio"
120
+ id={radioId}
121
+ name={name}
122
+ value={value}
123
+ checked={checked}
124
+ disabled={disabled}
125
+ onChange={onChange}
126
+ onBlur={(e) => {
127
+ handleBlur();
128
+ onBlur?.(e);
129
+ }}
130
+ onFocus={(e) => {
131
+ handleFocus();
132
+ onFocus?.(e);
133
+ }}
134
+ style={inputStyle}
135
+ />
136
+ <span style={checkmarkStyle}>
137
+ <span style={dotStyle} />
138
+ </span>
139
+ {description ?
140
+ <div style={labelContainerStyle}>
141
+ <span style={textStyle}>{label}</span>
142
+ <span style={descriptionStyle}>{description}</span>
143
+ </div>
144
+ : <span style={textStyle}>{label}</span>}
145
+ </label>
146
+ );
147
+ };
148
+
149
+ export const RadioButtonGroup = ({
150
+ label,
151
+ helperText,
152
+ required = false,
153
+ error = false,
154
+ options,
155
+ value,
156
+ defaultValue,
157
+ id,
158
+ name,
159
+ disabled = false,
160
+ direction = 'horizontal',
161
+ groupClassName,
162
+ itemClassName,
163
+ size = 'md',
164
+ onChange,
165
+ }: RadioButtonGroupProps) => {
166
+ const groupName = name || `radio-group-${Math.random().toString(36).substr(2, 9)}`;
167
+ const groupId = id ?? `${groupName}-group`;
168
+ const [internalValue, setInternalValue] = React.useState<string | undefined>(
169
+ value !== undefined ? value : defaultValue
170
+ );
171
+ const resolvedTokens = useTokens();
172
+ const tokens = getRadioTokensFromResolved(resolvedTokens);
173
+ const errorColor = resolvedTokens?.semantic?.color?.error?.default || '#c00';
174
+ const sizeKey = size as 'sm' | 'md' | 'lg';
175
+ const sizeTokens = (resolvedTokens?.components as any)?.radio?.size?.[sizeKey];
176
+ const radioSize = sizeTokens?.radioSize ?? tokens.radioSize;
177
+ const labelFontSize = sizeTokens?.labelFontSize ?? tokens.labelFontSize;
178
+ const descriptionFontSize = sizeTokens?.descriptionFontSize ?? tokens.descriptionFontSize;
179
+ const dotSize = sizeTokens?.dotSize ?? tokens.radioDotSize;
180
+
181
+ React.useEffect(() => {
182
+ if (value !== undefined) {
183
+ setInternalValue(value);
184
+ }
185
+ }, [value]);
186
+
187
+ const currentValue = value !== undefined ? value : internalValue;
188
+
189
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
190
+ const newValue = event.target.value;
191
+ if (value === undefined) {
192
+ // Uncontrolled
193
+ setInternalValue(newValue);
194
+ }
195
+ onChange?.(newValue);
196
+ };
197
+
198
+ const containerStyle: React.CSSProperties = {
199
+ display: 'flex',
200
+ flexDirection: 'column',
201
+ };
202
+
203
+ const headingStyle: React.CSSProperties = {
204
+ display: 'block',
205
+ marginBottom: parseInt(tokens.headingMarginBottom, 10),
206
+ fontSize: parseInt(tokens.headingFontSize, 10),
207
+ fontWeight: tokens.headingFontWeight,
208
+ color: tokens.headingColor,
209
+ fontFamily: tokens.headingFontFamily,
210
+ };
211
+
212
+ const groupStyle: React.CSSProperties = {
213
+ display: 'flex',
214
+ flexDirection: direction === 'horizontal' ? 'row' : 'column',
215
+ columnGap: direction === 'horizontal' ? tokens.groupGapHorizontal : tokens.groupGapVertical,
216
+ rowGap: direction === 'horizontal' ? tokens.groupGapVertical : tokens.groupGapHorizontal,
217
+ flexWrap: 'wrap',
218
+ };
219
+
220
+ return (
221
+ <div style={containerStyle}>
222
+ {label != null && label !== '' && (
223
+ <label style={headingStyle}>
224
+ {required && <span style={{ color: errorColor, marginRight: 4 }}>*</span>}
225
+ {label}
226
+ </label>
227
+ )}
228
+ <div
229
+ role="radiogroup"
230
+ aria-invalid={error}
231
+ aria-describedby={helperText ? `${groupId}-helper` : undefined}
232
+ style={groupStyle}
233
+ className={groupClassName}
234
+ >
235
+ {options.map((option) => {
236
+ const isChecked = currentValue === option.value;
237
+ const optionDisabled = disabled || option.disabled;
238
+ const optionId = `${groupName}-${option.value}`;
239
+
240
+ return (
241
+ <div key={option.value} className={itemClassName}>
242
+ <RadioButtonGroupItem
243
+ option={option}
244
+ optionId={optionId}
245
+ groupName={groupName}
246
+ isChecked={isChecked}
247
+ optionDisabled={optionDisabled}
248
+ error={error}
249
+ tokens={tokens}
250
+ radioSize={radioSize}
251
+ labelFontSize={labelFontSize}
252
+ descriptionFontSize={descriptionFontSize}
253
+ dotSize={dotSize}
254
+ handleChange={handleChange}
255
+ />
256
+ </div>
257
+ );
258
+ })}
259
+ </div>
260
+ {helperText != null && helperText !== '' && (
261
+ <div
262
+ id={`${groupId}-helper`}
263
+ style={{
264
+ marginTop: tokens.helperMarginTop,
265
+ fontSize: tokens.helperFontSize,
266
+ color: error ? tokens.helperErrorColor : tokens.helperColor,
267
+ fontFamily: tokens.labelFontFamily,
268
+ display: 'flex',
269
+ alignItems: 'flex-start',
270
+ gap: 4,
271
+ }}
272
+ role={error ? 'alert' : undefined}
273
+ >
274
+ {/* <FirebirdIcon name="info_circle" size={16} /> */}
275
+ <span>{helperText}</span>
276
+ </div>
277
+ )}
278
+ </div>
279
+ );
280
+ };
281
+
282
+ // Extracted to its own component so each item can have its own hover state
283
+ function RadioButtonGroupItem({
284
+ option,
285
+ optionId,
286
+ groupName,
287
+ isChecked,
288
+ optionDisabled,
289
+ error = false,
290
+ tokens,
291
+ radioSize,
292
+ labelFontSize,
293
+ descriptionFontSize,
294
+ dotSize,
295
+ handleChange,
296
+ }: {
297
+ option: {
298
+ label: string;
299
+ value: string;
300
+ description?: string;
301
+ disabled?: boolean;
302
+ };
303
+ optionId: string;
304
+ groupName: string;
305
+ isChecked: boolean;
306
+ optionDisabled: boolean | undefined;
307
+ error?: boolean;
308
+ tokens: ReturnType<typeof getRadioTokensFromResolved>;
309
+ radioSize: string;
310
+ labelFontSize: string;
311
+ descriptionFontSize: string;
312
+ dotSize: string;
313
+ handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
314
+ }) {
315
+ const activeColor = tokens.optionBorderActive;
316
+ const inactiveBorderColor = error ? tokens.helperErrorColor : tokens.optionBorderInactive;
317
+ const activeBackground = tokens.optionBackgroundActive;
318
+ const inactiveBackground = tokens.optionBackgroundInactive;
319
+ const activeTextColor = tokens.optionTextActive;
320
+ const inactiveTextColor = tokens.optionTextInactive;
321
+
322
+ const labelStyle: React.CSSProperties = {
323
+ display: 'flex',
324
+ flexDirection: 'row',
325
+ alignItems: 'center',
326
+ margin: 0,
327
+ marginBottom: option.description ? tokens.labelMarginBottom : 0,
328
+ // padding: '12px 20px',
329
+ // borderRadius: 999,
330
+ // border: `2px solid ${isChecked ? activeColor : inactiveBorderColor}`,
331
+ // backgroundColor: isChecked ? activeBackground : inactiveBackground,
332
+ cursor: optionDisabled ? 'not-allowed' : 'pointer',
333
+ userSelect: 'none',
334
+ transition: 'border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease',
335
+ // boxShadow:
336
+ // focused && !optionDisabled && tokens.radioFocusRing !== 'none' ?
337
+ // tokens.radioFocusRing
338
+ // : undefined,
339
+ opacity: optionDisabled ? 0.6 : 1,
340
+ };
341
+
342
+ const inputStyle: React.CSSProperties = {
343
+ position: 'absolute',
344
+ opacity: 0,
345
+ cursor: optionDisabled ? 'not-allowed' : 'pointer',
346
+ width: 0,
347
+ height: 0,
348
+ };
349
+
350
+ const checkmarkStyle: React.CSSProperties = {
351
+ position: 'relative',
352
+ flexShrink: 0,
353
+ width: radioSize,
354
+ height: radioSize,
355
+ borderRadius: '50%',
356
+ backgroundColor: isChecked ? activeBackground : inactiveBackground,
357
+ border: `${tokens.radioStrokeWidth} solid ${isChecked ? activeColor : inactiveBorderColor}`,
358
+ boxSizing: 'border-box',
359
+ display: 'flex',
360
+ alignItems: 'center',
361
+ justifyContent: 'center',
362
+ marginRight: 10,
363
+ transition: 'border-color 0.2s ease, background-color 0.2s ease',
364
+ };
365
+
366
+ const dotStyleInner: React.CSSProperties = {
367
+ position: 'absolute',
368
+ top: '50%',
369
+ left: '50%',
370
+ transform: 'translate(-50%, -50%)',
371
+ width: dotSize,
372
+ height: dotSize,
373
+ borderRadius: '50%',
374
+ background: tokens.radioColorDot,
375
+ display: isChecked ? 'block' : 'none',
376
+ };
377
+
378
+ const textStyle: React.CSSProperties = {
379
+ fontFamily: tokens.labelFontFamily,
380
+ fontSize: labelFontSize,
381
+ fontWeight: isChecked ? 600 : tokens.labelFontWeight,
382
+ color: isChecked ? activeTextColor : inactiveTextColor,
383
+ userSelect: 'none',
384
+ transition: 'color 0.2s ease',
385
+ lineHeight: '1.5',
386
+ };
387
+
388
+ const labelContainerStyle: React.CSSProperties = {
389
+ display: 'flex',
390
+ flexDirection: 'column',
391
+ marginLeft: `calc(${radioSize} + 10px)`,
392
+ marginTop: '6px',
393
+ };
394
+
395
+ const descriptionStyle: React.CSSProperties = {
396
+ fontFamily: tokens.labelFontFamily,
397
+ fontSize: descriptionFontSize,
398
+ fontWeight: tokens.descriptionFontWeight,
399
+ color: tokens.descriptionColor,
400
+ userSelect: 'none',
401
+ transition: 'color 0.2s ease',
402
+ };
403
+
404
+ return (
405
+ <label key={option.value} htmlFor={optionId} style={labelStyle}>
406
+ <input
407
+ type="radio"
408
+ id={optionId}
409
+ name={groupName}
410
+ value={option.value}
411
+ checked={isChecked}
412
+ disabled={!!optionDisabled}
413
+ onChange={handleChange}
414
+ style={inputStyle}
415
+ />
416
+ <span style={checkmarkStyle}>
417
+ <span style={dotStyleInner} />
418
+ </span>
419
+ <span style={textStyle}>{option.label}</span>
420
+ {option.description ?
421
+ <div style={labelContainerStyle}>
422
+ <span style={descriptionStyle}>{option.description}</span>
423
+ </div>
424
+ : <></>}
425
+ </label>
426
+ );
427
+ }
@@ -0,0 +1,117 @@
1
+ export type RadioButtonSize = "sm" | "md" | "lg";
2
+
3
+ export interface RadioButtonProps {
4
+ /**
5
+ * The label text displayed next to the radio button
6
+ */
7
+ label: string;
8
+ /**
9
+ * Optional description text displayed below the label
10
+ */
11
+ description?: string;
12
+ /**
13
+ * Size variant: sm, md, or lg
14
+ */
15
+ size?: RadioButtonSize;
16
+ /**
17
+ * Current value (for controlled usage)
18
+ */
19
+ value?: string;
20
+ /**
21
+ * Whether the radio button is checked
22
+ */
23
+ checked?: boolean;
24
+ /**
25
+ * Whether the radio button is disabled
26
+ */
27
+ disabled?: boolean;
28
+ /**
29
+ * The name attribute for form grouping
30
+ */
31
+ name?: string;
32
+ /**
33
+ * HTML id attribute
34
+ */
35
+ id?: string;
36
+ /**
37
+ * onChange handler
38
+ */
39
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
40
+ /**
41
+ * onBlur handler
42
+ */
43
+ onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
44
+ /**
45
+ * onFocus handler
46
+ */
47
+ onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
48
+ }
49
+
50
+ export interface RadioButtonGroupProps {
51
+ /**
52
+ * The label/heading displayed above the radio button group
53
+ */
54
+ label?: string;
55
+ /**
56
+ * Helper or error text displayed below the group
57
+ */
58
+ helperText?: string;
59
+ /**
60
+ * Whether the field is required
61
+ */
62
+ required?: boolean;
63
+ /**
64
+ * Shows error state (red border on radios, helper text in error color)
65
+ */
66
+ error?: boolean;
67
+ /**
68
+ * Array of radio button options
69
+ */
70
+ options: Array<{
71
+ label: string;
72
+ value: string;
73
+ description?: string;
74
+ disabled?: boolean;
75
+ }>;
76
+ /**
77
+ * Selected value (for controlled usage)
78
+ */
79
+ value?: string;
80
+ /**
81
+ * Default selected value (for uncontrolled usage)
82
+ */
83
+ defaultValue?: string;
84
+ /**
85
+ * HTML id for the group (used for aria-describedby when helperText is present)
86
+ */
87
+ id?: string;
88
+ /**
89
+ * The name attribute for form grouping
90
+ */
91
+ name?: string;
92
+ /**
93
+ * Whether the group is disabled
94
+ */
95
+ disabled?: boolean;
96
+ /**
97
+ * Layout direction: 'horizontal' or 'vertical'
98
+ */
99
+ direction?: "horizontal" | "vertical";
100
+ /**
101
+ * Optional className applied to the group container that wraps all options.
102
+ */
103
+ groupClassName?: string;
104
+ /**
105
+ * Optional className applied to each option wrapper.
106
+ * Useful for grid layouts (e.g. Bootstrap `col-md-4`).
107
+ */
108
+ itemClassName?: string;
109
+ /**
110
+ * Size variant: sm, md, or lg
111
+ */
112
+ size?: RadioButtonSize;
113
+ /**
114
+ * onChange handler - receives the selected value
115
+ */
116
+ onChange?: (value: string) => void;
117
+ }