@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,284 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import type { PhoneFieldProps, CountryCodeOption } from './PhoneField.types';
3
+ import Selector from '../Selector/Selector';
4
+ import { TextField } from '../TextField/TextField';
5
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
6
+ import { getPhoneFieldTokensFromResolved } from '../../tokens';
7
+ import { DEFAULT_COUNTRY_OPTIONS } from './countryCodes';
8
+
9
+ function getDialCode(option: CountryCodeOption): number | string | null {
10
+ const phonecode = option.phonecode;
11
+ if (phonecode != null) return phonecode;
12
+ const phoneCode = (option as CountryCodeOption & { phoneCode?: unknown }).phoneCode;
13
+ if (phoneCode != null && phoneCode !== '') return phoneCode as number | string;
14
+ return null;
15
+ }
16
+
17
+ function getCountryCodeDisplay(option: CountryCodeOption): string {
18
+ const code = getDialCode(option);
19
+ return code != null ? `+${code}` : '';
20
+ }
21
+
22
+ function getCountryOptionLabel(option: CountryCodeOption): string {
23
+ if (typeof option === 'string') return option;
24
+ const codeLabel = getCountryCodeDisplay(option);
25
+ const countryLabel = option.nicename ?? option.name ?? (option as { label?: string }).label ?? '';
26
+ return codeLabel || countryLabel;
27
+ }
28
+
29
+ export const PhoneField = ({
30
+ label = 'Phone',
31
+ required = false,
32
+ countryOptions = DEFAULT_COUNTRY_OPTIONS,
33
+ countryValue,
34
+ onCountryChange,
35
+ value,
36
+ onChange,
37
+ placeholder = '05x-xxx-xxxx',
38
+ disabled = false,
39
+ countryDisabled = false,
40
+ error = false,
41
+ helperText,
42
+ size = 'md',
43
+ color = 'grey',
44
+ id,
45
+ name,
46
+ fullWidth = true,
47
+ clearable = false,
48
+ }: PhoneFieldProps) => {
49
+ const [internalCountry, setInternalCountry] = useState<CountryCodeOption | null>(
50
+ countryValue !== undefined ? countryValue : null
51
+ );
52
+ const [internalPhone, setInternalPhone] = useState(value ?? '');
53
+ const combinedFieldRef = useRef<HTMLDivElement>(null);
54
+ const [listboxMinWidth, setListboxMinWidth] = useState(0);
55
+ const resolvedTokens = useTokens();
56
+ const tokens = getPhoneFieldTokensFromResolved(resolvedTokens);
57
+
58
+ useEffect(() => {
59
+ if (value !== undefined) {
60
+ setInternalPhone(value);
61
+ }
62
+ }, [value]);
63
+
64
+ useEffect(() => {
65
+ if (countryValue !== undefined) {
66
+ setInternalCountry(countryValue);
67
+ }
68
+ }, [countryValue]);
69
+
70
+ useEffect(() => {
71
+ const updateListboxWidth = () => {
72
+ const width = combinedFieldRef.current?.getBoundingClientRect().width ?? 0;
73
+ setListboxMinWidth(width > 0 ? Math.ceil(width) : 0);
74
+ };
75
+
76
+ updateListboxWidth();
77
+
78
+ if (typeof ResizeObserver !== 'undefined' && combinedFieldRef.current) {
79
+ const observer = new ResizeObserver(() => {
80
+ updateListboxWidth();
81
+ });
82
+ observer.observe(combinedFieldRef.current);
83
+ return () => observer.disconnect();
84
+ }
85
+
86
+ window.addEventListener('resize', updateListboxWidth);
87
+ return () => window.removeEventListener('resize', updateListboxWidth);
88
+ }, []);
89
+
90
+ const currentCountry = countryValue !== undefined ? countryValue : internalCountry;
91
+
92
+ // Truncate phone when country changes and current value exceeds new msisdnLength
93
+ useEffect(() => {
94
+ if (!currentCountry || typeof currentCountry !== 'object') return;
95
+ const maxLen = currentCountry.msisdnLength ?? 15;
96
+ const current = value !== undefined ? value : internalPhone;
97
+ const digitsOnly = String(current).replace(/\D/g, '');
98
+ if (digitsOnly.length > maxLen) {
99
+ const truncated = digitsOnly.slice(0, maxLen);
100
+ if (value === undefined) {
101
+ setInternalPhone(truncated);
102
+ }
103
+ onChange?.({
104
+ target: { value: truncated },
105
+ } as React.ChangeEvent<HTMLInputElement>);
106
+ }
107
+ }, [currentCountry, value, internalPhone, onChange]);
108
+ const currentPhone = value !== undefined ? value : internalPhone;
109
+ const msisdnLength =
110
+ currentCountry && typeof currentCountry === 'object' ? (currentCountry.msisdnLength ?? 15) : 15;
111
+
112
+ const errorMessage = typeof error === 'string' ? error : undefined;
113
+ const hasError = !!error;
114
+ const displayHelperText = errorMessage ?? helperText;
115
+
116
+ const handlePhoneChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
117
+ const raw = e.target.value;
118
+ const digitsOnly = raw.replace(/\D/g, '');
119
+ const truncated = digitsOnly.slice(0, msisdnLength);
120
+ const newValue = truncated;
121
+ if (value === undefined) setInternalPhone(newValue);
122
+ e.target.value = newValue;
123
+ onChange?.(e as React.ChangeEvent<HTMLInputElement>);
124
+ };
125
+
126
+ const labelStyle: React.CSSProperties = {
127
+ display: 'block',
128
+ marginBottom: parseInt(tokens.labelMarginBottom, 10),
129
+ fontSize: tokens.labelFontSize,
130
+ fontWeight: tokens.labelFontWeight,
131
+ color: tokens.labelColor,
132
+ fontFamily: tokens.fontFamily,
133
+ opacity: disabled ? parseFloat(tokens.disabledOpacity) : 1,
134
+ };
135
+
136
+ const borderRadius =
137
+ size === 'sm' ? tokens.borderRadiusSm
138
+ : size === 'lg' ? tokens.borderRadiusLg
139
+ : tokens.borderRadiusMd;
140
+ const selectorWidth =
141
+ size === 'sm' ? tokens.selectorWidthSm
142
+ : size === 'lg' ? tokens.selectorWidthLg
143
+ : tokens.selectorWidthMd;
144
+
145
+ const containerStyle: React.CSSProperties = {
146
+ display: 'flex',
147
+ width: fullWidth ? '100%' : undefined,
148
+ borderRadius,
149
+ border: hasError ? tokens.outlineError : tokens.outlineDefault,
150
+ boxSizing: 'border-box',
151
+ };
152
+
153
+ const selectorBackground =
154
+ color === 'white' ? tokens.selectorBackgroundWhite : tokens.selectorBackgroundWhite;
155
+ const selectorWrapperStyle: React.CSSProperties = {
156
+ flex: '0 0 auto',
157
+ width: parseInt(selectorWidth, 10),
158
+ minWidth: parseInt(selectorWidth, 10),
159
+ backgroundColor: selectorBackground,
160
+ borderRight: tokens.selectorBorderRight,
161
+ borderTopLeftRadius: borderRadius,
162
+ borderBottomLeftRadius: borderRadius,
163
+ };
164
+
165
+ const textFieldWrapperStyle: React.CSSProperties = {
166
+ flex: 1,
167
+ minWidth: 0,
168
+ borderTopRightRadius: borderRadius,
169
+ borderBottomRightRadius: borderRadius,
170
+ };
171
+
172
+ const phoneInputId =
173
+ id ??
174
+ name ??
175
+ (label ? `phone-${String(label).replace(/\s+/g, '-').toLowerCase()}` : 'phone-field');
176
+
177
+ return (
178
+ <div className="phone-field-root" style={{ width: fullWidth ? '100%' : undefined }}>
179
+ <style>
180
+ {`.phone-field-root .phone-field-selector input {
181
+ border: 1px solid #c7ced6 !important;
182
+ border-radius: ${borderRadius} 0 0 ${borderRadius} !important;
183
+ box-shadow: none !important;
184
+ background-color: ${selectorBackground} !important;
185
+ }
186
+ .phone-field-root .phone-field-textfield input {
187
+ border: 1px solid #c7ced6 !important;
188
+ border-radius: 0 ${borderRadius} ${borderRadius} 0 !important;
189
+ box-shadow: none !important;
190
+ }`}
191
+ </style>
192
+ {label != null && label !== '' && (
193
+ <label htmlFor={phoneInputId} style={labelStyle}>
194
+ {label}
195
+ {required && <span style={{ color: tokens.labelRequiredColor, marginRight: 4 }}>*</span>}
196
+ </label>
197
+ )}
198
+ <div ref={combinedFieldRef} style={containerStyle}>
199
+ <div className="phone-field-selector" style={selectorWrapperStyle}>
200
+ <Selector<CountryCodeOption>
201
+ options={countryOptions}
202
+ value={currentCountry}
203
+ onChange={(v) => {
204
+ const nextCountry = (v as CountryCodeOption | null) ?? null;
205
+ if (countryValue === undefined) {
206
+ setInternalCountry(nextCountry);
207
+ }
208
+ onCountryChange?.(nextCountry);
209
+ }}
210
+ label=""
211
+ placeholder="Code"
212
+ required={false}
213
+ disabled={disabled || countryDisabled}
214
+ size={size}
215
+ color={color}
216
+ showFlag={true}
217
+ autoComplete="new-password"
218
+ getOptionLabel={getCountryOptionLabel}
219
+ renderOption={(opt) => {
220
+ if (typeof opt === 'string') return opt;
221
+ const code = getCountryCodeDisplay(opt);
222
+ const displayName = opt.nicename ?? opt.name ?? '';
223
+ return (
224
+ <span>
225
+ <span>{code}</span>
226
+ {displayName && (
227
+ <span style={{ marginLeft: 6, opacity: 0.7 }}>{displayName}</span>
228
+ )}
229
+ </span>
230
+ );
231
+ }}
232
+ filterOptions={(options, state) => {
233
+ const query = state.inputValue.toLowerCase();
234
+ return options.filter((opt) => {
235
+ const code = getCountryCodeDisplay(opt).toLowerCase();
236
+ const name = (opt.name || '').toLowerCase();
237
+ const nicename = (opt.nicename || '').toLowerCase();
238
+ return code.includes(query) || name.includes(query) || nicename.includes(query);
239
+ });
240
+ }}
241
+ clearable={clearable}
242
+ listboxMinWidth={listboxMinWidth}
243
+ id={`${phoneInputId}-country`}
244
+ />
245
+ </div>
246
+ <div className="phone-field-textfield" style={textFieldWrapperStyle}>
247
+ <TextField
248
+ id={phoneInputId}
249
+ name={name}
250
+ label=""
251
+ value={currentPhone}
252
+ onChange={handlePhoneChange}
253
+ placeholder={placeholder}
254
+ disabled={disabled || !currentCountry || Object.keys(currentCountry).length === 0}
255
+ error={hasError}
256
+ size={size}
257
+ color={color}
258
+ fullWidth
259
+ type="tel"
260
+ inputProps={{
261
+ inputMode: 'numeric',
262
+ pattern: '[0-9]*',
263
+ maxLength: msisdnLength,
264
+ }}
265
+ />
266
+ </div>
267
+ </div>
268
+ {displayHelperText != null && displayHelperText !== '' && (
269
+ <span
270
+ style={{
271
+ display: 'block',
272
+ marginTop: 4,
273
+ fontSize: 12,
274
+ color: hasError ? tokens.labelRequiredColor : tokens.labelColor,
275
+ fontFamily: tokens.fontFamily,
276
+ }}
277
+ role={hasError ? 'alert' : undefined}
278
+ >
279
+ {displayHelperText}
280
+ </span>
281
+ )}
282
+ </div>
283
+ );
284
+ };
@@ -0,0 +1,89 @@
1
+ export interface CountryCodeOption {
2
+ [key: string]: unknown;
3
+ id?: number;
4
+ iso?: string;
5
+ name?: string;
6
+ nicename?: string;
7
+ iso3?: string;
8
+ numcode?: number;
9
+ /** Country dial code (e.g. 971 for UAE). Displayed as +971 */
10
+ phonecode: number;
11
+ currency?: string;
12
+ /** Max length for the local phone number (digits only). Used to limit input. */
13
+ msisdnLength: number;
14
+ }
15
+
16
+ export interface PhoneFieldProps {
17
+ /**
18
+ * Field label (e.g. "Phone")
19
+ */
20
+ label?: string;
21
+ /**
22
+ * Whether the field is required (shows asterisk)
23
+ */
24
+ required?: boolean;
25
+ /**
26
+ * Country code options for the selector
27
+ */
28
+ countryOptions?: CountryCodeOption[];
29
+ /**
30
+ * Selected country code option
31
+ */
32
+ countryValue?: CountryCodeOption | null;
33
+ /**
34
+ * Callback when country selection changes
35
+ */
36
+ onCountryChange?: (value: CountryCodeOption | null) => void;
37
+ /**
38
+ * Phone number value
39
+ */
40
+ value?: string;
41
+ /**
42
+ * Callback when phone number changes
43
+ */
44
+ onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
45
+ /**
46
+ * Placeholder for the phone number input (e.g. "05x-xxx-xxxx")
47
+ */
48
+ placeholder?: string;
49
+ /**
50
+ * Disables the field
51
+ */
52
+ disabled?: boolean;
53
+ /**
54
+ * Disables only the country code selector
55
+ */
56
+ countryDisabled?: boolean;
57
+ /**
58
+ * Error message or state
59
+ */
60
+ error?: string | boolean;
61
+ /**
62
+ * Helper text below the field
63
+ */
64
+ helperText?: string;
65
+ /**
66
+ * Size of the inputs
67
+ */
68
+ size?: 'sm' | 'md' | 'lg';
69
+ /**
70
+ * Background color variant
71
+ */
72
+ color?: 'grey' | 'white';
73
+ /**
74
+ * HTML id for the phone input
75
+ */
76
+ id?: string;
77
+ /**
78
+ * Input name for the phone number
79
+ */
80
+ name?: string;
81
+ /**
82
+ * Full width of the container
83
+ */
84
+ fullWidth?: boolean;
85
+ /**
86
+ * Whether the country selector is clearable
87
+ */
88
+ clearable?: boolean;
89
+ }
@@ -0,0 +1,236 @@
1
+ import type { CountryCodeOption } from "./PhoneField.types";
2
+
3
+ /** Default country code options for PhoneField (API format with msisdnLength for input validation) */
4
+ export const DEFAULT_COUNTRY_OPTIONS: CountryCodeOption[] = [
5
+ {
6
+ id: 2,
7
+ iso: "AE",
8
+ name: "UAE",
9
+ nicename: "United Arab Emirates",
10
+ iso3: "ARE",
11
+ numcode: 784,
12
+ phonecode: 971,
13
+ currency: "AED",
14
+ msisdnLength: 9,
15
+ },
16
+ {
17
+ id: 1,
18
+ iso: "AF",
19
+ name: "AFGHANISTAN",
20
+ nicename: "Afghanistan",
21
+ iso3: "AFG",
22
+ numcode: 4,
23
+ phonecode: 93,
24
+ currency: "AFN",
25
+ msisdnLength: 9,
26
+ },
27
+ {
28
+ id: 3,
29
+ iso: "IN",
30
+ name: "INDIA",
31
+ nicename: "India",
32
+ iso3: "IND",
33
+ numcode: 356,
34
+ phonecode: 91,
35
+ currency: "INR",
36
+ msisdnLength: 10,
37
+ },
38
+ {
39
+ id: 4,
40
+ iso: "US",
41
+ name: "US",
42
+ nicename: "United States",
43
+ iso3: "USA",
44
+ numcode: 840,
45
+ phonecode: 1,
46
+ currency: "USD",
47
+ msisdnLength: 10,
48
+ },
49
+ {
50
+ id: 5,
51
+ iso: "GB",
52
+ name: "UK",
53
+ nicename: "United Kingdom",
54
+ iso3: "GBR",
55
+ numcode: 826,
56
+ phonecode: 44,
57
+ currency: "GBP",
58
+ msisdnLength: 10,
59
+ },
60
+ {
61
+ id: 6,
62
+ iso: "SA",
63
+ name: "SAUDI ARABIA",
64
+ nicename: "Saudi Arabia",
65
+ iso3: "SAU",
66
+ numcode: 682,
67
+ phonecode: 966,
68
+ currency: "SAR",
69
+ msisdnLength: 9,
70
+ },
71
+ {
72
+ id: 7,
73
+ iso: "BH",
74
+ name: "BAHRAIN",
75
+ nicename: "Bahrain",
76
+ iso3: "BHR",
77
+ numcode: 48,
78
+ phonecode: 973,
79
+ currency: "BHD",
80
+ msisdnLength: 8,
81
+ },
82
+ {
83
+ id: 8,
84
+ iso: "QA",
85
+ name: "QATAR",
86
+ nicename: "Qatar",
87
+ iso3: "QAT",
88
+ numcode: 634,
89
+ phonecode: 974,
90
+ currency: "QAR",
91
+ msisdnLength: 8,
92
+ },
93
+ {
94
+ id: 9,
95
+ iso: "OM",
96
+ name: "OMAN",
97
+ nicename: "Oman",
98
+ iso3: "OMN",
99
+ numcode: 512,
100
+ phonecode: 968,
101
+ currency: "OMR",
102
+ msisdnLength: 8,
103
+ },
104
+ {
105
+ id: 10,
106
+ iso: "KW",
107
+ name: "KUWAIT",
108
+ nicename: "Kuwait",
109
+ iso3: "KWT",
110
+ numcode: 414,
111
+ phonecode: 965,
112
+ currency: "KWD",
113
+ msisdnLength: 8,
114
+ },
115
+ {
116
+ id: 11,
117
+ iso: "EG",
118
+ name: "EGYPT",
119
+ nicename: "Egypt",
120
+ iso3: "EGY",
121
+ numcode: 818,
122
+ phonecode: 20,
123
+ currency: "EGP",
124
+ msisdnLength: 10,
125
+ },
126
+ {
127
+ id: 12,
128
+ iso: "PK",
129
+ name: "PAKISTAN",
130
+ nicename: "Pakistan",
131
+ iso3: "PAK",
132
+ numcode: 586,
133
+ phonecode: 92,
134
+ currency: "PKR",
135
+ msisdnLength: 10,
136
+ },
137
+ {
138
+ id: 13,
139
+ iso: "AU",
140
+ name: "AUSTRALIA",
141
+ nicename: "Australia",
142
+ iso3: "AUS",
143
+ numcode: 36,
144
+ phonecode: 61,
145
+ currency: "AUD",
146
+ msisdnLength: 9,
147
+ },
148
+ {
149
+ id: 14,
150
+ iso: "DE",
151
+ name: "GERMANY",
152
+ nicename: "Germany",
153
+ iso3: "DEU",
154
+ numcode: 276,
155
+ phonecode: 49,
156
+ currency: "EUR",
157
+ msisdnLength: 11,
158
+ },
159
+ {
160
+ id: 15,
161
+ iso: "FR",
162
+ name: "FRANCE",
163
+ nicename: "France",
164
+ iso3: "FRA",
165
+ numcode: 250,
166
+ phonecode: 33,
167
+ currency: "EUR",
168
+ msisdnLength: 9,
169
+ },
170
+ {
171
+ id: 16,
172
+ iso: "JP",
173
+ name: "JAPAN",
174
+ nicename: "Japan",
175
+ iso3: "JPN",
176
+ numcode: 392,
177
+ phonecode: 81,
178
+ currency: "JPY",
179
+ msisdnLength: 10,
180
+ },
181
+ {
182
+ id: 17,
183
+ iso: "CN",
184
+ name: "CHINA",
185
+ nicename: "China",
186
+ iso3: "CHN",
187
+ numcode: 156,
188
+ phonecode: 86,
189
+ currency: "CNY",
190
+ msisdnLength: 11,
191
+ },
192
+ {
193
+ id: 18,
194
+ iso: "RU",
195
+ name: "RUSSIA",
196
+ nicename: "Russia",
197
+ iso3: "RUS",
198
+ numcode: 643,
199
+ phonecode: 7,
200
+ currency: "RUB",
201
+ msisdnLength: 10,
202
+ },
203
+ {
204
+ id: 19,
205
+ iso: "ZA",
206
+ name: "SOUTH AFRICA",
207
+ nicename: "South Africa",
208
+ iso3: "ZAF",
209
+ numcode: 710,
210
+ phonecode: 27,
211
+ currency: "ZAR",
212
+ msisdnLength: 9,
213
+ },
214
+ {
215
+ id: 20,
216
+ iso: "BR",
217
+ name: "BRAZIL",
218
+ nicename: "Brazil",
219
+ iso3: "BRA",
220
+ numcode: 76,
221
+ phonecode: 55,
222
+ currency: "BRL",
223
+ msisdnLength: 11,
224
+ },
225
+ {
226
+ id: 21,
227
+ iso: "NG",
228
+ name: "NIGERIA",
229
+ nicename: "Nigeria",
230
+ iso3: "NGA",
231
+ numcode: 566,
232
+ phonecode: 234,
233
+ currency: "NGN",
234
+ msisdnLength: 10,
235
+ },
236
+ ];
@@ -0,0 +1,83 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Progress } from "./Progress";
4
+
5
+ const meta: Meta<typeof Progress> = {
6
+ title: "Design System/Progress",
7
+ component: Progress,
8
+ tags: ["autodocs"],
9
+ decorators: [
10
+ (Story) => (
11
+ <div style={{ padding: "24px", width: "420px", maxWidth: "100%" }}>
12
+ <Story />
13
+ </div>
14
+ ),
15
+ ],
16
+ args: {
17
+ value: 50,
18
+ variant: "determinate",
19
+ },
20
+ argTypes: {
21
+ value: {
22
+ control: { type: "range", min: 0, max: 100 },
23
+ description: "Progress value (0-100) for determinate variant",
24
+ },
25
+ color: {
26
+ control: "color",
27
+ description: "Custom color for the progress bar filler",
28
+ },
29
+ variant: {
30
+ control: "radio",
31
+ options: ["determinate", "indeterminate"],
32
+ description: "Type of progress bar",
33
+ },
34
+ },
35
+ };
36
+
37
+ export default meta;
38
+
39
+ type Story = StoryObj<typeof Progress>;
40
+
41
+ export const Playground: Story = {
42
+ args: {
43
+ value: 50,
44
+ variant: "determinate",
45
+ },
46
+ };
47
+
48
+ const ShowcaseComponent = () => {
49
+ return (
50
+ <div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
51
+ {/* Determinate States */}
52
+ <div>
53
+ <h4 style={{ marginBottom: "16px", color: "#666" }}>Determinate (0 to 100)</h4>
54
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
55
+ <Progress value={0} />
56
+ <Progress value={25} />
57
+ <Progress value={50} />
58
+ <Progress value={75} />
59
+ <Progress value={100} />
60
+ </div>
61
+ </div>
62
+
63
+ {/* Indeterminate State */}
64
+ <div>
65
+ <h4 style={{ marginBottom: "16px", color: "#666" }}>Indeterminate</h4>
66
+ <Progress variant="indeterminate" />
67
+ </div>
68
+
69
+ {/* Custom Colors */}
70
+ <div>
71
+ <h4 style={{ marginBottom: "16px", color: "#666" }}>Custom Colors</h4>
72
+ <div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
73
+ <Progress value={60} color="#E91E63" />
74
+ <Progress value={80} color="#9C27B0" />
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export const Showcase: Story = {
82
+ render: () => <ShowcaseComponent />,
83
+ };