@altruist03/ui 0.1.0 → 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 (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,105 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import React, { useState } from 'react';
3
+ import { ToggleButton } from './ToggleButton';
4
+ import type { ToggleButtonOption, ToggleButtonProps } from './ToggleButton.types';
5
+
6
+ const options: ToggleButtonOption[] = [
7
+ { value: '1d', label: '1D' },
8
+ { value: '1w', label: '1W' },
9
+ { value: '1m', label: '1M' },
10
+ { value: '3m', label: '3M' },
11
+ { value: '6m', label: '6M' },
12
+ ];
13
+
14
+ const meta: Meta<typeof ToggleButton> = {
15
+ title: 'Design System/ToggleButton',
16
+ component: ToggleButton,
17
+ tags: [],
18
+ decorators: [
19
+ (Story) => (
20
+ <div style={{ padding: '24px', maxWidth: '420px' }}>
21
+ <Story />
22
+ </div>
23
+ ),
24
+ ],
25
+ args: {
26
+ options,
27
+ value: '1m',
28
+ size: 'sm',
29
+ disabled: false,
30
+ color: 'grey',
31
+ } as ToggleButtonProps,
32
+ argTypes: {
33
+ color: {
34
+ control: { type: 'radio' },
35
+ options: ['grey', 'white'],
36
+ },
37
+ value: {
38
+ control: { type: 'radio' },
39
+ options: options.map((opt) => opt.value),
40
+ },
41
+ disabled: {
42
+ control: { type: 'boolean' },
43
+ },
44
+ size: {
45
+ control: { type: 'radio' },
46
+ options: ['sm', 'md', 'lg'],
47
+ },
48
+ onChange: { action: 'changed' },
49
+ },
50
+ };
51
+
52
+ export default meta;
53
+
54
+ type Story = StoryObj<typeof ToggleButton>;
55
+
56
+ export const Playground: Story = {
57
+ render: function PlaygroundRender(args) {
58
+ const [value, setValue] = useState(args.value);
59
+ return (
60
+ <ToggleButton
61
+ {...args}
62
+ value={value}
63
+ onChange={(next) => {
64
+ setValue(next);
65
+ args.onChange?.(next);
66
+ }}
67
+ />
68
+ );
69
+ },
70
+ };
71
+
72
+ export const Showcase: Story = {
73
+ render: () => (
74
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
75
+ <ToggleButton options={options} value="1m" onChange={() => {}} />
76
+ <ToggleButton options={options} value="1w" onChange={() => {}} size="md" />
77
+ <ToggleButton options={options} value="6m" onChange={() => {}} size="lg" />
78
+ <ToggleButton options={options} value="1m" onChange={() => {}} disabled />
79
+ <ToggleButton
80
+ options={[
81
+ { value: '1d', label: '1D' },
82
+ { value: '1w', label: '1W', disabled: true },
83
+ { value: '1m', label: '1M' },
84
+ ]}
85
+ value="1m"
86
+ onChange={() => {}}
87
+ />
88
+
89
+ {/* Color: white */}
90
+ <ToggleButton options={options} value="1m" onChange={() => {}} color="white" />
91
+
92
+ {/* Pill variant */}
93
+ <ToggleButton
94
+ options={[
95
+ { value: 'view', label: 'View' },
96
+ { value: 'edit', label: 'Edit' },
97
+ ]}
98
+ value="view"
99
+ onChange={() => {}}
100
+ variant="pill"
101
+ size="md"
102
+ />
103
+ </div>
104
+ ),
105
+ };
@@ -0,0 +1,267 @@
1
+ import React, { useState } from 'react';
2
+
3
+ import { Button } from '../Button/Button';
4
+ import { getToggleButtonTokensFromResolved } from '../../tokens';
5
+ import type { ToggleButtonProps } from './ToggleButton.types';
6
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
7
+
8
+ function parsePx(value: string): number {
9
+ const num = parseInt(String(value).replace(/px$/i, ''), 10);
10
+ return Number.isFinite(num) ? num : 0;
11
+ }
12
+
13
+ export const ToggleButton = ({
14
+ options,
15
+ value,
16
+ onChange,
17
+ size = 'sm',
18
+ color = 'grey',
19
+ variant = 'default',
20
+ disabled = false,
21
+ getOptionDisabled,
22
+ style,
23
+ ...rest
24
+ }: ToggleButtonProps) => {
25
+ const resolvedTokens = useTokens();
26
+ const t = getToggleButtonTokensFromResolved(resolvedTokens);
27
+ const sizeTokens =
28
+ size === 'lg' ?
29
+ {
30
+ height: t.sizeLgHeight,
31
+ paddingX: t.sizeLgPaddingX,
32
+ paddingY: t.sizeLgPaddingY,
33
+ fontSize: t.sizeLgFontSize,
34
+ borderRadius: t.sizeLgBorderRadius,
35
+ borderWidth: t.sizeLgBorderWidth,
36
+ }
37
+ : size === 'md' ?
38
+ {
39
+ height: t.sizeMdHeight,
40
+ paddingX: t.sizeMdPaddingX,
41
+ paddingY: t.sizeMdPaddingY,
42
+ fontSize: t.sizeMdFontSize,
43
+ borderRadius: t.sizeMdBorderRadius,
44
+ borderWidth: t.sizeMdBorderWidth,
45
+ }
46
+ : {
47
+ height: t.sizeSmHeight,
48
+ paddingX: t.sizeSmPaddingX,
49
+ paddingY: t.sizeSmPaddingY,
50
+ fontSize: t.sizeSmFontSize,
51
+ borderRadius: t.sizeSmBorderRadius,
52
+ borderWidth: t.sizeSmBorderWidth,
53
+ };
54
+
55
+ if (variant === 'pill') {
56
+ return (
57
+ <PillToggleButton
58
+ options={options}
59
+ value={value}
60
+ onChange={onChange}
61
+ sizeTokens={sizeTokens}
62
+ tokens={t}
63
+ color={color}
64
+ disabled={disabled}
65
+ getOptionDisabled={getOptionDisabled}
66
+ style={style}
67
+ {...rest}
68
+ />
69
+ );
70
+ }
71
+
72
+ return (
73
+ <div
74
+ role="group"
75
+ aria-disabled={disabled}
76
+ style={{
77
+ display: 'inline-flex',
78
+ alignItems: 'center',
79
+ gap: t.gap,
80
+ ...style,
81
+ }}
82
+ {...rest}
83
+ >
84
+ {options.map((option, index) => {
85
+ const isSelected = option.value === value;
86
+ const isOptionDisabled =
87
+ disabled || option.disabled || getOptionDisabled?.(option) === true;
88
+ const inactiveBackground =
89
+ color === 'white' ? t.inactive.backgroundWhite : t.inactive.background;
90
+ const borderColor = isSelected ? t.active.border : t.inactive.border;
91
+ const fontWeight = isSelected ? t.activeFontWeight : t.fontWeight;
92
+ const isFirst = index === 0;
93
+ const isLast = index === options.length - 1;
94
+ const showBoderRadis = isFirst || isLast;
95
+ const borderRadius =
96
+ isFirst ? `${sizeTokens.borderRadius} 0 0 ${sizeTokens.borderRadius}`
97
+ : isLast ? `0 ${sizeTokens.borderRadius} ${sizeTokens.borderRadius} 0`
98
+ : '0';
99
+
100
+ return (
101
+ <Button
102
+ key={option.value}
103
+ type="button"
104
+ size={size}
105
+ variant={isSelected ? 'blue' : 'grey'}
106
+ disabled={isOptionDisabled}
107
+ showBoderRadis={showBoderRadis}
108
+ aria-pressed={isSelected}
109
+ aria-label={option.label}
110
+ onClick={() => {
111
+ if (!isOptionDisabled) onChange?.(option.value);
112
+ }}
113
+ style={{
114
+ backgroundColor: isSelected ? t.active.background : inactiveBackground,
115
+ color: isSelected ? t.active.color : t.inactive.color,
116
+ border: `${sizeTokens.borderWidth} solid ${borderColor}`,
117
+ height: sizeTokens.height,
118
+ minHeight: sizeTokens.height,
119
+ maxHeight: sizeTokens.height,
120
+ padding: `${sizeTokens.paddingY} ${sizeTokens.paddingX}`,
121
+ borderRadius,
122
+ fontFamily: t.fontFamily,
123
+ fontSize: sizeTokens.fontSize,
124
+ lineHeight: t.lineHeight,
125
+ fontWeight,
126
+ boxSizing: 'border-box',
127
+ }}
128
+ >
129
+ {option.label}
130
+ </Button>
131
+ );
132
+ })}
133
+ </div>
134
+ );
135
+ };
136
+
137
+ type PillToggleButtonProps = Pick<
138
+ ToggleButtonProps,
139
+ 'options' | 'value' | 'onChange' | 'color' | 'disabled' | 'getOptionDisabled' | 'style'
140
+ > &
141
+ Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {
142
+ sizeTokens: {
143
+ height: string;
144
+ paddingX: string;
145
+ paddingY: string;
146
+ fontSize: string;
147
+ borderRadius: string;
148
+ borderWidth: string;
149
+ };
150
+ tokens: ReturnType<typeof getToggleButtonTokensFromResolved>;
151
+ };
152
+
153
+ function PillToggleButton({
154
+ options,
155
+ value,
156
+ onChange,
157
+ sizeTokens,
158
+ tokens: t,
159
+ color = 'grey',
160
+ disabled = false,
161
+ getOptionDisabled,
162
+ style,
163
+ ...rest
164
+ }: PillToggleButtonProps) {
165
+ const [hoveredValue, setHoveredValue] = useState<string | null>(null);
166
+ const selectedIndex = Math.max(
167
+ 0,
168
+ options.findIndex((option) => option.value === value)
169
+ );
170
+ const trackPadding = 3;
171
+ const heightPx = parsePx(sizeTokens.height);
172
+ const trackRadius = heightPx > 0 ? `${heightPx / 2}px` : sizeTokens.borderRadius;
173
+ const inactiveTrackBg = color === 'white' ? '#F9FAFB' : (t.inactive.background ?? '#F3F4F6');
174
+ const segmentCount = Math.max(options.length, 1);
175
+
176
+ return (
177
+ <div
178
+ role="group"
179
+ aria-disabled={disabled}
180
+ style={{
181
+ position: 'relative',
182
+ display: 'inline-flex',
183
+ alignItems: 'stretch',
184
+ boxSizing: 'border-box',
185
+ height: sizeTokens.height,
186
+ minHeight: sizeTokens.height,
187
+ padding: trackPadding,
188
+ borderRadius: trackRadius,
189
+ border: `${sizeTokens.borderWidth} solid ${t.inactive.border}`,
190
+ backgroundColor: inactiveTrackBg,
191
+ boxShadow: 'inset 0 1px 2px rgba(0, 0, 0, 0.05)',
192
+ ...style,
193
+ }}
194
+ {...rest}
195
+ >
196
+ <span
197
+ aria-hidden
198
+ style={{
199
+ position: 'absolute',
200
+ top: trackPadding,
201
+ left: trackPadding,
202
+ width: `calc((100% - ${trackPadding * 2}px) / ${segmentCount})`,
203
+ height: `calc(100% - ${trackPadding * 2}px)`,
204
+ borderRadius: `calc(${trackRadius} - ${trackPadding}px)`,
205
+ backgroundColor: t.active.background,
206
+ boxShadow:
207
+ '0 1px 3px rgba(0, 0, 0, 0.14), 0 1px 2px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.12)',
208
+ transform: `translateX(calc(${selectedIndex} * 100%))`,
209
+ transition: 'transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.2s ease',
210
+ pointerEvents: 'none',
211
+ zIndex: 0,
212
+ }}
213
+ />
214
+
215
+ {options.map((option) => {
216
+ const isSelected = option.value === value;
217
+ const isHovered = hoveredValue === option.value && !isSelected;
218
+ const isOptionDisabled =
219
+ disabled || option.disabled || getOptionDisabled?.(option) === true;
220
+
221
+ return (
222
+ <button
223
+ key={option.value}
224
+ type="button"
225
+ aria-pressed={isSelected}
226
+ aria-label={option.label}
227
+ disabled={isOptionDisabled}
228
+ onClick={() => {
229
+ if (!isOptionDisabled) onChange?.(option.value);
230
+ }}
231
+ onMouseEnter={() => {
232
+ if (!isOptionDisabled) setHoveredValue(option.value);
233
+ }}
234
+ onMouseLeave={() => setHoveredValue(null)}
235
+ style={{
236
+ position: 'relative',
237
+ zIndex: 1,
238
+ flex: '1 1 0',
239
+ minWidth: 0,
240
+ display: 'inline-flex',
241
+ alignItems: 'center',
242
+ justifyContent: 'center',
243
+ height: '100%',
244
+ padding: `0 ${sizeTokens.paddingX}`,
245
+ border: 'none',
246
+ borderRadius: `calc(${trackRadius} - ${trackPadding}px)`,
247
+ backgroundColor: isHovered ? 'rgba(0, 0, 0, 0.04)' : 'transparent',
248
+ color: isSelected ? t.active.color : t.inactive.color,
249
+ fontFamily: t.fontFamily,
250
+ fontSize: sizeTokens.fontSize,
251
+ lineHeight: t.lineHeight,
252
+ fontWeight: isSelected ? t.activeFontWeight : t.fontWeight,
253
+ cursor: isOptionDisabled ? 'not-allowed' : 'pointer',
254
+ opacity: isOptionDisabled ? 0.5 : 1,
255
+ transition: 'color 0.2s ease, background-color 0.15s ease, opacity 0.15s ease',
256
+ whiteSpace: 'nowrap',
257
+ userSelect: 'none',
258
+ WebkitTapHighlightColor: 'transparent',
259
+ }}
260
+ >
261
+ {option.label}
262
+ </button>
263
+ );
264
+ })}
265
+ </div>
266
+ );
267
+ }
@@ -0,0 +1,19 @@
1
+ export interface ToggleButtonOption {
2
+ value: string;
3
+ label: string;
4
+ disabled?: boolean;
5
+ }
6
+
7
+ export interface ToggleButtonProps extends Omit<
8
+ React.HTMLAttributes<HTMLDivElement>,
9
+ 'onChange' | 'children'
10
+ > {
11
+ options: ToggleButtonOption[];
12
+ value: string;
13
+ onChange?: (value: string) => void;
14
+ size?: 'sm' | 'md' | 'lg';
15
+ color?: 'grey' | 'white';
16
+ variant?: 'default' | 'pill';
17
+ disabled?: boolean;
18
+ getOptionDisabled?: (option: ToggleButtonOption) => boolean;
19
+ }
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+
4
+ const UploadDropzone = ({ fileInputRef, handleFileSelect, uploadedFile, uploadFileError }) => {
5
+ const { t } = useTranslation();
6
+ return (
7
+ <div className="w-full">
8
+ <div
9
+ className={`w-full border-2 border-dashed rounded-xl px-6 py-8 text-center cursor-pointer transition-all duration-200 flex flex-col items-center justify-center bg-[#fcfff7] border-[#d1d5db] ${uploadFileError ? 'border-red-400 bg-[#fcfff7]' : ''}`}
10
+ onClick={() => fileInputRef.current?.click()}
11
+ onDrop={(e) => {
12
+ e.preventDefault();
13
+ e.stopPropagation();
14
+ e.currentTarget.classList.remove('border-[#52760a]', 'bg-[#fbffe9]');
15
+
16
+ const droppedFiles = Array.from(e.dataTransfer.files || []);
17
+ if (droppedFiles.length > 0) {
18
+ handleFileSelect(droppedFiles[0]);
19
+ }
20
+ }}
21
+ onDragOver={(e) => {
22
+ e.preventDefault();
23
+ e.stopPropagation();
24
+ e.currentTarget.classList.add('border-[#52760a]', 'bg-[#fbffe9]');
25
+ }}
26
+ onDragLeave={(e) => {
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+ e.currentTarget.classList.remove('border-[#52760a]', 'bg-[#fbffe9]');
30
+ }}
31
+ >
32
+ <div className="w-12 h-12 rounded-full bg-[#e7f3cf] flex items-center justify-center mb-4">
33
+ <i className="fas fa-cloud-upload-alt text-[#52760a] text-xl" />
34
+ </div>
35
+
36
+ <p className="text-sm font-medium text-[#111827] mb-1">
37
+ {t('Click to upload or drag and drop')}
38
+ </p>
39
+ <p className="text-xs text-[#6b7280] mb-5">XLSX, CSV {t('up to')} 10MB</p>
40
+
41
+ <button
42
+ type="button"
43
+ className="px-6 py-2 rounded-lg bg-[#52760a] text-white text-sm font-medium shadow-sm hover:bg-[#476507] transition-colors duration-150"
44
+ onClick={(e) => {
45
+ e.stopPropagation();
46
+ fileInputRef.current?.click();
47
+ }}
48
+ >
49
+ {t('Browse Files')}
50
+ </button>
51
+
52
+ {uploadedFile && (
53
+ <div className="mt-4 text-xs text-[#4b5563]">
54
+ <span className="font-medium">{t('Selected file')}:</span>{' '}
55
+ <span>{uploadedFile.name}</span>
56
+ </div>
57
+ )}
58
+
59
+ <input
60
+ ref={fileInputRef}
61
+ type="file"
62
+ accept=".xlsx,.csv"
63
+ className="hidden"
64
+ onChange={(e) => {
65
+ const selectedFile = e.target.files?.[0];
66
+ if (selectedFile) {
67
+ handleFileSelect(selectedFile);
68
+ }
69
+ e.target.value = '';
70
+ }}
71
+ onClick={(e) => e.stopPropagation()}
72
+ />
73
+ </div>
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default UploadDropzone;
@@ -0,0 +1,94 @@
1
+ import React, { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { UploadDropzone } from "./UploadDropzone";
4
+ import type { UploadDropzoneProps } from "./UploadDropzone.types";
5
+
6
+ /** Wrapper to manage value/onChange for controlled UploadDropzone in stories */
7
+ const ControlledUploadDropzone = (props: Omit<UploadDropzoneProps, "value" | "onChange">) => {
8
+ const [value, setValue] = useState<File | File[] | null>(null);
9
+ return (
10
+ <UploadDropzone
11
+ {...props}
12
+ value={value}
13
+ onChange={setValue}
14
+ />
15
+ );
16
+ };
17
+
18
+ const meta: Meta<typeof UploadDropzone> = {
19
+ title: "Design System/UploadDropzone",
20
+ component: ControlledUploadDropzone,
21
+ tags: [],
22
+ decorators: [
23
+ (Story) => (
24
+ <Story />
25
+ ),
26
+ ],
27
+ args: {
28
+ title: "Upload File",
29
+ instructionText: "Drag & drop or click to upload",
30
+ hintText: "Max file size - 5MB Allowed file extension - txt,csv,xlsx",
31
+ buttonText: "Browse File",
32
+ fullWidth: true,
33
+ } as UploadDropzoneProps,
34
+ argTypes: {
35
+ title: { control: { type: "text" } },
36
+ instructionText: { control: { type: "text" } },
37
+ hintText: { control: { type: "text" } },
38
+ buttonText: { control: { type: "text" } },
39
+ accept: { control: { type: "text" } },
40
+ multiple: { control: { type: "boolean" } },
41
+ disabled: { control: { type: "boolean" } },
42
+ },
43
+ };
44
+
45
+ export default meta;
46
+
47
+ type Story = StoryObj<typeof UploadDropzone>;
48
+
49
+ export const Playground: Story = {
50
+ args: {
51
+ title: "Upload File",
52
+ instructionText: "Drag & drop or click to upload",
53
+ hintText: "Max file size - 5MB Allowed file extension - txt,csv,xlsx",
54
+ buttonText: "Browse File",
55
+ },
56
+ };
57
+
58
+ export const Showcase: Story = {
59
+ render: () => (
60
+ <>
61
+ <ControlledUploadDropzone
62
+ title="Upload File"
63
+ instructionText="Drag & drop or click to upload"
64
+ hintText="Max file size - 5MB Allowed file extension - txt,csv,xlsx"
65
+ buttonText="Browse File"
66
+ accept=".txt,.csv,.xlsx"
67
+ />
68
+
69
+ <ControlledUploadDropzone
70
+ title="Upload Document"
71
+ instructionText="Drop your file here or click to browse"
72
+ hintText="PDF, DOC, DOCX up to 10MB"
73
+ buttonText="Choose File"
74
+ accept=".pdf,.doc,.docx"
75
+ />
76
+
77
+ <ControlledUploadDropzone
78
+ title="Upload Multiple Files"
79
+ instructionText="Drag & drop or click to upload"
80
+ hintText="Max file size - 5MB per file"
81
+ buttonText="Browse Files"
82
+ multiple
83
+ />
84
+
85
+ <ControlledUploadDropzone
86
+ title="Upload File"
87
+ instructionText="Drag & drop or click to upload"
88
+ hintText="Max file size - 5MB"
89
+ buttonText="Browse File"
90
+ disabled
91
+ />
92
+ </>
93
+ ),
94
+ };