@altruist03/ui 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/assets/images/noData.webp +0 -0
  2. package/dist/index.js +15237 -650
  3. package/dist/index.js.map +1 -1
  4. package/dist/noData-XPRHKMFR.webp +0 -0
  5. package/package.json +21 -7
  6. package/src/ThemeProvider/ThemeContext.tsx +75 -0
  7. package/src/ThemeProvider/index.ts +7 -0
  8. package/src/assets/images/noData.webp +0 -0
  9. package/src/assets.d.ts +10 -0
  10. package/src/components/Accordion/Accordion.stories.tsx +64 -0
  11. package/src/components/Accordion/Accordion.tsx +219 -0
  12. package/src/components/Accordion/Accordion.types.ts +16 -0
  13. package/src/components/Avatar/Avatar.stories.tsx +304 -0
  14. package/src/components/Avatar/Avatar.tsx +211 -0
  15. package/src/components/Avatar/Avatar.types.ts +33 -0
  16. package/src/components/Badge/Badge.stories.tsx +143 -0
  17. package/src/components/Badge/Badge.tsx +138 -0
  18. package/src/components/Badge/Badge.types.ts +24 -0
  19. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
  20. package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
  21. package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
  22. package/src/components/Button/Button.stories.tsx +457 -0
  23. package/src/components/Button/Button.tsx +202 -0
  24. package/src/components/Button/Button.types.ts +18 -0
  25. package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
  26. package/src/components/Checkbox/Checkbox.tsx +352 -0
  27. package/src/components/Checkbox/Checkbox.types.ts +123 -0
  28. package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
  29. package/src/components/CloseButton/CloseButton.tsx +117 -0
  30. package/src/components/CloseButton/CloseButton.types.ts +15 -0
  31. package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
  32. package/src/components/DataTable/DataTable.stories.tsx +233 -0
  33. package/src/components/DataTable/DataTable.tsx +968 -0
  34. package/src/components/DataTable/DataTable.types.ts +49 -0
  35. package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
  36. package/src/components/DatePicker/DatePicker.tsx +669 -0
  37. package/src/components/DatePicker/DatePicker.types.ts +105 -0
  38. package/src/components/DatePicker/DateTimePicker.tsx +35 -0
  39. package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
  40. package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
  41. package/src/components/Drawer/Drawer.stories.tsx +243 -0
  42. package/src/components/Drawer/Drawer.tsx +196 -0
  43. package/src/components/Drawer/Drawer.types.ts +60 -0
  44. package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
  45. package/src/components/FileUpload/FileUpload.tsx +216 -0
  46. package/src/components/FileUpload/FileUpload.types.ts +80 -0
  47. package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
  48. package/src/components/FilterPopover/FilterPopover.tsx +310 -0
  49. package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
  50. package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
  51. package/src/components/ImageViewer/useImagePanRotate.js +102 -0
  52. package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
  53. package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
  54. package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
  55. package/src/components/Loader/Loader.stories.tsx +95 -0
  56. package/src/components/Loader/Loader.tsx +132 -0
  57. package/src/components/Loader/Loader.types.ts +6 -0
  58. package/src/components/Menu/Menu.tsx +143 -0
  59. package/src/components/Menu/Menu.types.ts +28 -0
  60. package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
  61. package/src/components/MenuButton/MenuButton.tsx +45 -0
  62. package/src/components/MenuButton/MenuButton.types.ts +21 -0
  63. package/src/components/Modal/Modal.stories.tsx +266 -0
  64. package/src/components/Modal/Modal.tsx +399 -0
  65. package/src/components/Modal/index.ts +2 -0
  66. package/src/components/Modal/types.ts +126 -0
  67. package/src/components/Pagination/Pagination.stories.tsx +167 -0
  68. package/src/components/Pagination/Pagination.tsx +266 -0
  69. package/src/components/Pagination/Pagination.types.ts +20 -0
  70. package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
  71. package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
  72. package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
  73. package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
  74. package/src/components/PhoneField/PhoneField.tsx +284 -0
  75. package/src/components/PhoneField/PhoneField.types.ts +89 -0
  76. package/src/components/PhoneField/countryCodes.ts +236 -0
  77. package/src/components/Progress/Progress.stories.tsx +83 -0
  78. package/src/components/Progress/Progress.tsx +91 -0
  79. package/src/components/Progress/Progress.types.ts +22 -0
  80. package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
  81. package/src/components/RadioButton/RadioButton.tsx +427 -0
  82. package/src/components/RadioButton/RadioButton.types.ts +117 -0
  83. package/src/components/ReportCard/ReportCard.tsx +183 -0
  84. package/src/components/ReportCard/ReportCard.types.ts +17 -0
  85. package/src/components/SectionIcon/SectionIcon.tsx +87 -0
  86. package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
  87. package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
  88. package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
  89. package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
  90. package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
  91. package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
  92. package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
  93. package/src/components/Selector/Selector.stories.tsx +382 -0
  94. package/src/components/Selector/Selector.tsx +1310 -0
  95. package/src/components/Selector/Selector.types.ts +71 -0
  96. package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
  97. package/src/components/Skeleton/Skeleton.tsx +20 -0
  98. package/src/components/Skeleton/Skeleton.types.ts +3 -0
  99. package/src/components/Spinner/Spinner.stories.tsx +84 -0
  100. package/src/components/Spinner/Spinner.tsx +79 -0
  101. package/src/components/Spinner/Spinner.types.ts +8 -0
  102. package/src/components/StatCard/StatCard.stories.tsx +125 -0
  103. package/src/components/StatCard/StatCard.tsx +167 -0
  104. package/src/components/StatCard/StatCard.types.ts +20 -0
  105. package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
  106. package/src/components/StatusBadge/StatusBadge.tsx +74 -0
  107. package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
  108. package/src/components/Stepper/Stepper.stories.tsx +85 -0
  109. package/src/components/Stepper/Stepper.tsx +138 -0
  110. package/src/components/Stepper/Stepper.types.ts +16 -0
  111. package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
  112. package/src/components/SubTabs/SubTabs.tsx +279 -0
  113. package/src/components/SubTabs/SubTabs.types.ts +44 -0
  114. package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
  115. package/src/components/SummaryCard/SummaryCard.tsx +141 -0
  116. package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
  117. package/src/components/Switch/Switch.stories.tsx +59 -0
  118. package/src/components/Switch/Switch.tsx +217 -0
  119. package/src/components/Switch/Switch.types.ts +22 -0
  120. package/src/components/Tabs/Tabs.stories.tsx +178 -0
  121. package/src/components/Tabs/Tabs.tsx +197 -0
  122. package/src/components/Tabs/Tabs.types.ts +34 -0
  123. package/src/components/TextField/TextField.stories.tsx +249 -0
  124. package/src/components/TextField/TextField.tsx +462 -0
  125. package/src/components/TextField/TextField.types.ts +141 -0
  126. package/src/components/Toast/Toast.stories.tsx +237 -0
  127. package/src/components/Toast/Toast.tsx +246 -0
  128. package/src/components/Toast/Toast.types.ts +31 -0
  129. package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
  130. package/src/components/ToggleButton/ToggleButton.tsx +267 -0
  131. package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
  132. package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
  133. package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
  134. package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
  135. package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
  136. package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
  137. package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
  138. package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
  139. package/src/components/index.ts +157 -0
  140. package/src/icons/ReportDollarIcon.tsx +71 -0
  141. package/src/icons/index.ts +27 -0
  142. package/src/index.ts +214 -0
  143. package/src/storybook/preview.tsx +64 -0
  144. package/src/theme/index.ts +4 -0
  145. package/src/theme/types.d.ts +1 -0
  146. package/src/themes/cpc.ts +4 -0
  147. package/src/themes/firebird.ts +4 -0
  148. package/src/tokens/brands/cpc/themes/dark.json +82 -0
  149. package/src/tokens/brands/cpc/themes/light.json +1 -0
  150. package/src/tokens/brands/default/brand.json +20 -0
  151. package/src/tokens/brands/firebird/themes/dark.json +28 -0
  152. package/src/tokens/brands/firebird/themes/light.json +20 -0
  153. package/src/tokens/button.ts +24 -0
  154. package/src/tokens/components/accordion.json +52 -0
  155. package/src/tokens/components/avatar.json +40 -0
  156. package/src/tokens/components/badge.json +41 -0
  157. package/src/tokens/components/breadcrumb.json +13 -0
  158. package/src/tokens/components/button.json +67 -0
  159. package/src/tokens/components/card.json +39 -0
  160. package/src/tokens/components/checkbox.json +64 -0
  161. package/src/tokens/components/closebutton.json +55 -0
  162. package/src/tokens/components/datatable.json +40 -0
  163. package/src/tokens/components/datepicker.json +99 -0
  164. package/src/tokens/components/drawer.json +23 -0
  165. package/src/tokens/components/field.json +29 -0
  166. package/src/tokens/components/fileupload.json +57 -0
  167. package/src/tokens/components/filterpopover.json +29 -0
  168. package/src/tokens/components/input.json +31 -0
  169. package/src/tokens/components/ipwhitelistfield.json +16 -0
  170. package/src/tokens/components/loader.json +15 -0
  171. package/src/tokens/components/menu.json +21 -0
  172. package/src/tokens/components/modal.json +25 -0
  173. package/src/tokens/components/pagination.json +45 -0
  174. package/src/tokens/components/phonefield.json +41 -0
  175. package/src/tokens/components/progress.json +12 -0
  176. package/src/tokens/components/radio.json +87 -0
  177. package/src/tokens/components/reportcard.json +17 -0
  178. package/src/tokens/components/selector.json +87 -0
  179. package/src/tokens/components/selectscroll.json +65 -0
  180. package/src/tokens/components/skeleton.json +6 -0
  181. package/src/tokens/components/stepper.json +38 -0
  182. package/src/tokens/components/subtabs.json +45 -0
  183. package/src/tokens/components/switch.json +55 -0
  184. package/src/tokens/components/tabs.json +38 -0
  185. package/src/tokens/components/textfield.json +59 -0
  186. package/src/tokens/components/togglebutton.json +48 -0
  187. package/src/tokens/components/uploaddropzone.json +61 -0
  188. package/src/tokens/drawer.ts +37 -0
  189. package/src/tokens/foundation/breakpoints.json +10 -0
  190. package/src/tokens/foundation/colors.json +122 -0
  191. package/src/tokens/foundation/opacity.json +16 -0
  192. package/src/tokens/foundation/radius.json +14 -0
  193. package/src/tokens/foundation/size.json +38 -0
  194. package/src/tokens/foundation/spacing.json +19 -0
  195. package/src/tokens/foundation/typography.json +43 -0
  196. package/src/tokens/index.ts +3665 -0
  197. package/src/tokens/modal.ts +42 -0
  198. package/src/tokens/semantic/color.json +148 -0
  199. package/src/tokens/semantic/field.json +28 -0
  200. package/src/tokens/semantic/opacity.json +5 -0
  201. package/src/tokens/semantic/size.json +722 -0
  202. package/src/tokens/semantic/spacing.json +27 -0
  203. package/src/tokens/semantic/typography.json +87 -0
  204. package/src/utils/scrollLock.ts +44 -0
  205. package/dist/index.css +0 -514
  206. package/dist/index.css.map +0 -1
  207. package/dist/index.d.ts +0 -289
