@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,279 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+
3
+ import { getSubTabsTokensFromResolved } from '../../tokens';
4
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
5
+ import type { SubTabsColor, SubTabsProps, SubTabsVariant } from './SubTabs.types';
6
+
7
+ function toColor(value: string | undefined, fallback: string): string {
8
+ if (!value || typeof value !== 'string') return fallback;
9
+ const s = value.trim();
10
+ if (s.includes('{')) return fallback;
11
+ if (s.startsWith('#') || s.startsWith('rgb') || s.startsWith('hsl')) return s;
12
+ if (/^[0-9A-Fa-f]{6}$/.test(s)) return `#${s}`;
13
+ return fallback;
14
+ }
15
+
16
+ export function normalizeSubTabValue(value: string | number | null | undefined): string {
17
+ if (value === null || value === undefined) return '';
18
+ return String(value);
19
+ }
20
+
21
+ interface ColorPalette {
22
+ accent: string;
23
+ accentLight: string;
24
+ activeText: string;
25
+ inactiveText: string;
26
+ activeIconBackground: string;
27
+ inactiveIconBackground: string;
28
+ }
29
+
30
+ function getColorPalette(
31
+ colors: Record<string, ColorPalette> | undefined,
32
+ color: SubTabsColor
33
+ ): ColorPalette {
34
+ const fallback: ColorPalette = {
35
+ accent: '#00545e',
36
+ accentLight: '#F3F5F6',
37
+ activeText: '#00545e',
38
+ inactiveText: '#6B7280',
39
+ activeIconBackground: '#00545e',
40
+ inactiveIconBackground: '#EEF1F2',
41
+ };
42
+
43
+ const greenFallback: ColorPalette = {
44
+ accent: '#52760A',
45
+ accentLight: '#F3F6EC',
46
+ activeText: '#52760A',
47
+ inactiveText: '#6B7280',
48
+ activeIconBackground: '#52760A',
49
+ inactiveIconBackground: '#FFFFFF',
50
+ };
51
+
52
+ const palette = colors?.[color];
53
+ const base = color === 'green' ? greenFallback : fallback;
54
+
55
+ return {
56
+ accent: toColor(palette?.accent, base.accent),
57
+ accentLight: toColor(palette?.accentLight, base.accentLight),
58
+ activeText: toColor(palette?.activeText, base.activeText),
59
+ inactiveText: toColor(palette?.inactiveText, base.inactiveText),
60
+ activeIconBackground: toColor(palette?.activeIconBackground, base.activeIconBackground),
61
+ inactiveIconBackground: toColor(palette?.inactiveIconBackground, base.inactiveIconBackground),
62
+ };
63
+ }
64
+
65
+ function getTabButtonStyles({
66
+ variant,
67
+ isSelected,
68
+ palette,
69
+ sizeTokens,
70
+ isButtonDisabled,
71
+ }: {
72
+ variant: SubTabsVariant;
73
+ isSelected: boolean;
74
+ palette: ColorPalette;
75
+ sizeTokens: {
76
+ height: string;
77
+ padding: string;
78
+ fontSize: string;
79
+ borderRadius: string;
80
+ fontWeight: string;
81
+ activeFontWeight: string;
82
+ };
83
+ isButtonDisabled: boolean;
84
+ }) {
85
+ const base = {
86
+ display: 'inline-flex',
87
+ alignItems: 'center',
88
+ gap: '8px',
89
+ height: sizeTokens.height,
90
+ minHeight: sizeTokens.height,
91
+ padding: sizeTokens.padding,
92
+ fontSize: sizeTokens.fontSize,
93
+ fontWeight: isSelected ? sizeTokens.activeFontWeight : sizeTokens.fontWeight,
94
+ whiteSpace: 'nowrap' as const,
95
+ cursor: isButtonDisabled ? 'not-allowed' : 'pointer',
96
+ opacity: isButtonDisabled ? 0.5 : 1,
97
+ transition: 'all 0.2s ease',
98
+ border: '1px solid transparent',
99
+ background: 'transparent',
100
+ color: isSelected ? palette.activeText : palette.inactiveText,
101
+ flexShrink: 0,
102
+ };
103
+
104
+ if (variant === 'pill') {
105
+ return {
106
+ ...base,
107
+ borderRadius: sizeTokens.borderRadius,
108
+ backgroundColor: isSelected ? palette.accent : '#FFFFFF',
109
+ borderColor: isSelected ? palette.accent : 'rgba(82, 118, 10, 0.18)',
110
+ color: isSelected ? '#FFFFFF' : palette.inactiveText,
111
+ boxShadow: isSelected ? '0 2px 6px rgba(82, 118, 10, 0.28)' : '0 1px 2px rgba(0,0,0,0.06)',
112
+ };
113
+ }
114
+
115
+ if (variant === 'underline') {
116
+ return {
117
+ ...base,
118
+ borderRadius: '0',
119
+ borderBottom: `2px solid ${isSelected ? palette.accent : 'transparent'}`,
120
+ marginBottom: '-1px',
121
+ backgroundColor: 'transparent',
122
+ color: isSelected ? palette.activeText : palette.inactiveText,
123
+ };
124
+ }
125
+
126
+ return {
127
+ ...base,
128
+ borderRadius: sizeTokens.borderRadius,
129
+ backgroundColor: isSelected ? palette.accent : '#FFFFFF',
130
+ borderColor: isSelected ? palette.accent : '#D4DDE1',
131
+ color: isSelected ? '#FFFFFF' : palette.inactiveText,
132
+ boxShadow: isSelected ? '0 1px 2px rgba(16, 24, 40, 0.06)' : 'none',
133
+ };
134
+ }
135
+
136
+ function SubTabIcon({
137
+ icon,
138
+ isSelected,
139
+ palette,
140
+ iconSize,
141
+ variant,
142
+ }: {
143
+ icon: ReactNode;
144
+ isSelected: boolean;
145
+ palette: ColorPalette;
146
+ iconSize: string;
147
+ variant: SubTabsVariant;
148
+ }) {
149
+ const isPill = variant === 'pill';
150
+ const bgColor =
151
+ isPill && isSelected ? 'rgba(255, 255, 255, 0.22)'
152
+ : isSelected ? palette.activeIconBackground
153
+ : palette.inactiveIconBackground;
154
+ const iconColor = isSelected ? '#FFFFFF' : palette.accent;
155
+
156
+ return (
157
+ <span
158
+ aria-hidden
159
+ style={{
160
+ display: 'inline-flex',
161
+ alignItems: 'center',
162
+ justifyContent: 'center',
163
+ width: iconSize,
164
+ height: iconSize,
165
+ borderRadius: '6px',
166
+ flexShrink: 0,
167
+ backgroundColor: bgColor,
168
+ color: iconColor,
169
+ transition: 'all 0.2s ease',
170
+ }}
171
+ >
172
+ {icon}
173
+ </span>
174
+ );
175
+ }
176
+
177
+ export const SubTabs = ({
178
+ options,
179
+ value,
180
+ onChange,
181
+ variant = 'pill',
182
+ color = 'primary',
183
+ size = 'md',
184
+ disabled = false,
185
+ getOptionDisabled,
186
+ ariaLabel = 'Sub tabs',
187
+ style,
188
+ ...rest
189
+ }: SubTabsProps) => {
190
+ const resolvedTokens = useTokens();
191
+ const { tokens: t, sizes } = getSubTabsTokensFromResolved(resolvedTokens);
192
+ const sizeTokens = sizes[size];
193
+ const palette = getColorPalette(
194
+ t.colors as unknown as Record<string, ColorPalette> | undefined,
195
+ color
196
+ );
197
+ const selectedValue = normalizeSubTabValue(value);
198
+
199
+ const trackStyle: CSSProperties =
200
+ variant === 'pill' ?
201
+ {
202
+ display: 'flex',
203
+ alignItems: 'center',
204
+ gap: t.gap,
205
+ width: '100%',
206
+ overflowX: 'auto',
207
+ padding: t.trackPadding,
208
+ borderRadius: t.trackBorderRadius,
209
+ backgroundColor: palette.accentLight,
210
+ border: `1px solid ${toColor(t.trackBorder, '#E5E7EB')}`,
211
+ boxShadow: 'inset 0 1px 2px rgba(16, 24, 40, 0.04)',
212
+ }
213
+ : variant === 'underline' ?
214
+ {
215
+ display: 'inline-flex',
216
+ alignItems: 'flex-end',
217
+ gap: t.gap,
218
+ maxWidth: '100%',
219
+ overflowX: 'auto',
220
+ borderBottom: `1px solid ${toColor(t.trackBorder, '#E5E7EB')}`,
221
+ }
222
+ : {
223
+ display: 'inline-flex',
224
+ alignItems: 'center',
225
+ gap: t.gap,
226
+ maxWidth: '100%',
227
+ overflowX: 'auto',
228
+ flexWrap: 'wrap',
229
+ };
230
+
231
+ return (
232
+ <div
233
+ role="tablist"
234
+ aria-label={ariaLabel}
235
+ aria-disabled={disabled}
236
+ style={{ ...trackStyle, ...style }}
237
+ {...rest}
238
+ >
239
+ {options.map((option) => {
240
+ const optionValue = normalizeSubTabValue(option.value);
241
+ const isSelected = optionValue === selectedValue;
242
+ const isOptionDisabled = option.disabled === true || getOptionDisabled?.(option) === true;
243
+ const isButtonDisabled = disabled || isOptionDisabled;
244
+
245
+ return (
246
+ <button
247
+ key={optionValue || option.label}
248
+ type="button"
249
+ role="tab"
250
+ aria-selected={isSelected}
251
+ aria-disabled={isOptionDisabled}
252
+ disabled={isButtonDisabled}
253
+ onClick={() => {
254
+ if (!isButtonDisabled) onChange?.(option.value);
255
+ }}
256
+ style={getTabButtonStyles({
257
+ variant,
258
+ isSelected,
259
+ palette,
260
+ sizeTokens,
261
+ isButtonDisabled,
262
+ })}
263
+ >
264
+ {option.icon ?
265
+ <SubTabIcon
266
+ icon={option.icon}
267
+ isSelected={isSelected}
268
+ palette={palette}
269
+ iconSize={sizeTokens.iconSize}
270
+ variant={variant}
271
+ />
272
+ : null}
273
+ <span>{option.label}</span>
274
+ </button>
275
+ );
276
+ })}
277
+ </div>
278
+ );
279
+ };
@@ -0,0 +1,44 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface SubTabsOption {
4
+ /** Value when this sub-tab is selected */
5
+ value: string | number;
6
+ /** Label shown for the sub-tab */
7
+ label: string;
8
+ /** Optional icon shown before the label */
9
+ icon?: ReactNode;
10
+ /** Display / sort order from config */
11
+ sequence?: number;
12
+ /** Disable this sub-tab only */
13
+ disabled?: boolean;
14
+ }
15
+
16
+ export type SubTabsVariant = 'pill' | 'underline' | 'outline';
17
+
18
+ export type SubTabsColor = 'primary' | 'green';
19
+
20
+ export type SubTabsSize = 'sm' | 'md';
21
+
22
+ export interface SubTabsProps extends Omit<
23
+ React.HTMLAttributes<HTMLDivElement>,
24
+ 'onChange' | 'children'
25
+ > {
26
+ /** List of sub-tab options; one can be selected */
27
+ options: SubTabsOption[];
28
+ /** Currently selected value (must match one option's value) */
29
+ value: string | number;
30
+ /** Called when user selects a different sub-tab */
31
+ onChange?: (value: string | number) => void;
32
+ /** Visual style variant */
33
+ variant?: SubTabsVariant;
34
+ /** Accent color palette */
35
+ color?: SubTabsColor;
36
+ /** Size of the sub-tabs */
37
+ size?: SubTabsSize;
38
+ /** Disable all sub-tabs */
39
+ disabled?: boolean;
40
+ /** Callback to disable a particular option */
41
+ getOptionDisabled?: (option: SubTabsOption) => boolean;
42
+ /** Accessible label for the tab list */
43
+ ariaLabel?: string;
44
+ }
@@ -0,0 +1,100 @@
1
+ import React from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { Icon } from '@mui/material';
4
+ import { SummaryCard, SummaryCardGrid } from './SummaryCard';
5
+ import type { SummaryCardProps } from './SummaryCard.types';
6
+
7
+ const meta: Meta<typeof SummaryCard> = {
8
+ title: 'Design System/SummaryCard',
9
+ component: SummaryCard,
10
+ decorators: [
11
+ (Story) => (
12
+ <div style={{ padding: 24, backgroundColor: '#F4FAF5', maxWidth: 1200 }}>
13
+ <Story />
14
+ </div>
15
+ ),
16
+ ],
17
+ args: {
18
+ title: 'Invoiced (August)',
19
+ value: 'AED 452,000',
20
+ subtitle: '+12% vs July',
21
+ accentColor: '#2563eb',
22
+ iconBackgroundColor: '#dbeafe',
23
+ icon: <Icon sx={{ fontSize: 20 }}>receipt_long</Icon>,
24
+ } satisfies SummaryCardProps,
25
+ };
26
+
27
+ export default meta;
28
+
29
+ type Story = StoryObj<typeof SummaryCard>;
30
+
31
+ export const Playground: Story = {};
32
+
33
+ export const ThreeCardsFullWidth: Story = {
34
+ render: () => (
35
+ <SummaryCardGrid cardCount={3}>
36
+ <SummaryCard
37
+ title="Invoiced (August)"
38
+ value="AED 452,000"
39
+ subtitle="+12% vs July"
40
+ accentColor="#2563eb"
41
+ iconBackgroundColor="#dbeafe"
42
+ icon={<Icon sx={{ fontSize: 20 }}>receipt_long</Icon>}
43
+ />
44
+ <SummaryCard
45
+ title="Accounts Receivable"
46
+ value="AED 185,400"
47
+ subtitle="Total Outstanding"
48
+ accentColor="#ca8a04"
49
+ iconBackgroundColor="#fef3c7"
50
+ icon={<Icon sx={{ fontSize: 20 }}>account_balance_wallet</Icon>}
51
+ />
52
+ <SummaryCard
53
+ title="Overdue Receivable"
54
+ value="AED 42,300"
55
+ subtitle="Requires Attention"
56
+ accentColor="#dc2626"
57
+ iconBackgroundColor="#fee2e2"
58
+ icon={<Icon sx={{ fontSize: 20 }}>warning_amber</Icon>}
59
+ />
60
+ </SummaryCardGrid>
61
+ ),
62
+ };
63
+
64
+ export const TwoCardsFullWidth: Story = {
65
+ render: () => (
66
+ <SummaryCardGrid cardCount={2}>
67
+ <SummaryCard
68
+ title="Invoiced"
69
+ value="AED 452,000"
70
+ subtitle="+12% vs July"
71
+ accentColor="#52760A"
72
+ icon={<Icon sx={{ fontSize: 20 }}>receipt_long</Icon>}
73
+ />
74
+ <SummaryCard
75
+ title="Accounts Receivable"
76
+ value="AED 185,400"
77
+ subtitle="Total Outstanding"
78
+ accentColor="#ca8a04"
79
+ icon={<Icon sx={{ fontSize: 20 }}>account_balance_wallet</Icon>}
80
+ />
81
+ </SummaryCardGrid>
82
+ ),
83
+ };
84
+
85
+ export const FourCardsFullWidth: Story = {
86
+ render: () => (
87
+ <SummaryCardGrid cardCount={4}>
88
+ {['Invoiced', 'Receivable', 'Overdue', 'Collected'].map((label, index) => (
89
+ <SummaryCard
90
+ key={label}
91
+ title={label}
92
+ value={`AED ${(index + 1) * 100000}`}
93
+ subtitle="Sample subtitle"
94
+ accentColor="#52760A"
95
+ icon={<Icon sx={{ fontSize: 20 }}>receipt_long</Icon>}
96
+ />
97
+ ))}
98
+ </SummaryCardGrid>
99
+ ),
100
+ };
@@ -0,0 +1,141 @@
1
+ import type { CSSProperties } from 'react';
2
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
3
+ import type { SummaryCardProps, SummaryCardGridProps } from './SummaryCard.types';
4
+
5
+ function defaultAriaLabel(title: string, value: string | number, subtitle?: string) {
6
+ const valueText = typeof value === 'number' ? String(value) : value;
7
+ return subtitle ? `${title}: ${valueText}. ${subtitle}` : `${title}: ${valueText}`;
8
+ }
9
+
10
+ export const SummaryCard = ({
11
+ title,
12
+ value,
13
+ subtitle,
14
+ accentColor = '#52760A',
15
+ valueColor = '#111827',
16
+ iconBackgroundColor,
17
+ icon,
18
+ ariaLabel,
19
+ className,
20
+ style,
21
+ }: SummaryCardProps) => {
22
+ const resolvedTokens = useTokens();
23
+ const resolvedIconBg = iconBackgroundColor ?? `color-mix(in srgb, ${accentColor} 18%, #ffffff)`;
24
+ const bodyFontFamily =
25
+ resolvedTokens.semantic?.typography?.body?.medium?.fontFamily ??
26
+ resolvedTokens.semantic?.typography?.label?.md?.fontFamily ??
27
+ 'Inter, sans-serif';
28
+ const titleColor = resolvedTokens.semantic?.color?.text?.muted ?? '#5b6471';
29
+ const cardBorderColor = resolvedTokens.semantic?.color?.secondary?.default ?? '#d8e0eb';
30
+ const cardRadius = resolvedTokens.foundation?.radius?.lg ?? '12px';
31
+ const cardBackground = resolvedTokens.semantic?.color?.bg?.primary ?? '#ffffff';
32
+
33
+ return (
34
+ <article
35
+ className={className}
36
+ role="group"
37
+ aria-label={ariaLabel ?? defaultAriaLabel(title, value, subtitle)}
38
+ style={{
39
+ boxSizing: 'border-box',
40
+ background: cardBackground,
41
+ border: `1px solid ${cardBorderColor}`,
42
+ borderRadius: cardRadius,
43
+ minHeight: '88px',
44
+ padding: '10px 12px',
45
+ display: 'flex',
46
+ alignItems: 'center',
47
+ gap: '12px',
48
+ width: '100%',
49
+ ...style,
50
+ }}
51
+ >
52
+ {icon ?
53
+ <div
54
+ style={{
55
+ width: '44px',
56
+ height: '44px',
57
+ borderRadius: '10px',
58
+ display: 'flex',
59
+ alignItems: 'center',
60
+ justifyContent: 'center',
61
+ flexShrink: 0,
62
+ backgroundColor: resolvedIconBg,
63
+ color: accentColor,
64
+ }}
65
+ >
66
+ {icon}
67
+ </div>
68
+ : null}
69
+ <div style={{ minWidth: 0, flex: 1, fontFamily: bodyFontFamily }}>
70
+ <p
71
+ style={{
72
+ margin: 0,
73
+ color: titleColor,
74
+ fontSize: '13px',
75
+ lineHeight: '16px',
76
+ fontWeight: 600,
77
+ width: 'auto',
78
+ maxWidth: '100%',
79
+ whiteSpace: 'normal',
80
+ overflow: 'visible',
81
+ textOverflow: 'unset',
82
+ wordBreak: 'break-word',
83
+ }}
84
+ >
85
+ {title}
86
+ </p>
87
+ <h3
88
+ className="text-[18px] xl:text-[22px] leading-[1.3]"
89
+ style={{
90
+ margin: '2px 0px 2px 0px',
91
+ color: valueColor,
92
+ fontWeight: 700,
93
+ }}
94
+ >
95
+ {value}
96
+ </h3>
97
+ {subtitle ?
98
+ <p
99
+ style={{
100
+ margin: 0,
101
+ fontSize: '12px',
102
+ lineHeight: '16px',
103
+ fontWeight: 500,
104
+ width: 'auto',
105
+ maxWidth: '100%',
106
+ whiteSpace: 'normal',
107
+ overflow: 'visible',
108
+ textOverflow: 'unset',
109
+ wordBreak: 'break-word',
110
+ color: accentColor,
111
+ }}
112
+ >
113
+ {subtitle}
114
+ </p>
115
+ : null}
116
+ </div>
117
+ </article>
118
+ );
119
+ };
120
+
121
+ export const SummaryCardGrid = ({
122
+ children,
123
+ cardCount = 1,
124
+ className,
125
+ style,
126
+ }: SummaryCardGridProps) => {
127
+ const columns = Math.min(Math.max(cardCount, 1), 4);
128
+ const gridStyle: CSSProperties = {
129
+ display: 'grid',
130
+ gap: '12px',
131
+ width: '100%',
132
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
133
+ ...style,
134
+ };
135
+
136
+ return (
137
+ <div className={className} style={gridStyle}>
138
+ {children}
139
+ </div>
140
+ );
141
+ };
@@ -0,0 +1,22 @@
1
+ import type { CSSProperties, ReactNode } from 'react';
2
+
3
+ export interface SummaryCardProps {
4
+ title: string;
5
+ value: string | number;
6
+ subtitle?: string;
7
+ accentColor?: string;
8
+ valueColor?: string;
9
+ iconBackgroundColor?: string;
10
+ icon?: ReactNode;
11
+ ariaLabel?: string;
12
+ className?: string;
13
+ style?: CSSProperties;
14
+ }
15
+
16
+ export interface SummaryCardGridProps {
17
+ children: ReactNode;
18
+ /** Number of cards in the row (used to stretch 2–4 cards across full width). */
19
+ cardCount?: number;
20
+ className?: string;
21
+ style?: CSSProperties;
22
+ }
@@ -0,0 +1,59 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import React, { useState } from "react";
3
+ import { Switch } from "./Switch";
4
+ import type { SwitchProps } from "./Switch.types";
5
+
6
+ const meta: Meta<typeof Switch> = {
7
+ title: "Design System/Switch",
8
+ component: Switch,
9
+ tags: [],
10
+ decorators: [
11
+ (Story) => (
12
+ <div style={{ padding: "24px", maxWidth: "420px" }}>
13
+ <Story />
14
+ </div>
15
+ ),
16
+ ],
17
+ args: {
18
+ checked: false,
19
+ disabled: false,
20
+ size: "md",
21
+ } as SwitchProps,
22
+ argTypes: {
23
+ checked: { control: { type: "boolean" } },
24
+ disabled: { control: { type: "boolean" } },
25
+ size: { control: { type: "radio" }, options: ["sm", "md", "lg"] },
26
+ onChange: { action: "changed" },
27
+ },
28
+ };
29
+
30
+ export default meta;
31
+
32
+ type Story = StoryObj<typeof Switch>;
33
+
34
+ export const Playground: Story = {
35
+ render: function PlaygroundRender(args) {
36
+ const [checked, setChecked] = useState(!!args.checked);
37
+ return (
38
+ <Switch
39
+ {...args}
40
+ checked={checked}
41
+ onChange={(next) => {
42
+ setChecked(next);
43
+ args.onChange?.(next);
44
+ }}
45
+ />
46
+ );
47
+ },
48
+ };
49
+
50
+ export const Showcase: Story = {
51
+ render: () => (
52
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
53
+ <Switch checked={false} onChange={() => {}} />
54
+ <Switch checked onChange={() => {}} />
55
+ <Switch checked={false} disabled />
56
+ <Switch checked disabled />
57
+ </div>
58
+ ),
59
+ };