@youngonesworks/ui 0.1.116 → 0.1.118
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/dist/components/phoneInput/PhoneInput.stories.d.ts +9 -0
- package/dist/components/phoneInput/index.d.ts +15 -0
- package/dist/components/select/index.d.ts +1 -1
- package/dist/hooks/phone/usePhoneNumber.d.ts +13 -0
- package/dist/hooks/phone/usePhoneNumberPrefix.d.ts +1 -0
- package/dist/index.cjs +267 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +267 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export { TruncatedText } from './components/truncatedText';
|
|
|
61
61
|
export { UnorderedList } from './components/unorderedList';
|
|
62
62
|
export { UnorderedListItem } from './components/unorderedListItem';
|
|
63
63
|
export { UnstyledButton } from './components/unstyledButton';
|
|
64
|
+
export { PhoneInput } from './components/phoneInput';
|
|
64
65
|
export { ProfileMenu } from './components/profileMenu';
|
|
65
66
|
export { type IWysiwygEditorImperativeHandle, WysiwygEditor } from './components/wysiwygEditor';
|
|
66
67
|
export * from './theme';
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,8 @@ import { addMonths, format, setMonth } from "date-fns";
|
|
|
13
13
|
import { enGB, fr, nl, nlBE } from "date-fns/locale";
|
|
14
14
|
import { createPortal } from "react-dom";
|
|
15
15
|
import { Tooltip as Tooltip$1 } from "react-tooltip";
|
|
16
|
+
import phone from "phone";
|
|
17
|
+
import CountryList from "country-list-with-dial-code-and-flag";
|
|
16
18
|
import { Placeholder } from "@tiptap/extension-placeholder";
|
|
17
19
|
import { Underline } from "@tiptap/extension-underline";
|
|
18
20
|
import { EditorContent, useEditor } from "@tiptap/react";
|
|
@@ -2449,9 +2451,7 @@ function Select({ id, options, placeholder, label, errorText, hideError = false,
|
|
|
2449
2451
|
case "Enter":
|
|
2450
2452
|
case " ":
|
|
2451
2453
|
event.preventDefault();
|
|
2452
|
-
if (focusedIndex >= 0 && filteredOptions[focusedIndex])
|
|
2453
|
-
handleSelect(filteredOptions[focusedIndex].value);
|
|
2454
|
-
}
|
|
2454
|
+
if (focusedIndex >= 0 && filteredOptions[focusedIndex]) handleSelect(filteredOptions[focusedIndex].value);
|
|
2455
2455
|
break;
|
|
2456
2456
|
}
|
|
2457
2457
|
}, [
|
|
@@ -3056,6 +3056,269 @@ const UnorderedListItem = ({ children, actionItem, className, header = false,...
|
|
|
3056
3056
|
})]
|
|
3057
3057
|
});
|
|
3058
3058
|
|
|
3059
|
+
//#endregion
|
|
3060
|
+
//#region src/hooks/phone/usePhoneNumber.ts
|
|
3061
|
+
function usePhoneNumber() {
|
|
3062
|
+
/**
|
|
3063
|
+
* Validates a phone number and returns parsing results
|
|
3064
|
+
*/
|
|
3065
|
+
const validatePhone = useCallback((phoneNumber, options) => phone(phoneNumber, options), []);
|
|
3066
|
+
/**
|
|
3067
|
+
* Strips the country code from a phone number
|
|
3068
|
+
* Example: +85265698900 -> 65698900
|
|
3069
|
+
*/
|
|
3070
|
+
const stripCountryCode = useCallback((phoneNumber) => {
|
|
3071
|
+
const result = phone(phoneNumber);
|
|
3072
|
+
if (!result.isValid) return phoneNumber;
|
|
3073
|
+
return result.phoneNumber.replace(result.countryCode, "");
|
|
3074
|
+
}, []);
|
|
3075
|
+
/**
|
|
3076
|
+
* Returns the country code from a phone number
|
|
3077
|
+
* Example: +85265698900 -> +852
|
|
3078
|
+
*/
|
|
3079
|
+
const getCountryCode = useCallback((phoneNumber) => {
|
|
3080
|
+
const result = phone(phoneNumber);
|
|
3081
|
+
if (!result.isValid) return phoneNumber;
|
|
3082
|
+
return result.countryCode;
|
|
3083
|
+
}, []);
|
|
3084
|
+
/**
|
|
3085
|
+
* Formats a phone number to international format with country code
|
|
3086
|
+
* Example: 0648711212 (with country: 'NL') -> +31648711212
|
|
3087
|
+
*/
|
|
3088
|
+
const formatToInternational = useCallback((phoneNumber, options) => {
|
|
3089
|
+
const result = phone(phoneNumber, options);
|
|
3090
|
+
if (!result.isValid) return phoneNumber;
|
|
3091
|
+
return result.phoneNumber;
|
|
3092
|
+
}, []);
|
|
3093
|
+
return {
|
|
3094
|
+
validatePhone,
|
|
3095
|
+
stripCountryCode,
|
|
3096
|
+
getCountryCode,
|
|
3097
|
+
formatToInternational
|
|
3098
|
+
};
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3101
|
+
//#endregion
|
|
3102
|
+
//#region src/hooks/phone/usePhoneNumberPrefix.ts
|
|
3103
|
+
const usePhoneNumberPrefix = (defaultCountry) => {
|
|
3104
|
+
const countryList = CountryList.getAll();
|
|
3105
|
+
return countryList.filter((country) => country.dial_code).sort((a, b) => {
|
|
3106
|
+
if (a?.code === defaultCountry) return -1;
|
|
3107
|
+
if (b?.code === defaultCountry) return 1;
|
|
3108
|
+
return a.name.localeCompare(b.name);
|
|
3109
|
+
});
|
|
3110
|
+
};
|
|
3111
|
+
|
|
3112
|
+
//#endregion
|
|
3113
|
+
//#region src/components/phoneInput/index.tsx
|
|
3114
|
+
const CountrySelector = ({ searchPlaceholder, defaultCountry, ref }) => {
|
|
3115
|
+
const phoneNumberPrefixes = usePhoneNumberPrefix(defaultCountry);
|
|
3116
|
+
const [selectedCountry, setSelectedCountry] = useState(phoneNumberPrefixes.find((country) => country.code === defaultCountry) || null);
|
|
3117
|
+
const [openDropdown, setOpenDropdown] = useState(false);
|
|
3118
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
3119
|
+
const [filteredCountries, setFilteredCountries] = useState(phoneNumberPrefixes);
|
|
3120
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
3121
|
+
const optionRefs = useRef([]);
|
|
3122
|
+
const { refs, floatingStyles, context } = useFloating({
|
|
3123
|
+
open: openDropdown,
|
|
3124
|
+
onOpenChange: (isOpen) => {
|
|
3125
|
+
setOpenDropdown(isOpen);
|
|
3126
|
+
if (isOpen) setHighlightedIndex(-1);
|
|
3127
|
+
},
|
|
3128
|
+
middleware: [
|
|
3129
|
+
offset(4),
|
|
3130
|
+
flip(),
|
|
3131
|
+
shift()
|
|
3132
|
+
],
|
|
3133
|
+
whileElementsMounted: autoUpdate,
|
|
3134
|
+
placement: "bottom-start"
|
|
3135
|
+
});
|
|
3136
|
+
const click = useClick(context);
|
|
3137
|
+
const dismiss = useDismiss(context);
|
|
3138
|
+
const role = useRole(context, { role: "combobox" });
|
|
3139
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
3140
|
+
click,
|
|
3141
|
+
dismiss,
|
|
3142
|
+
role
|
|
3143
|
+
]);
|
|
3144
|
+
const performSearch = useCallback((query) => {
|
|
3145
|
+
if (!query) return phoneNumberPrefixes;
|
|
3146
|
+
const lowerQuery = query.toLowerCase();
|
|
3147
|
+
return phoneNumberPrefixes.filter((country) => country.name.toLowerCase().includes(lowerQuery) || country.dial_code.toLowerCase().includes(lowerQuery) || country.code.toLowerCase().includes(lowerQuery));
|
|
3148
|
+
}, [phoneNumberPrefixes]);
|
|
3149
|
+
useEffect(() => {
|
|
3150
|
+
const results = performSearch(searchQuery);
|
|
3151
|
+
setFilteredCountries(results);
|
|
3152
|
+
setHighlightedIndex(-1);
|
|
3153
|
+
}, [searchQuery]);
|
|
3154
|
+
useEffect(() => {
|
|
3155
|
+
optionRefs.current = optionRefs.current.slice(0, filteredCountries.length);
|
|
3156
|
+
}, [filteredCountries]);
|
|
3157
|
+
useEffect(() => {
|
|
3158
|
+
if (highlightedIndex >= 0 && optionRefs.current[highlightedIndex]) {
|
|
3159
|
+
optionRefs.current[highlightedIndex]?.scrollIntoView({
|
|
3160
|
+
behavior: "auto",
|
|
3161
|
+
block: "nearest"
|
|
3162
|
+
});
|
|
3163
|
+
}
|
|
3164
|
+
}, [highlightedIndex]);
|
|
3165
|
+
const handleSelect = (country) => {
|
|
3166
|
+
setSelectedCountry(country);
|
|
3167
|
+
setOpenDropdown(false);
|
|
3168
|
+
};
|
|
3169
|
+
const handleKeyDown = (e) => {
|
|
3170
|
+
if (filteredCountries.length === 0) return;
|
|
3171
|
+
switch (e.key) {
|
|
3172
|
+
case "ArrowDown":
|
|
3173
|
+
e.preventDefault();
|
|
3174
|
+
setHighlightedIndex((prev) => prev < filteredCountries.length - 1 ? prev + 1 : 0);
|
|
3175
|
+
break;
|
|
3176
|
+
case "ArrowUp":
|
|
3177
|
+
e.preventDefault();
|
|
3178
|
+
setHighlightedIndex((prev) => prev > 0 ? prev - 1 : filteredCountries.length - 1);
|
|
3179
|
+
break;
|
|
3180
|
+
case "Enter": {
|
|
3181
|
+
e.preventDefault();
|
|
3182
|
+
if (highlightedIndex >= 0) handleSelect(filteredCountries[highlightedIndex]);
|
|
3183
|
+
break;
|
|
3184
|
+
}
|
|
3185
|
+
case "Escape":
|
|
3186
|
+
e.preventDefault();
|
|
3187
|
+
setOpenDropdown(false);
|
|
3188
|
+
break;
|
|
3189
|
+
}
|
|
3190
|
+
};
|
|
3191
|
+
useImperativeHandle(ref, () => ({
|
|
3192
|
+
updateCountry: (countryCode) => {
|
|
3193
|
+
const country = phoneNumberPrefixes.find((c) => c.dial_code === countryCode);
|
|
3194
|
+
if (country) setSelectedCountry(country);
|
|
3195
|
+
},
|
|
3196
|
+
getSelectedCountry: () => selectedCountry
|
|
3197
|
+
}));
|
|
3198
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3199
|
+
className: "relative",
|
|
3200
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
3201
|
+
type: "button",
|
|
3202
|
+
ref: refs.setReference,
|
|
3203
|
+
...getReferenceProps(),
|
|
3204
|
+
className: cn("flex h-10 items-center justify-between px-2 text-sm transition-colors"),
|
|
3205
|
+
"aria-haspopup": "listbox",
|
|
3206
|
+
"aria-expanded": openDropdown,
|
|
3207
|
+
"aria-label": `Country selector. Currently selected: ${selectedCountry?.name || "None"} ${selectedCountry?.dial_code || ""}`,
|
|
3208
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3209
|
+
className: "flex items-center gap-1",
|
|
3210
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3211
|
+
className: "text-xs font-medium",
|
|
3212
|
+
children: selectedCountry?.dial_code
|
|
3213
|
+
})
|
|
3214
|
+
})
|
|
3215
|
+
}), openDropdown && /* @__PURE__ */ jsx(FloatingPortal, { children: /* @__PURE__ */ jsx(FloatingFocusManager, {
|
|
3216
|
+
context,
|
|
3217
|
+
modal: false,
|
|
3218
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
3219
|
+
ref: refs.setFloating,
|
|
3220
|
+
style: {
|
|
3221
|
+
...floatingStyles,
|
|
3222
|
+
width: "280px"
|
|
3223
|
+
},
|
|
3224
|
+
...getFloatingProps(),
|
|
3225
|
+
role: "listbox",
|
|
3226
|
+
"aria-label": "Country selection",
|
|
3227
|
+
className: cn("z-[999] rounded-md border border-gray-200 bg-white shadow-lg", "overflow-hidden animate-in fade-in"),
|
|
3228
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3229
|
+
className: "p-2 border-b border-gray-100",
|
|
3230
|
+
children: /* @__PURE__ */ jsx(TextInput, {
|
|
3231
|
+
placeholder: searchPlaceholder,
|
|
3232
|
+
value: searchQuery,
|
|
3233
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
3234
|
+
onKeyDown: handleKeyDown,
|
|
3235
|
+
autoFocus: true,
|
|
3236
|
+
"aria-label": "Search countries",
|
|
3237
|
+
role: "searchbox"
|
|
3238
|
+
})
|
|
3239
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
3240
|
+
className: "max-h-[200px] overflow-auto p-1",
|
|
3241
|
+
children: filteredCountries.length > 0 ? filteredCountries.map((country, index) => /* @__PURE__ */ jsxs("button", {
|
|
3242
|
+
ref: (el) => optionRefs.current[index] = el,
|
|
3243
|
+
type: "button",
|
|
3244
|
+
role: "option",
|
|
3245
|
+
"aria-selected": selectedCountry?.code === country.code,
|
|
3246
|
+
onClick: () => handleSelect(country),
|
|
3247
|
+
className: cn("flex w-full items-center gap-3 rounded px-3 py-2 text-sm hover:bg-gray-50", selectedCountry?.code === country.code && "bg-gray-50", highlightedIndex === index && "bg-gray-50 border border-gray-100"),
|
|
3248
|
+
children: [
|
|
3249
|
+
/* @__PURE__ */ jsx("span", {
|
|
3250
|
+
className: "text-base",
|
|
3251
|
+
children: country.flag
|
|
3252
|
+
}),
|
|
3253
|
+
/* @__PURE__ */ jsx("span", {
|
|
3254
|
+
className: "flex-1 text-left truncate",
|
|
3255
|
+
children: country.name
|
|
3256
|
+
}),
|
|
3257
|
+
/* @__PURE__ */ jsx("span", {
|
|
3258
|
+
className: "text-xs font-medium text-gray-600",
|
|
3259
|
+
children: country.dial_code
|
|
3260
|
+
})
|
|
3261
|
+
]
|
|
3262
|
+
}, country.code + country.name + country.dial_code)) : /* @__PURE__ */ jsx("div", {
|
|
3263
|
+
className: "px-3 py-2 text-sm text-gray-500 text-center",
|
|
3264
|
+
children: "No countries found"
|
|
3265
|
+
})
|
|
3266
|
+
})]
|
|
3267
|
+
})
|
|
3268
|
+
}) })]
|
|
3269
|
+
});
|
|
3270
|
+
};
|
|
3271
|
+
const PhoneInput = ({ placeholder, searchPlaceHolder, defaultCountry = "NL", invalidMessage, defaultValue, onChange, ref,...rest }) => {
|
|
3272
|
+
const inputRef = useRef(null);
|
|
3273
|
+
const [phoneNumber, setPhoneNumber] = useState(defaultValue || "");
|
|
3274
|
+
const { validatePhone, stripCountryCode, getCountryCode, formatToInternational } = usePhoneNumber();
|
|
3275
|
+
const countrySelectorRef = useRef(null);
|
|
3276
|
+
const [error, setError] = useState("");
|
|
3277
|
+
const filterPhoneInput = (value) => value.replace(/[^0-9+()]/g, "");
|
|
3278
|
+
const handlePhoneNumberChange = (e) => {
|
|
3279
|
+
const filteredValue = filterPhoneInput(e.target.value);
|
|
3280
|
+
if (error) setError("");
|
|
3281
|
+
if (filteredValue.startsWith("+") && filteredValue.length > 1) {
|
|
3282
|
+
const countryCode = getCountryCode(filteredValue);
|
|
3283
|
+
if (countryCode) countrySelectorRef.current?.updateCountry(countryCode);
|
|
3284
|
+
const strippedNumber = stripCountryCode(filteredValue);
|
|
3285
|
+
setPhoneNumber(strippedNumber);
|
|
3286
|
+
return;
|
|
3287
|
+
} else setPhoneNumber(filteredValue);
|
|
3288
|
+
};
|
|
3289
|
+
const handleBlur = () => {
|
|
3290
|
+
if (phoneNumber.trim()) {
|
|
3291
|
+
const selectedCountry = countrySelectorRef.current?.getSelectedCountry();
|
|
3292
|
+
const validationResult = validatePhone(phoneNumber, { country: selectedCountry?.code });
|
|
3293
|
+
if (validationResult.isValid) {
|
|
3294
|
+
const formattedNumber = formatToInternational(phoneNumber, { country: selectedCountry?.code });
|
|
3295
|
+
const displayNumber = stripCountryCode(formattedNumber);
|
|
3296
|
+
const fullNumber = selectedCountry?.dial_code + displayNumber;
|
|
3297
|
+
setPhoneNumber(displayNumber);
|
|
3298
|
+
onChange(fullNumber);
|
|
3299
|
+
} else setError(invalidMessage);
|
|
3300
|
+
}
|
|
3301
|
+
};
|
|
3302
|
+
return /* @__PURE__ */ jsx(TextInput, {
|
|
3303
|
+
...rest,
|
|
3304
|
+
value: phoneNumber,
|
|
3305
|
+
onChange: handlePhoneNumberChange,
|
|
3306
|
+
onBlur: handleBlur,
|
|
3307
|
+
leftSection: /* @__PURE__ */ jsx(CountrySelector, {
|
|
3308
|
+
ref: countrySelectorRef,
|
|
3309
|
+
searchPlaceholder: searchPlaceHolder,
|
|
3310
|
+
defaultCountry
|
|
3311
|
+
}),
|
|
3312
|
+
placeholder,
|
|
3313
|
+
error,
|
|
3314
|
+
ref: ref || inputRef,
|
|
3315
|
+
"aria-label": "Phone number input",
|
|
3316
|
+
inputMode: "tel",
|
|
3317
|
+
autoComplete: "tel"
|
|
3318
|
+
});
|
|
3319
|
+
};
|
|
3320
|
+
PhoneInput.displayName = "PhoneInput";
|
|
3321
|
+
|
|
3059
3322
|
//#endregion
|
|
3060
3323
|
//#region src/components/profileMenu/index.tsx
|
|
3061
3324
|
const ProfileMenu = ({ title, metaTitle, icon, content, disabled = false, classNames }) => {
|
|
@@ -3355,5 +3618,5 @@ const setCSSVariable = (variable, value) => {
|
|
|
3355
3618
|
};
|
|
3356
3619
|
|
|
3357
3620
|
//#endregion
|
|
3358
|
-
export { AccordionItem, AccordionWrapper, ActionIcon, Alert, AppleAppButtonIcon, AutoCompleteInput, Avatar, AvatarIndicator, Badge, BigBadge, BreadCrumb, Button, CSS_VARIABLE_KEYS, Checkbox, DatePickerInput, Divider, FavouriteButton, Filters, GoogleAppButtonIcon, HR, HamburgerMenuButton, Island, KebabMenu, Label, Loader, LogoBlack, Modal, NavButtons, NumberField, NumberedStepper, PageUnavailable, PasswordInput, Popover, ProfileMenu, ProgressBar, RadioButton, Rating, RegionSelector, Reviews, ScrollToTop, SearchInput, Select, SettingsCard, Skeleton, SkillPill, Stepper, StickyMobileButtonWrapper, Table, TableCell, TableHeader, TableHeaderItem, TableHeaderRow, TableRow, TabsBadge, TabsWrapper, TextInput, Textarea, ThemeIcon, TimeInput, Toggle, Tooltip, TruncatedText, UnorderedList, UnorderedListItem, UnstyledButton, WysiwygEditor, buttonVariants, getCSSVariable, setCSSVariable };
|
|
3621
|
+
export { AccordionItem, AccordionWrapper, ActionIcon, Alert, AppleAppButtonIcon, AutoCompleteInput, Avatar, AvatarIndicator, Badge, BigBadge, BreadCrumb, Button, CSS_VARIABLE_KEYS, Checkbox, DatePickerInput, Divider, FavouriteButton, Filters, GoogleAppButtonIcon, HR, HamburgerMenuButton, Island, KebabMenu, Label, Loader, LogoBlack, Modal, NavButtons, NumberField, NumberedStepper, PageUnavailable, PasswordInput, PhoneInput, Popover, ProfileMenu, ProgressBar, RadioButton, Rating, RegionSelector, Reviews, ScrollToTop, SearchInput, Select, SettingsCard, Skeleton, SkillPill, Stepper, StickyMobileButtonWrapper, Table, TableCell, TableHeader, TableHeaderItem, TableHeaderRow, TableRow, TabsBadge, TabsWrapper, TextInput, Textarea, ThemeIcon, TimeInput, Toggle, Tooltip, TruncatedText, UnorderedList, UnorderedListItem, UnstyledButton, WysiwygEditor, buttonVariants, getCSSVariable, setCSSVariable };
|
|
3359
3622
|
//# sourceMappingURL=index.js.map
|