@@ -0,0 +1,266 @@
1
+ import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
2
+ import type { PaginationProps as Props } from './Pagination.types';
3
+ import { getPaginationTokensFromResolved, getTokens } from '../../tokens';
4
+ import { ThemeContext } from '../../ThemeProvider/ThemeContext';
5
+
6
+ const MOBILE_MAX_WIDTH = 767;
7
+
8
+ function useIsMobileScreen() {
9
+ const [isMobile, setIsMobile] = useState(
10
+ () => typeof window !== 'undefined' && window.innerWidth <= MOBILE_MAX_WIDTH
11
+ );
12
+
13
+ useEffect(() => {
14
+ const mediaQuery = window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH}px)`);
15
+ const update = () => setIsMobile(mediaQuery.matches);
16
+ update();
17
+ mediaQuery.addEventListener('change', update);
18
+ return () => mediaQuery.removeEventListener('change', update);
19
+ }, []);
20
+
21
+ return isMobile;
22
+ }
23
+
24
+ type RangeItem = number | 'ellipsis';
25
+
26
+ function getCompactPaginationRange(page: number, count: number): RangeItem[] {
27
+ if (count <= 3) {
28
+ return Array.from({ length: count }, (_, i) => i + 1);
29
+ }
30
+
31
+ if (page <= 2) return [1, 2, 'ellipsis', count];
32
+ if (page >= count - 1) return [1, 'ellipsis', count - 1, count];
33
+
34
+ const out: RangeItem[] = [1, 'ellipsis', page];
35
+ if (page + 1 < count) out.push(page + 1);
36
+ if (page + 2 < count) out.push('ellipsis');
37
+ out.push(count);
38
+ return out;
39
+ }
40
+
41
+ function getPaginationRange(
42
+ page: number,
43
+ count: number,
44
+ siblingCount: number,
45
+ compact = false
46
+ ): RangeItem[] {
47
+ if (count <= 0) return [];
48
+
49
+ if (compact && count > 3) {
50
+ return getCompactPaginationRange(page, count);
51
+ }
52
+
53
+ const totalVisible = 2 * siblingCount + 3; // current + 2*sibling + first + last
54
+ if (count <= totalVisible) {
55
+ return Array.from({ length: count }, (_, i) => i + 1);
56
+ }
57
+ const left = Math.max(1, page - siblingCount);
58
+ const right = Math.min(count, page + siblingCount);
59
+ const showLeftEllipsis = left > 2;
60
+ const showRightEllipsis = right < count - 1;
61
+ const out: RangeItem[] = [];
62
+ out.push(1);
63
+ if (showLeftEllipsis) out.push('ellipsis');
64
+ for (let i = left; i <= right; i++) {
65
+ if (i !== 1 && i !== count) out.push(i);
66
+ }
67
+ if (showRightEllipsis) out.push('ellipsis');
68
+ if (count > 1) out.push(count);
69
+ return out;
70
+ }
71
+
72
+ export function Pagination({
73
+ page,
74
+ count,
75
+ onPageChange,
76
+ size = 'md',
77
+ showFirstLast = true,
78
+ siblingCount = 1,
79
+ disabled = false,
80
+ 'aria-label': ariaLabel = 'Pagination',
81
+ }: Props) {
82
+ const [hovered, setHovered] = useState<'first' | 'prev' | 'next' | 'last' | number | null>(null);
83
+ const { brand, mode } = useContext(ThemeContext);
84
+ const isMobileScreen = useIsMobileScreen();
85
+ const tokens = useMemo(
86
+ () =>
87
+ getPaginationTokensFromResolved(
88
+ getTokens({ brand, mode, screen: isMobileScreen ? 'mobile' : 'desktop' })
89
+ ),
90
+ [brand, mode, isMobileScreen]
91
+ );
92
+ const sizeToken =
93
+ size === 'xs' ? tokens.sizeXs
94
+ : size === 'sm' ? tokens.sizeSm
95
+ : size === 'lg' ? tokens.sizeLg
96
+ : tokens.sizeMd;
97
+ const range = getPaginationRange(page, count, siblingCount, isMobileScreen);
98
+
99
+ const handleFirst = useCallback(() => {
100
+ if (!disabled && page > 1) onPageChange(1);
101
+ }, [disabled, page, onPageChange]);
102
+
103
+ const handlePrev = useCallback(() => {
104
+ if (!disabled && page > 1) onPageChange(page - 1);
105
+ }, [disabled, page, onPageChange]);
106
+
107
+ const handleNext = useCallback(() => {
108
+ if (!disabled && page < count) onPageChange(page + 1);
109
+ }, [disabled, page, count, onPageChange]);
110
+
111
+ const handleLast = useCallback(() => {
112
+ if (!disabled && page < count) onPageChange(count);
113
+ }, [disabled, page, count, onPageChange]);
114
+
115
+ const canFirst = page > 1 && !disabled;
116
+ const canPrev = page > 1 && !disabled;
117
+ const canNext = page < count && !disabled;
118
+ const canLast = page < count && !disabled;
119
+
120
+ const fontSizeCss =
121
+ typeof sizeToken?.fontSize === 'number' ?
122
+ `${sizeToken.fontSize}px`
123
+ : (sizeToken?.fontSize ?? '14px');
124
+
125
+ const baseStyle: React.CSSProperties = {
126
+ display: 'inline-flex',
127
+ alignItems: 'center',
128
+ justifyContent: 'center',
129
+ flexShrink: isMobileScreen ? 0 : undefined,
130
+ height: sizeToken?.height ?? '36px',
131
+ minWidth: sizeToken?.minWidth ?? '36px',
132
+ padding: sizeToken?.padding ?? '6px 12px',
133
+ fontFamily: tokens.fontFamily,
134
+ fontWeight: tokens.fontWeight,
135
+ fontSize: fontSizeCss,
136
+ borderRadius: sizeToken?.borderRadius ?? '50px',
137
+ border: 'none',
138
+ background: 'transparent',
139
+ color: tokens.iconColor,
140
+ cursor: disabled ? 'default' : 'pointer',
141
+ opacity: disabled ? tokens.disabledOpacity : 1,
142
+ transition: 'background 0.15s, color 0.15s',
143
+ };
144
+
145
+ const iconHover = hovered !== null && !disabled;
146
+ const iconBg = iconHover ? tokens.hoverBackground : 'transparent';
147
+
148
+ return (
149
+ <nav
150
+ role="navigation"
151
+ aria-label={ariaLabel}
152
+ style={{
153
+ display: 'inline-flex',
154
+ alignItems: 'center',
155
+ gap: isMobileScreen ? '2px' : tokens.gap,
156
+ flexWrap: isMobileScreen ? 'nowrap' : 'wrap',
157
+ maxWidth: isMobileScreen ? '100%' : undefined,
158
+ overflowX: isMobileScreen ? 'auto' : undefined,
159
+ }}
160
+ >
161
+ {showFirstLast && (
162
+ <button
163
+ type="button"
164
+ aria-label="Go to first page"
165
+ disabled={!canFirst}
166
+ style={{
167
+ ...baseStyle,
168
+ background: hovered === 'first' ? iconBg : 'transparent',
169
+ opacity: !canFirst ? tokens.disabledOpacity : baseStyle.opacity,
170
+ }}
171
+ onMouseEnter={() => setHovered('first')}
172
+ onMouseLeave={() => setHovered(null)}
173
+ onClick={handleFirst}
174
+ >
175
+ &#x00AB;
176
+ </button>
177
+ )}
178
+
179
+ <button
180
+ type="button"
181
+ aria-label="Go to previous page"
182
+ disabled={!canPrev}
183
+ style={{
184
+ ...baseStyle,
185
+ background: hovered === 'prev' ? iconBg : 'transparent',
186
+ opacity: !canPrev ? tokens.disabledOpacity : baseStyle.opacity,
187
+ }}
188
+ onMouseEnter={() => setHovered('prev')}
189
+ onMouseLeave={() => setHovered(null)}
190
+ onClick={handlePrev}
191
+ >
192
+ &#x2039;
193
+ </button>
194
+
195
+ {range.map((item, idx) =>
196
+ item === 'ellipsis' ?
197
+ <span
198
+ key={`ellipsis-${idx}`}
199
+ style={{
200
+ ...baseStyle,
201
+ cursor: 'default',
202
+ minWidth: isMobileScreen ? 'auto' : sizeToken?.minWidth,
203
+ padding: isMobileScreen ? '4px' : baseStyle.padding,
204
+ color: tokens.inactiveColor,
205
+ }}
206
+ aria-hidden
207
+ >
208
+ &#x2026;
209
+ </span>
210
+ : <button
211
+ key={item}
212
+ type="button"
213
+ aria-label={item === page ? `Current page, page ${item}` : `Go to page ${item}`}
214
+ aria-current={item === page ? 'page' : undefined}
215
+ style={{
216
+ ...baseStyle,
217
+ background:
218
+ item === page ? tokens.activeBackground
219
+ : hovered === item ? tokens.hoverBackground
220
+ : 'transparent',
221
+ color: item === page ? tokens.activeColor : tokens.inactiveColor,
222
+ }}
223
+ onMouseEnter={() => setHovered(item)}
224
+ onMouseLeave={() => setHovered(null)}
225
+ onClick={() => !disabled && onPageChange(item)}
226
+ >
227
+ {item}
228
+ </button>
229
+ )}
230
+
231
+ <button
232
+ type="button"
233
+ aria-label="Go to next page"
234
+ disabled={!canNext}
235
+ style={{
236
+ ...baseStyle,
237
+ background: hovered === 'next' ? iconBg : 'transparent',
238
+ opacity: !canNext ? tokens.disabledOpacity : baseStyle.opacity,
239
+ }}
240
+ onMouseEnter={() => setHovered('next')}
241
+ onMouseLeave={() => setHovered(null)}
242
+ onClick={handleNext}
243
+ >
244
+ &#x203A;
245
+ </button>
246
+
247
+ {showFirstLast && (
248
+ <button
249
+ type="button"
250
+ aria-label="Go to last page"
251
+ disabled={!canLast}
252
+ style={{
253
+ ...baseStyle,
254
+ background: hovered === 'last' ? iconBg : 'transparent',
255
+ opacity: !canLast ? tokens.disabledOpacity : baseStyle.opacity,
256
+ }}
257
+ onMouseEnter={() => setHovered('last')}
258
+ onMouseLeave={() => setHovered(null)}
259
+ onClick={handleLast}
260
+ >
261
+ &#x00BB;
262
+ </button>
263
+ )}
264
+ </nav>
265
+ );
266
+ }
@@ -0,0 +1,20 @@
1
+ export type PaginationSize = "xs" | "sm" | "md" | "lg";
2
+
3
+ export interface PaginationProps {
4
+ /** Current page (1-based). */
5
+ page: number;
6
+ /** Total number of pages. */
7
+ count: number;
8
+ /** Called when page changes (1-based). */
9
+ onPageChange: (page: number) => void;
10
+ /** Size of page buttons and icons. */
11
+ size?: PaginationSize;
12
+ /** Show first/last page buttons. Default true. */
13
+ showFirstLast?: boolean;
14
+ /** Number of sibling page numbers to show on each side of current. Default 1. */
15
+ siblingCount?: number;
16
+ /** Disable all buttons (e.g. while loading). */
17
+ disabled?: boolean;
18
+ /** Optional aria-label for the nav. */
19
+ "aria-label"?: string;
20
+ }
@@ -0,0 +1,50 @@
1
+ import React, { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import { PeriodSelector } from './PeriodSelector';
4
+ import type { PeriodSelectorProps } from './PeriodSelector.types';
5
+
6
+ const meta: Meta<typeof PeriodSelector> = {
7
+ title: 'Design System/PeriodSelector',
8
+ component: PeriodSelector,
9
+ tags: [],
10
+ decorators: [(Story) => <Story />],
11
+ args: {
12
+ period: '1m',
13
+ disabled: false,
14
+ },
15
+ argTypes: {
16
+ period: {
17
+ control: { type: 'select' },
18
+ options: ['1m', '3m', '6m', '1y', 'custom'],
19
+ description: 'The selected period',
20
+ },
21
+ disabled: {
22
+ control: { type: 'boolean' },
23
+ description: 'Disable the period selector',
24
+ },
25
+ },
26
+ };
27
+
28
+ export default meta;
29
+
30
+ type Story = StoryObj<typeof PeriodSelector>;
31
+
32
+ export const Playground: Story = {
33
+ render: function PlaygroundRender(args: PeriodSelectorProps) {
34
+ const [period, setPeriod] = useState<string>(args.period || '1m');
35
+ const [startDate, setStartDate] = useState<Date | null>(null);
36
+ const [endDate, setEndDate] = useState<Date | null>(null);
37
+
38
+ return (
39
+ <PeriodSelector
40
+ {...args}
41
+ period={period}
42
+ onPeriodChange={setPeriod}
43
+ startDate={startDate}
44
+ endDate={endDate}
45
+ onStartDateChange={setStartDate}
46
+ onEndDateChange={setEndDate}
47
+ />
48
+ );
49
+ },
50
+ };
@@ -0,0 +1,47 @@
1
+ import { ToggleButton } from '../ToggleButton/ToggleButton';
2
+ import { DateRangePicker } from '../DateRangePicker/DateRangePicker';
3
+ import type { PeriodSelectorProps } from './PeriodSelector.types';
4
+
5
+ const PERIOD_OPTIONS = [
6
+ { label: '1M', value: '1m' },
7
+ { label: '3M', value: '3m' },
8
+ { label: '6M', value: '6m' },
9
+ { label: '1Y', value: '1y' },
10
+ { label: 'Custom', value: 'custom' },
11
+ ];
12
+
13
+ export const PeriodSelector = ({
14
+ period,
15
+ onPeriodChange,
16
+ startDate,
17
+ endDate,
18
+ onStartDateChange,
19
+ onEndDateChange,
20
+ disabled = false,
21
+ options = PERIOD_OPTIONS,
22
+ }: PeriodSelectorProps) => {
23
+ return (
24
+ <div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
25
+ <ToggleButton
26
+ options={options}
27
+ value={period}
28
+ onChange={onPeriodChange}
29
+ disabled={disabled}
30
+ size="sm"
31
+ />
32
+ {period === 'custom' && (
33
+ <DateRangePicker
34
+ startDate={startDate || null}
35
+ endDate={endDate || null}
36
+ onChange={({ startDate: newStart, endDate: newEnd }) => {
37
+ onStartDateChange?.(newStart);
38
+ onEndDateChange?.(newEnd);
39
+ }}
40
+ startLabel="From"
41
+ endLabel="To"
42
+ disabled={disabled}
43
+ />
44
+ )}
45
+ </div>
46
+ );
47
+ };
@@ -0,0 +1,10 @@
1
+ export interface PeriodSelectorProps {
2
+ period: string;
3
+ onPeriodChange: (period: string) => void;
4
+ startDate?: Date | null;
5
+ endDate?: Date | null;
6
+ onStartDateChange?: (date: Date | null) => void;
7
+ onEndDateChange?: (date: Date | null) => void;
8
+ disabled?: boolean;
9
+ options?: { label: string; value: string }[];
10
+ }
@@ -0,0 +1,141 @@
1
+ import { useState, useEffect } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { PhoneField } from "./PhoneField";
4
+ import type { PhoneFieldProps, CountryCodeOption } from "./PhoneField.types";
5
+ import { DEFAULT_COUNTRY_OPTIONS } from "./countryCodes";
6
+
7
+ const defaultCountry = DEFAULT_COUNTRY_OPTIONS.find((c) => c.phonecode === 971) ?? DEFAULT_COUNTRY_OPTIONS[0];
8
+
9
+ /** Wrapper to manage value/onChange/country for controlled PhoneField in stories */
10
+ const ControlledPhoneField = (props: PhoneFieldProps) => {
11
+ const [value, setValue] = useState(props.value ?? "");
12
+ const [country, setCountry] = useState<CountryCodeOption | null>(props.countryValue ?? defaultCountry);
13
+ useEffect(() => {
14
+ setValue(props.value ?? "");
15
+ }, [props.value]);
16
+ useEffect(() => {
17
+ setCountry(props.countryValue ?? defaultCountry);
18
+ }, [props.countryValue]);
19
+ return (
20
+ <PhoneField
21
+ {...props}
22
+ countryValue={country}
23
+ onCountryChange={(v) => {
24
+ setCountry(v);
25
+ props.onCountryChange?.(v);
26
+ }}
27
+ value={value}
28
+ onChange={(e) => {
29
+ setValue(e.target.value);
30
+ props.onChange?.(e);
31
+ }}
32
+ />
33
+ );
34
+ };
35
+
36
+ const meta: Meta<typeof PhoneField> = {
37
+ title: "Design System/PhoneField",
38
+ component: ControlledPhoneField,
39
+ tags: [],
40
+ decorators: [
41
+ (Story) => (
42
+ <div style={{ padding: "24px", maxWidth: "420px" }}>
43
+ <Story />
44
+ </div>
45
+ ),
46
+ ],
47
+ args: {
48
+ label: "Phone",
49
+ placeholder: "05x-xxx-xxxx",
50
+ required: true,
51
+ error: false,
52
+ helperText: "",
53
+ size: "md",
54
+ fullWidth: true,
55
+ color: "grey",
56
+ } as PhoneFieldProps,
57
+ argTypes: {
58
+ color: {
59
+ control: { type: "radio" },
60
+ options: ["grey", "white"],
61
+ },
62
+ size: {
63
+ control: { type: "radio" },
64
+ options: ["sm", "md", "lg"],
65
+ },
66
+ value: {
67
+ control: { type: "text" },
68
+ },
69
+ helperText: {
70
+ control: { type: "text" },
71
+ },
72
+ error: {
73
+ control: { type: "boolean" },
74
+ },
75
+ },
76
+ };
77
+
78
+ export default meta;
79
+
80
+ type Story = StoryObj<typeof PhoneField>;
81
+
82
+ export const Playground: Story = {
83
+ args: {
84
+ label: "Phone",
85
+ required: true,
86
+ placeholder: "05x-xxx-xxxx",
87
+ },
88
+ };
89
+
90
+ export const Showcase: Story = {
91
+ render: () => (
92
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
93
+ {/* Default / Empty */}
94
+ <PhoneField
95
+ label="Phone"
96
+ required
97
+ placeholder="05x-xxx-xxxx"
98
+ />
99
+
100
+ {/* With Value */}
101
+ <PhoneField
102
+ label="Phone"
103
+ required
104
+ countryValue={defaultCountry}
105
+ value="501234567"
106
+ onChange={() => {}}
107
+ placeholder="05x-xxx-xxxx"
108
+ />
109
+
110
+ {/* Error State */}
111
+ <PhoneField
112
+ label="Phone"
113
+ required
114
+ value=""
115
+ onChange={() => {}}
116
+ placeholder="05x-xxx-xxxx"
117
+ error
118
+ helperText="Please enter a valid phone number"
119
+ />
120
+
121
+ {/* Disabled */}
122
+ <PhoneField
123
+ label="Phone"
124
+ required
125
+ countryValue={defaultCountry}
126
+ value="501234567"
127
+ onChange={() => {}}
128
+ placeholder="05x-xxx-xxxx"
129
+ disabled
130
+ />
131
+
132
+ {/* Color: white */}
133
+ <PhoneField
134
+ label="Phone (white)"
135
+ required
136
+ placeholder="05x-xxx-xxxx"
137
+ color="white"
138
+ />
139
+ </div>
140
+ ),
141
+ };