@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.
- package/assets/images/noData.webp +0 -0
- package/dist/index.js +15318 -0
- package/dist/index.js.map +1 -0
- package/dist/noData-XPRHKMFR.webp +0 -0
- package/package.json +51 -4
- package/src/ThemeProvider/ThemeContext.tsx +75 -0
- package/src/ThemeProvider/index.ts +7 -0
- package/src/assets/images/noData.webp +0 -0
- package/src/assets.d.ts +10 -0
- package/src/components/Accordion/Accordion.stories.tsx +64 -0
- package/src/components/Accordion/Accordion.tsx +219 -0
- package/src/components/Accordion/Accordion.types.ts +16 -0
- package/src/components/Avatar/Avatar.stories.tsx +304 -0
- package/src/components/Avatar/Avatar.tsx +211 -0
- package/src/components/Avatar/Avatar.types.ts +33 -0
- package/src/components/Badge/Badge.stories.tsx +143 -0
- package/src/components/Badge/Badge.tsx +138 -0
- package/src/components/Badge/Badge.types.ts +24 -0
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
- package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
- package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
- package/src/components/Button/Button.stories.tsx +457 -0
- package/src/components/Button/Button.tsx +202 -0
- package/src/components/Button/Button.types.ts +18 -0
- package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
- package/src/components/Checkbox/Checkbox.tsx +352 -0
- package/src/components/Checkbox/Checkbox.types.ts +123 -0
- package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
- package/src/components/CloseButton/CloseButton.tsx +117 -0
- package/src/components/CloseButton/CloseButton.types.ts +15 -0
- package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
- package/src/components/DataTable/DataTable.stories.tsx +233 -0
- package/src/components/DataTable/DataTable.tsx +968 -0
- package/src/components/DataTable/DataTable.types.ts +49 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
- package/src/components/DatePicker/DatePicker.tsx +669 -0
- package/src/components/DatePicker/DatePicker.types.ts +105 -0
- package/src/components/DatePicker/DateTimePicker.tsx +35 -0
- package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
- package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
- package/src/components/Drawer/Drawer.stories.tsx +243 -0
- package/src/components/Drawer/Drawer.tsx +196 -0
- package/src/components/Drawer/Drawer.types.ts +60 -0
- package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
- package/src/components/FileUpload/FileUpload.tsx +216 -0
- package/src/components/FileUpload/FileUpload.types.ts +80 -0
- package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
- package/src/components/FilterPopover/FilterPopover.tsx +310 -0
- package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
- package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
- package/src/components/ImageViewer/useImagePanRotate.js +102 -0
- package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
- package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
- package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
- package/src/components/Loader/Loader.stories.tsx +95 -0
- package/src/components/Loader/Loader.tsx +132 -0
- package/src/components/Loader/Loader.types.ts +6 -0
- package/src/components/Menu/Menu.tsx +143 -0
- package/src/components/Menu/Menu.types.ts +28 -0
- package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
- package/src/components/MenuButton/MenuButton.tsx +45 -0
- package/src/components/MenuButton/MenuButton.types.ts +21 -0
- package/src/components/Modal/Modal.stories.tsx +266 -0
- package/src/components/Modal/Modal.tsx +399 -0
- package/src/components/Modal/index.ts +2 -0
- package/src/components/Modal/types.ts +126 -0
- package/src/components/Pagination/Pagination.stories.tsx +167 -0
- package/src/components/Pagination/Pagination.tsx +266 -0
- package/src/components/Pagination/Pagination.types.ts +20 -0
- package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
- package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
- package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
- package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
- package/src/components/PhoneField/PhoneField.tsx +284 -0
- package/src/components/PhoneField/PhoneField.types.ts +89 -0
- package/src/components/PhoneField/countryCodes.ts +236 -0
- package/src/components/Progress/Progress.stories.tsx +83 -0
- package/src/components/Progress/Progress.tsx +91 -0
- package/src/components/Progress/Progress.types.ts +22 -0
- package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
- package/src/components/RadioButton/RadioButton.tsx +427 -0
- package/src/components/RadioButton/RadioButton.types.ts +117 -0
- package/src/components/ReportCard/ReportCard.tsx +183 -0
- package/src/components/ReportCard/ReportCard.types.ts +17 -0
- package/src/components/SectionIcon/SectionIcon.tsx +87 -0
- package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
- package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
- package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
- package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
- package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
- package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
- package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
- package/src/components/Selector/Selector.stories.tsx +382 -0
- package/src/components/Selector/Selector.tsx +1310 -0
- package/src/components/Selector/Selector.types.ts +71 -0
- package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
- package/src/components/Skeleton/Skeleton.tsx +20 -0
- package/src/components/Skeleton/Skeleton.types.ts +3 -0
- package/src/components/Spinner/Spinner.stories.tsx +84 -0
- package/src/components/Spinner/Spinner.tsx +79 -0
- package/src/components/Spinner/Spinner.types.ts +8 -0
- package/src/components/StatCard/StatCard.stories.tsx +125 -0
- package/src/components/StatCard/StatCard.tsx +167 -0
- package/src/components/StatCard/StatCard.types.ts +20 -0
- package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
- package/src/components/StatusBadge/StatusBadge.tsx +74 -0
- package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
- package/src/components/Stepper/Stepper.stories.tsx +85 -0
- package/src/components/Stepper/Stepper.tsx +138 -0
- package/src/components/Stepper/Stepper.types.ts +16 -0
- package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
- package/src/components/SubTabs/SubTabs.tsx +279 -0
- package/src/components/SubTabs/SubTabs.types.ts +44 -0
- package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
- package/src/components/SummaryCard/SummaryCard.tsx +141 -0
- package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
- package/src/components/Switch/Switch.stories.tsx +59 -0
- package/src/components/Switch/Switch.tsx +217 -0
- package/src/components/Switch/Switch.types.ts +22 -0
- package/src/components/Tabs/Tabs.stories.tsx +178 -0
- package/src/components/Tabs/Tabs.tsx +197 -0
- package/src/components/Tabs/Tabs.types.ts +34 -0
- package/src/components/TextField/TextField.stories.tsx +249 -0
- package/src/components/TextField/TextField.tsx +462 -0
- package/src/components/TextField/TextField.types.ts +141 -0
- package/src/components/Toast/Toast.stories.tsx +237 -0
- package/src/components/Toast/Toast.tsx +246 -0
- package/src/components/Toast/Toast.types.ts +31 -0
- package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
- package/src/components/ToggleButton/ToggleButton.tsx +267 -0
- package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
- package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
- package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
- package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
- package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
- package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
- package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
- package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
- package/src/components/index.ts +157 -0
- package/src/icons/ReportDollarIcon.tsx +71 -0
- package/src/icons/index.ts +27 -0
- package/src/index.ts +214 -0
- package/src/storybook/preview.tsx +64 -0
- package/src/theme/index.ts +4 -0
- package/src/theme/types.d.ts +1 -0
- package/src/themes/cpc.ts +4 -0
- package/src/themes/firebird.ts +4 -0
- package/src/tokens/brands/cpc/themes/dark.json +82 -0
- package/src/tokens/brands/cpc/themes/light.json +1 -0
- package/src/tokens/brands/default/brand.json +20 -0
- package/src/tokens/brands/firebird/themes/dark.json +28 -0
- package/src/tokens/brands/firebird/themes/light.json +20 -0
- package/src/tokens/button.ts +24 -0
- package/src/tokens/components/accordion.json +52 -0
- package/src/tokens/components/avatar.json +40 -0
- package/src/tokens/components/badge.json +41 -0
- package/src/tokens/components/breadcrumb.json +13 -0
- package/src/tokens/components/button.json +67 -0
- package/src/tokens/components/card.json +39 -0
- package/src/tokens/components/checkbox.json +64 -0
- package/src/tokens/components/closebutton.json +55 -0
- package/src/tokens/components/datatable.json +40 -0
- package/src/tokens/components/datepicker.json +99 -0
- package/src/tokens/components/drawer.json +23 -0
- package/src/tokens/components/field.json +29 -0
- package/src/tokens/components/fileupload.json +57 -0
- package/src/tokens/components/filterpopover.json +29 -0
- package/src/tokens/components/input.json +31 -0
- package/src/tokens/components/ipwhitelistfield.json +16 -0
- package/src/tokens/components/loader.json +15 -0
- package/src/tokens/components/menu.json +21 -0
- package/src/tokens/components/modal.json +25 -0
- package/src/tokens/components/pagination.json +45 -0
- package/src/tokens/components/phonefield.json +41 -0
- package/src/tokens/components/progress.json +12 -0
- package/src/tokens/components/radio.json +87 -0
- package/src/tokens/components/reportcard.json +17 -0
- package/src/tokens/components/selector.json +87 -0
- package/src/tokens/components/selectscroll.json +65 -0
- package/src/tokens/components/skeleton.json +6 -0
- package/src/tokens/components/stepper.json +38 -0
- package/src/tokens/components/subtabs.json +45 -0
- package/src/tokens/components/switch.json +55 -0
- package/src/tokens/components/tabs.json +38 -0
- package/src/tokens/components/textfield.json +59 -0
- package/src/tokens/components/togglebutton.json +48 -0
- package/src/tokens/components/uploaddropzone.json +61 -0
- package/src/tokens/drawer.ts +37 -0
- package/src/tokens/foundation/breakpoints.json +10 -0
- package/src/tokens/foundation/colors.json +122 -0
- package/src/tokens/foundation/opacity.json +16 -0
- package/src/tokens/foundation/radius.json +14 -0
- package/src/tokens/foundation/size.json +38 -0
- package/src/tokens/foundation/spacing.json +19 -0
- package/src/tokens/foundation/typography.json +43 -0
- package/src/tokens/index.ts +3665 -0
- package/src/tokens/modal.ts +42 -0
- package/src/tokens/semantic/color.json +148 -0
- package/src/tokens/semantic/field.json +28 -0
- package/src/tokens/semantic/opacity.json +5 -0
- package/src/tokens/semantic/size.json +722 -0
- package/src/tokens/semantic/spacing.json +27 -0
- package/src/tokens/semantic/typography.json +87 -0
- package/src/utils/scrollLock.ts +44 -0
- package/README.md +0 -3
|
@@ -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
|
+
};
|