@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,399 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
4
+ import { getModalTokensFromResolved } from '../../tokens/modal';
5
+ import { CloseButton } from '../CloseButton/CloseButton';
6
+ import { Button } from '../Button/Button';
7
+ import type { ModalProps } from './types';
8
+ import { lockScroll, unlockScroll } from '../../utils/scrollLock';
9
+
10
+ const DEFAULT_ALERT_ICON_COLOR = '#EF4444';
11
+ const DEFAULT_ALERT_ICON_BACKGROUND = '#FEE2E2';
12
+ const ALERT_TITLE_COLOR = '#111827';
13
+ const ALERT_DESC_COLOR = '#6B7280';
14
+ const LAPTOP_MAX_WIDTH = 1024;
15
+
16
+ function useIsCompactScreen() {
17
+ const [isCompact, setIsCompact] = useState(
18
+ () => typeof window !== 'undefined' && window.innerWidth <= LAPTOP_MAX_WIDTH
19
+ );
20
+
21
+ useEffect(() => {
22
+ const mediaQuery = window.matchMedia(`(max-width: ${LAPTOP_MAX_WIDTH}px)`);
23
+ const update = () => setIsCompact(mediaQuery.matches);
24
+ update();
25
+ mediaQuery.addEventListener('change', update);
26
+ return () => mediaQuery.removeEventListener('change', update);
27
+ }, []);
28
+
29
+ return isCompact;
30
+ }
31
+
32
+ function AlertRefreshIcon({ size = 28 }: { size?: number }) {
33
+ return (
34
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
35
+ <path
36
+ d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"
37
+ stroke="currentColor"
38
+ strokeWidth="2"
39
+ strokeLinecap="round"
40
+ strokeLinejoin="round"
41
+ />
42
+ <path
43
+ d="M21 3v5h-5"
44
+ stroke="currentColor"
45
+ strokeWidth="2"
46
+ strokeLinecap="round"
47
+ strokeLinejoin="round"
48
+ />
49
+ <path
50
+ d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"
51
+ stroke="currentColor"
52
+ strokeWidth="2"
53
+ strokeLinecap="round"
54
+ strokeLinejoin="round"
55
+ />
56
+ <path
57
+ d="M3 21v-5h5"
58
+ stroke="currentColor"
59
+ strokeWidth="2"
60
+ strokeLinecap="round"
61
+ strokeLinejoin="round"
62
+ />
63
+ </svg>
64
+ );
65
+ }
66
+
67
+ export const ModalComponent: React.FC<ModalProps> = ({
68
+ title,
69
+ size = 'sm',
70
+ isOpen,
71
+ onClose,
72
+ children,
73
+ className = '',
74
+ headerId = 'modal-header-bg',
75
+ style,
76
+ modalWidth,
77
+ modalsStatus,
78
+ setModalsStatus,
79
+ contentClass,
80
+ title_background_color,
81
+ text_color,
82
+ borderRadius,
83
+ variant = 'default',
84
+ alertDescription = 'Are you sure you want to reset the form? All unsaved changes will be lost.',
85
+ onAlertCancel,
86
+ onAlertConfirm,
87
+ alertCancelLabel = 'Cancel',
88
+ alertConfirmLabel = 'Reset',
89
+ alertConfirmLoading = false,
90
+ alertIcon,
91
+ alertIconColor = DEFAULT_ALERT_ICON_COLOR,
92
+ alertIconBackgroundColor = DEFAULT_ALERT_ICON_BACKGROUND,
93
+ alertConfirmVariant = 'blue',
94
+ hideCloseButton = false,
95
+ headerIcon,
96
+ }) => {
97
+ const resolvedTokens = useTokens();
98
+ const tokens = getModalTokensFromResolved(resolvedTokens);
99
+
100
+ const _isOpen = isOpen ?? modalsStatus ?? false;
101
+ const _onClose = onClose ?? (() => setModalsStatus?.(false));
102
+
103
+ const [renderState, setRenderState] = useState<'unmounted' | 'entering' | 'entered' | 'exiting'>(
104
+ _isOpen ? 'entered' : 'unmounted'
105
+ );
106
+ const isCompactScreen = useIsCompactScreen();
107
+ const closeButtonSize = isCompactScreen ? 'md' : 'lg';
108
+
109
+ // Prevent background scrolling when open and handle animation state
110
+ useEffect(() => {
111
+ if (_isOpen) {
112
+ lockScroll();
113
+ setRenderState('entering');
114
+ const timer = setTimeout(() => setRenderState('entered'), 10);
115
+ return () => {
116
+ clearTimeout(timer);
117
+ unlockScroll();
118
+ };
119
+ } else {
120
+ if (renderState !== 'unmounted') {
121
+ setRenderState('exiting');
122
+ const timer = setTimeout(() => setRenderState('unmounted'), 300);
123
+ return () => clearTimeout(timer);
124
+ }
125
+ }
126
+ }, [_isOpen]);
127
+
128
+ if (renderState === 'unmounted') return null;
129
+
130
+ const rawModalWidth = typeof modalWidth === 'object' ? modalWidth.width : modalWidth;
131
+ const finalModalWidth =
132
+ variant === 'alert' ?
133
+ rawModalWidth || '420px'
134
+ : rawModalWidth || tokens.size[size]?.width || '550px';
135
+
136
+ const finalBackground = tokens.background;
137
+ const finalRadius = borderRadius || tokens.radius;
138
+
139
+ const variantTitleBackground =
140
+ variant === 'error' ? '#F0645A'
141
+ : variant === 'confirmation' ? '#52760a'
142
+ : variant === 'alert' ? 'transparent'
143
+ : tokens.title.background;
144
+ const variantTitleColor =
145
+ variant === 'confirmation' || variant === 'error' ? '#ffffff'
146
+ : variant === 'alert' ? ALERT_TITLE_COLOR
147
+ : tokens.title.color;
148
+
149
+ const finalTitleBackground = title_background_color || variantTitleBackground || 'transparent';
150
+ const finalTitleColor = text_color || variantTitleColor;
151
+
152
+ let transformY = '0';
153
+ if (renderState === 'entering') transformY = '-100vh';
154
+ if (renderState === 'exiting') transformY = '-100vh';
155
+
156
+ const bgOpacity =
157
+ renderState === 'entering' || renderState === 'exiting' ?
158
+ tokens.backdrop.opacityHidden
159
+ : tokens.backdrop.opacityVisible;
160
+
161
+ const handleAlertCancel = () => {
162
+ (onAlertCancel ?? _onClose)();
163
+ };
164
+
165
+ const modalNode = (
166
+ <>
167
+ <div
168
+ className="cpc-modal-backdrop"
169
+ style={{
170
+ position: 'fixed',
171
+ top: 0,
172
+ left: 0,
173
+ width: '100vw',
174
+ height: '100vh',
175
+ backgroundColor: tokens.backdrop.background,
176
+ zIndex: 1200,
177
+ opacity: Number(bgOpacity),
178
+ transition: 'opacity 0.3s ease-in-out',
179
+ }}
180
+ />
181
+ <div
182
+ className={`cpc-modal-overlay ${className} ${contentClass || ''}`}
183
+ role="dialog"
184
+ aria-modal="true"
185
+ aria-labelledby={headerId}
186
+ style={{
187
+ position: 'fixed',
188
+ top: 0,
189
+ left: 0,
190
+ width: '100vw',
191
+ height: '100vh',
192
+ display: 'flex',
193
+ justifyContent: 'center',
194
+ alignItems: 'center',
195
+ zIndex: 1210,
196
+ ...style,
197
+ }}
198
+ >
199
+ {/*
200
+ Stop propagation so clicking inside the modal content
201
+ doesn't trigger the overlay's onClose event
202
+ */}
203
+ <div
204
+ className="cpc-modal-content"
205
+ onClick={(e) => e.stopPropagation()}
206
+ style={{
207
+ backgroundColor: finalBackground,
208
+ borderRadius: finalRadius,
209
+ width: '100%',
210
+ maxWidth: finalModalWidth,
211
+ maxHeight: '90vh',
212
+ display: 'flex',
213
+ flexDirection: 'column',
214
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
215
+ margin: '1.75rem',
216
+ transform: `translateY(${transformY})`,
217
+ transition: 'transform 0.3s ease-in-out',
218
+ }}
219
+ >
220
+ {variant === 'alert' ?
221
+ <div style={{ position: 'relative', padding: '28px 32px 32px' }}>
222
+ {!hideCloseButton && (
223
+ <div style={{ position: 'absolute', top: 16, right: 16 }}>
224
+ <CloseButton size={closeButtonSize} onClick={_onClose} aria-label="close" />
225
+ </div>
226
+ )}
227
+ <div
228
+ style={{
229
+ display: 'flex',
230
+ flexDirection: 'column',
231
+ alignItems: 'center',
232
+ textAlign: 'center',
233
+ gap: 16,
234
+ paddingTop: 8,
235
+ }}
236
+ >
237
+ <div
238
+ aria-hidden
239
+ style={{
240
+ width: 56,
241
+ height: 56,
242
+ borderRadius: '50%',
243
+ backgroundColor: alertIconBackgroundColor,
244
+ display: 'flex',
245
+ alignItems: 'center',
246
+ justifyContent: 'center',
247
+ color: alertIconColor,
248
+ }}
249
+ >
250
+ {alertIcon ?? <AlertRefreshIcon size={28} />}
251
+ </div>
252
+ <h2
253
+ id={headerId}
254
+ className="cpc-modal-title m-0"
255
+ style={{
256
+ fontSize: 20,
257
+ fontWeight: 700,
258
+ color: text_color || ALERT_TITLE_COLOR,
259
+ margin: 0,
260
+ lineHeight: 1.3,
261
+ }}
262
+ >
263
+ {title}
264
+ </h2>
265
+ <p
266
+ style={{
267
+ margin: 0,
268
+ fontSize: 15,
269
+ fontWeight: 500,
270
+ color: ALERT_DESC_COLOR,
271
+ lineHeight: 1.5,
272
+ maxWidth: 340,
273
+ }}
274
+ >
275
+ {alertDescription}
276
+ </p>
277
+ <div
278
+ style={{
279
+ display: 'flex',
280
+ flexDirection: 'row',
281
+ flexWrap: 'wrap',
282
+ gap: 12,
283
+ marginTop: 8,
284
+ justifyContent: 'center',
285
+ }}
286
+ >
287
+ <Button
288
+ variant="grey"
289
+ type="button"
290
+ disabled={alertConfirmLoading}
291
+ onClick={handleAlertCancel}
292
+ >
293
+ {alertCancelLabel}
294
+ </Button>
295
+ <Button
296
+ variant={alertConfirmVariant}
297
+ type="button"
298
+ loading={alertConfirmLoading}
299
+ onClick={() => onAlertConfirm?.()}
300
+ >
301
+ {alertConfirmLabel}
302
+ </Button>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ : <>
307
+ {/* Header */}
308
+ <div
309
+ className="cpc-modal-header"
310
+ id={headerId}
311
+ style={{
312
+ backgroundColor: finalTitleBackground,
313
+ color: finalTitleColor,
314
+ padding: `${tokens.padding} ${tokens.padding} 16px ${tokens.padding}`,
315
+ borderTopLeftRadius: finalRadius,
316
+ borderTopRightRadius: finalRadius,
317
+ display: 'flex',
318
+ justifyContent: 'space-between',
319
+ alignItems: 'center',
320
+ flexShrink: 0,
321
+ }}
322
+ >
323
+ <h5
324
+ className="cpc-modal-title section_header m-0"
325
+ style={{
326
+ display: 'flex',
327
+ alignItems: 'center',
328
+ gap: '8px',
329
+ fontSize: tokens.title.typography?.fontSize,
330
+ fontWeight: tokens.title.typography?.fontWeight,
331
+ lineHeight: tokens.title.typography?.lineHeight,
332
+ color: finalTitleColor,
333
+ margin: 0,
334
+ }}
335
+ >
336
+ {(variant === 'error' || variant === 'confirmation' || headerIcon) && (
337
+ <span
338
+ aria-hidden="true"
339
+ style={{
340
+ display: 'inline-flex',
341
+ alignItems: 'center',
342
+ justifyContent: 'center',
343
+ width: '20px',
344
+ height: '20px',
345
+ borderRadius: '50%',
346
+ fontSize: '12px',
347
+ fontWeight: 700,
348
+ lineHeight: 1,
349
+ backgroundColor: 'rgba(255, 255, 255, 0.2)',
350
+ color: finalTitleColor,
351
+ flexShrink: 0,
352
+ }}
353
+ >
354
+ {headerIcon ?? (variant === 'confirmation' ? '✓' : '!')}
355
+ </span>
356
+ )}
357
+ {title}
358
+ </h5>
359
+ {!hideCloseButton && (
360
+ <CloseButton size={closeButtonSize} onClick={_onClose} aria-label="close" />
361
+ )}
362
+ </div>
363
+ <div
364
+ className="content_dividers"
365
+ style={{
366
+ display: 'flex',
367
+ flexDirection: 'column',
368
+ flex: '1 1 auto',
369
+ minHeight: 0,
370
+ overflow: 'hidden',
371
+ }}
372
+ >
373
+ {/* Body */}
374
+ <div
375
+ className=""
376
+ style={{
377
+ display: 'flex',
378
+ flexDirection: 'column',
379
+ flex: '1 1 auto',
380
+ minHeight: 0,
381
+ overflowY: 'auto',
382
+ overflowX: 'hidden',
383
+ }}
384
+ >
385
+ {children}
386
+ </div>
387
+ </div>
388
+ </>
389
+ }
390
+ </div>
391
+ </div>
392
+ </>
393
+ );
394
+
395
+ if (typeof document === 'undefined') return modalNode;
396
+ return createPortal(modalNode, document.body);
397
+ };
398
+
399
+ export default ModalComponent;
@@ -0,0 +1,2 @@
1
+ export { default as Modal } from "./Modal";
2
+ export * from "./types";
@@ -0,0 +1,126 @@
1
+ import React from 'react';
2
+
3
+ export interface ModalProps {
4
+ /**
5
+ * Title displayed at the top of the modal.
6
+ */
7
+ title: string | React.ReactNode;
8
+
9
+ /**
10
+ * Size of the modal.
11
+ * @default "sm"
12
+ */
13
+ size?: 'sm' | 'lg';
14
+
15
+ /**
16
+ * Whether the modal is open.
17
+ */
18
+ isOpen?: boolean;
19
+
20
+ /**
21
+ * Callback fired when the modal requests to be closed.
22
+ */
23
+ onClose?: () => void;
24
+
25
+ /**
26
+ * When `true`, the header close (X) button is not rendered.
27
+ * Useful for blocking flows (e.g. forced password reset) where the user
28
+ * must complete an action before the modal can be dismissed.
29
+ * @default false
30
+ */
31
+ hideCloseButton?: boolean;
32
+
33
+ /**
34
+ * Content to render inside the modal. Optional when `variant="alert"` (layout is built-in).
35
+ */
36
+ children?: React.ReactNode;
37
+
38
+ /**
39
+ * Optional CSS class name for the wrapper.
40
+ */
41
+ className?: string;
42
+
43
+ /**
44
+ * Accessibility ID for the modal header.
45
+ */
46
+ headerId?: string;
47
+
48
+ /**
49
+ * Custom style for the overlay wrapper, if any.
50
+ */
51
+ style?: React.CSSProperties;
52
+
53
+ /**
54
+ * Visual variant for modal header styling.
55
+ * `alert`: centered confirmation card (icon, title, description, actions); no colored header bar.
56
+ * @default "default"
57
+ */
58
+ variant?: 'default' | 'error' | 'confirmation' | 'alert';
59
+
60
+ /**
61
+ * Body copy for `variant="alert"`. Ignored for other variants.
62
+ */
63
+ alertDescription?: React.ReactNode;
64
+
65
+ /**
66
+ * Cancel control for `variant="alert"`. Defaults to `onClose`.
67
+ */
68
+ onAlertCancel?: () => void;
69
+
70
+ /**
71
+ * Primary action for `variant="alert"` (e.g. reset form).
72
+ */
73
+ onAlertConfirm?: () => void;
74
+
75
+ /** @default "Cancel" */
76
+ alertCancelLabel?: string;
77
+
78
+ /** @default "Reset" */
79
+ alertConfirmLabel?: string;
80
+
81
+ /**
82
+ * Shows the design-system button spinner on the alert confirm action.
83
+ * Also disables cancel while true.
84
+ * @default false
85
+ */
86
+ alertConfirmLoading?: boolean;
87
+
88
+ /**
89
+ * Icon for `variant="alert"`. When omitted, a default refresh glyph is shown.
90
+ */
91
+ alertIcon?: React.ReactNode;
92
+
93
+ /**
94
+ * Color for the alert icon (default refresh strokes, and `color` on the icon wrapper for `currentColor` icons).
95
+ * @default "#EF4444"
96
+ */
97
+ alertIconColor?: string;
98
+
99
+ /**
100
+ * Background color of the circular icon badge for `variant="alert"`.
101
+ * @default "#FEE2E2" (light red; pair with `alertIconColor` for destructive flows)
102
+ */
103
+ alertIconBackgroundColor?: string;
104
+
105
+ /**
106
+ * Design-system button variant for the alert primary action.
107
+ * @default "blue"
108
+ */
109
+ alertConfirmVariant?: 'blue' | 'red' | 'grey';
110
+
111
+ /**
112
+ * Optional icon shown in the header badge for `confirmation` / `error` variants.
113
+ * When omitted, defaults to ✓ (confirmation) or ! (error).
114
+ */
115
+ headerIcon?: React.ReactNode;
116
+
117
+ // --- Legacy Props (from Modal.jsx) ---
118
+ modalWidth?: { width?: string | number } | string | number;
119
+ modalsStatus?: boolean;
120
+ setModalsStatus?: (status: boolean) => void;
121
+ open?: boolean;
122
+ contentClass?: string;
123
+ title_background_color?: string;
124
+ text_color?: string;
125
+ borderRadius?: string;
126
+ }
@@ -0,0 +1,167 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Pagination } from "./Pagination";
4
+ import type { PaginationSize } from "./Pagination.types";
5
+
6
+ const meta: Meta<typeof Pagination> = {
7
+ title: "Design System/Pagination",
8
+ component: Pagination,
9
+ tags: [],
10
+ decorators: [(Story) => <Story />],
11
+ args: {
12
+ page: 1,
13
+ count: 5,
14
+ size: "md",
15
+ showFirstLast: true,
16
+ siblingCount: 1,
17
+ disabled: false,
18
+ },
19
+ argTypes: {
20
+ page: {
21
+ control: { type: "number", min: 1 },
22
+ description: "Current page (1-based)",
23
+ table: { type: { summary: "number" }, defaultValue: { summary: "1" } },
24
+ },
25
+ count: {
26
+ control: { type: "number", min: 1 },
27
+ description: "Total number of pages",
28
+ table: { type: { summary: "number" }, defaultValue: { summary: "5" } },
29
+ },
30
+ onPageChange: { action: "pageChange" },
31
+ size: {
32
+ control: { type: "select" },
33
+ options: ["xs", "sm", "md", "lg"] as PaginationSize[],
34
+ description: "Size of page buttons and icons",
35
+ table: {
36
+ type: { summary: "xs | sm | md | lg" },
37
+ defaultValue: { summary: "md" },
38
+ },
39
+ },
40
+ showFirstLast: {
41
+ control: { type: "boolean" },
42
+ description: "Show first/last page buttons",
43
+ table: { type: { summary: "boolean" }, defaultValue: { summary: "true" } },
44
+ },
45
+ siblingCount: {
46
+ control: { type: "number", min: 0 },
47
+ description: "Sibling page numbers on each side of current",
48
+ table: { type: { summary: "number" }, defaultValue: { summary: "1" } },
49
+ },
50
+ disabled: {
51
+ control: { type: "boolean" },
52
+ description: "Disable all buttons",
53
+ table: { type: { summary: "boolean" }, defaultValue: { summary: "false" } },
54
+ },
55
+ },
56
+ };
57
+
58
+ export default meta;
59
+
60
+ type Story = StoryObj<typeof Pagination>;
61
+
62
+ // Interactive playground with state so page updates
63
+ function PaginationWithState(props: {
64
+ count: number;
65
+ size?: PaginationSize;
66
+ showFirstLast?: boolean;
67
+ siblingCount?: number;
68
+ disabled?: boolean;
69
+ }) {
70
+ const [page, setPage] = useState(1);
71
+ return (
72
+ <Pagination
73
+ page={page}
74
+ count={props.count}
75
+ onPageChange={setPage}
76
+ size={props.size}
77
+ showFirstLast={props.showFirstLast}
78
+ siblingCount={props.siblingCount}
79
+ disabled={props.disabled}
80
+ />
81
+ );
82
+ }
83
+
84
+ export const Playground: Story = {
85
+ render: (args) => (
86
+ <PaginationWithState
87
+ count={args.count ?? 5}
88
+ size={args.size}
89
+ showFirstLast={args.showFirstLast}
90
+ siblingCount={args.siblingCount}
91
+ disabled={args.disabled}
92
+ />
93
+ ),
94
+ args: {
95
+ page: 1,
96
+ count: 5,
97
+ size: "md",
98
+ showFirstLast: true,
99
+ siblingCount: 1,
100
+ disabled: false,
101
+ },
102
+ };
103
+
104
+ export const Showcase: Story = {
105
+ render: () => (
106
+ <div style={{ display: "flex", flexDirection: "column", gap: 32, padding: 20 }}>
107
+ <div>
108
+ <h3 style={{ fontSize: 18, fontWeight: "bold", marginBottom: 16 }}>
109
+ Default (page 1 of 5, with ellipsis when many pages)
110
+ </h3>
111
+ <PaginationWithState count={5} />
112
+ </div>
113
+
114
+ <div>
115
+ <h3 style={{ fontSize: 18, fontWeight: "bold", marginBottom: 16 }}>
116
+ Many pages (current page 1 – shows 1, 2, 3, … 5)
117
+ </h3>
118
+ <PaginationWithState count={10} />
119
+ </div>
120
+
121
+ <div>
122
+ <h3 style={{ fontSize: 18, fontWeight: "bold", marginBottom: 16 }}>
123
+ Size variants
124
+ </h3>
125
+ <div style={{ display: "flex", flexDirection: "column", gap: 16, alignItems: "flex-start" }}>
126
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
127
+ <span style={{ minWidth: 60, fontSize: 14 }}>XS:</span>
128
+ <PaginationWithState count={5} size="xs" />
129
+ </div>
130
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
131
+ <span style={{ minWidth: 60, fontSize: 14 }}>Small:</span>
132
+ <PaginationWithState count={5} size="sm" />
133
+ </div>
134
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
135
+ <span style={{ minWidth: 60, fontSize: 14 }}>Medium:</span>
136
+ <PaginationWithState count={5} size="md" />
137
+ </div>
138
+ <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
139
+ <span style={{ minWidth: 60, fontSize: 14 }}>Large:</span>
140
+ <PaginationWithState count={5} size="lg" />
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div>
146
+ <h3 style={{ fontSize: 18, fontWeight: "bold", marginBottom: 16 }}>
147
+ Without first/last buttons
148
+ </h3>
149
+ <PaginationWithState count={5} showFirstLast={false} />
150
+ </div>
151
+
152
+ <div>
153
+ <h3 style={{ fontSize: 18, fontWeight: "bold", marginBottom: 16 }}>
154
+ Disabled
155
+ </h3>
156
+ <PaginationWithState count={5} disabled />
157
+ </div>
158
+
159
+ <div>
160
+ <h3 style={{ fontSize: 18, fontWeight: "bold", marginBottom: 16 }}>
161
+ Single page (no navigation)
162
+ </h3>
163
+ <PaginationWithState count={1} />
164
+ </div>
165
+ </div>
166
+ ),
167
+ };