@swan-io/shared-business 13.9.3 → 13.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/shared-business",
3
- "version": "13.9.3",
3
+ "version": "13.10.1",
4
4
  "engines": {
5
5
  "node": ">22.12.0"
6
6
  },
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "license": "MIT",
27
27
  "peerDependencies": {
28
- "@swan-io/lake": "13.9.3"
28
+ "@swan-io/lake": "13.10.1"
29
29
  },
30
30
  "dependencies": {
31
31
  "@formatjs/intl": "^4.1.2",
@@ -50,6 +50,6 @@
50
50
  "@types/react-dom": "^19.2.3",
51
51
  "@types/react-native": "^0.72.8",
52
52
  "type-fest": "^5.4.3",
53
- "@swan-io/lake": "13.9.3"
53
+ "@swan-io/lake": "13.10.1"
54
54
  }
55
55
  }
@@ -0,0 +1,27 @@
1
+ import { Ref } from "react";
2
+ import { Country, CountryCCA3 } from "../constants/countries";
3
+ export type InputPhoneNumberRef = {
4
+ focus: () => void;
5
+ blur: () => void;
6
+ };
7
+ type Props = {
8
+ ref?: Ref<InputPhoneNumberRef>;
9
+ id?: string;
10
+ country: Country;
11
+ value: string;
12
+ countries?: CountryCCA3[];
13
+ error?: string;
14
+ help?: string;
15
+ valid?: boolean;
16
+ autofocus?: boolean;
17
+ disabled?: boolean;
18
+ readOnly?: boolean;
19
+ countryAriaLabel?: string;
20
+ hideErrors?: boolean;
21
+ onCountryChange: (country: Country) => void;
22
+ onChangeText: (text: string) => void;
23
+ onSubmitEditing?: () => void;
24
+ onBlur?: () => void;
25
+ };
26
+ export declare const InputPhoneNumber: ({ ref, id, country, value, countries, error, help, valid, autofocus, disabled, readOnly, countryAriaLabel, hideErrors, onCountryChange, onChangeText, onSubmitEditing, onBlur, }: Props) => import("react/jsx-runtime").JSX.Element;
27
+ export {};
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box } from "@swan-io/lake/src/components/Box";
3
+ import { LakeText } from "@swan-io/lake/src/components/LakeText";
4
+ import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
5
+ import { Space } from "@swan-io/lake/src/components/Space";
6
+ import { colors } from "@swan-io/lake/src/constants/design";
7
+ import { isNotNullishOrEmpty } from "@swan-io/lake/src/utils/nullish";
8
+ import { useEffect, useImperativeHandle, useRef } from "react";
9
+ import { StyleSheet } from "react-native";
10
+ import { phoneCountries } from "../constants/countries";
11
+ import { PhoneCountryPicker } from "./PhoneCountryPicker";
12
+ const styles = StyleSheet.create({
13
+ picker: {
14
+ borderTopRightRadius: 0,
15
+ borderBottomRightRadius: 0,
16
+ borderRightWidth: 0,
17
+ },
18
+ pickerDisabled: {
19
+ borderRightWidth: 1,
20
+ borderRightColor: colors.gray[100],
21
+ },
22
+ input: {
23
+ borderTopLeftRadius: 0,
24
+ borderBottomLeftRadius: 0,
25
+ },
26
+ });
27
+ export const InputPhoneNumber = ({ ref, id, country, value, countries, error, help, valid = false, autofocus = false, disabled = false, readOnly = false, countryAriaLabel, hideErrors = false, onCountryChange, onChangeText, onSubmitEditing, onBlur, }) => {
28
+ var _a;
29
+ const inputRef = useRef(null);
30
+ const handleChangeText = (text) => {
31
+ const clean = text.replace(/[^ +0-9-()]/g, "");
32
+ if (text === clean) {
33
+ onChangeText(text);
34
+ }
35
+ };
36
+ useEffect(() => {
37
+ if (autofocus && inputRef.current) {
38
+ setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 250);
39
+ }
40
+ }, [autofocus]);
41
+ useImperativeHandle(ref, () => ({
42
+ focus: () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); },
43
+ blur: () => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.blur(); },
44
+ }), []);
45
+ const pickerStyle = [styles.picker, (disabled || readOnly) && styles.pickerDisabled];
46
+ return (_jsxs(_Fragment, { children: [_jsxs(Box, { direction: "row", children: [_jsx(PhoneCountryPicker, { value: country, countries: countries !== null && countries !== void 0 ? countries : phoneCountries, onValueChange: onCountryChange, disabled: disabled, readOnly: readOnly, error: error, ariaLabel: countryAriaLabel, style: pickerStyle }), _jsx(LakeTextInput, { id: id, ref: inputRef, autoComplete: "tel", inputMode: "tel", rows: 1, hideErrors: true, value: value, valid: valid, error: error, disabled: disabled, readOnly: readOnly, onChangeText: handleChangeText, onSubmitEditing: onSubmitEditing, onBlur: onBlur, style: styles.input })] }), !hideErrors && (_jsxs(_Fragment, { children: [_jsx(Space, { height: 4 }), _jsx(LakeText, { variant: "smallRegular", color: isNotNullishOrEmpty(error) ? colors.negative[500] : colors.gray[500], children: (_a = error !== null && error !== void 0 ? error : help) !== null && _a !== void 0 ? _a : " " })] }))] }));
47
+ };
@@ -0,0 +1,14 @@
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ import { Country, CountryCCA3 } from "../constants/countries";
3
+ type Props = {
4
+ value: Country;
5
+ onValueChange: (country: Country) => void;
6
+ countries: CountryCCA3[];
7
+ style?: StyleProp<ViewStyle>;
8
+ disabled?: boolean;
9
+ readOnly?: boolean;
10
+ error?: string;
11
+ ariaLabel?: string;
12
+ };
13
+ export declare const PhoneCountryPicker: ({ value, onValueChange, countries, style, disabled, readOnly, error, ariaLabel, }: Props) => import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box } from "@swan-io/lake/src/components/Box";
3
+ import { FlatList } from "@swan-io/lake/src/components/FlatList";
4
+ import { Icon } from "@swan-io/lake/src/components/Icon";
5
+ import { LakeText } from "@swan-io/lake/src/components/LakeText";
6
+ import { Popover } from "@swan-io/lake/src/components/Popover";
7
+ import { Pressable } from "@swan-io/lake/src/components/Pressable";
8
+ import { Separator } from "@swan-io/lake/src/components/Separator";
9
+ import { Space } from "@swan-io/lake/src/components/Space";
10
+ import { colors, radii, spacings, texts } from "@swan-io/lake/src/constants/design";
11
+ import { useDebounce } from "@swan-io/lake/src/hooks/useDebounce";
12
+ import { useDisclosure } from "@swan-io/lake/src/hooks/useDisclosure";
13
+ import { isNotNullish, isNotNullishOrEmpty } from "@swan-io/lake/src/utils/nullish";
14
+ import { deburr } from "@swan-io/lake/src/utils/string";
15
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
16
+ import { StyleSheet, TextInput, View } from "react-native";
17
+ import { getCountryByCCA3 } from "../constants/countries";
18
+ import { Flag } from "./Flag";
19
+ const ROW_HEIGHT = 48;
20
+ const styles = StyleSheet.create({
21
+ trigger: {
22
+ alignItems: "center",
23
+ backgroundColor: colors.gray[50],
24
+ borderColor: colors.gray[100],
25
+ borderTopLeftRadius: radii[4],
26
+ borderTopRightRadius: radii[4],
27
+ borderBottomLeftRadius: radii[4],
28
+ borderBottomRightRadius: radii[4],
29
+ borderWidth: 1,
30
+ flexDirection: "row",
31
+ height: 40,
32
+ justifyContent: "center",
33
+ outlineStyle: "none",
34
+ paddingLeft: spacings[16],
35
+ paddingRight: spacings[12],
36
+ transitionDuration: "150ms",
37
+ transitionProperty: "background-color",
38
+ },
39
+ triggerPressed: { backgroundColor: colors.gray[100] },
40
+ triggerErrored: { borderColor: colors.negative[500] },
41
+ triggerDisabled: { borderColor: colors.gray[50] },
42
+ list: { height: 230 },
43
+ searchIcon: { position: "absolute", left: 16 },
44
+ searchInput: {
45
+ ...texts.regular,
46
+ color: colors.gray[700],
47
+ flexGrow: 1,
48
+ height: ROW_HEIGHT,
49
+ outlineStyle: "none",
50
+ paddingLeft: 44,
51
+ },
52
+ row: {
53
+ flexDirection: "row",
54
+ alignItems: "center",
55
+ height: ROW_HEIGHT,
56
+ paddingHorizontal: 16,
57
+ transitionProperty: "background-color",
58
+ transitionDuration: "150ms",
59
+ },
60
+ rowHovered: { backgroundColor: colors.gray[50] },
61
+ rowPressed: { backgroundColor: colors.gray[100] },
62
+ rowName: { flexGrow: 1 },
63
+ });
64
+ export const PhoneCountryPicker = ({ value, onValueChange, countries, style, disabled = false, readOnly = false, error, ariaLabel = "Select country", }) => {
65
+ const referenceRef = useRef(null);
66
+ const inputRef = useRef(null);
67
+ const [visible, { open, close }] = useDisclosure(false);
68
+ const allowedCountries = useMemo(() => countries
69
+ .filter((cca3, index, array) => array.indexOf(cca3) === index)
70
+ .map(cca3 => getCountryByCCA3(cca3)), [countries]);
71
+ const [filteredCountries, setFilteredCountries] = useState(allowedCountries);
72
+ useEffect(() => {
73
+ setFilteredCountries(allowedCountries);
74
+ }, [allowedCountries]);
75
+ useEffect(() => {
76
+ if (visible) {
77
+ setTimeout(() => { var _a; return (_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus(); }, 250);
78
+ }
79
+ }, [visible]);
80
+ const handleOnChangeText = useDebounce(useCallback((text) => {
81
+ const cleaned = deburr(text.trim().toLowerCase());
82
+ if (cleaned === "") {
83
+ setFilteredCountries(allowedCountries);
84
+ return;
85
+ }
86
+ const filtered = allowedCountries.filter(country => country.deburr.includes(cleaned) ||
87
+ country.idd.includes(cleaned) ||
88
+ `+${country.idd}`.includes(cleaned));
89
+ setFilteredCountries(filtered);
90
+ }, [allowedCountries]), 200);
91
+ return (_jsxs(_Fragment, { children: [_jsxs(Pressable, { ref: referenceRef, role: "button", "aria-label": ariaLabel, disabled: visible || disabled || readOnly, onPress: open, style: ({ pressed }) => [
92
+ styles.trigger,
93
+ style,
94
+ !visible && pressed && styles.triggerPressed,
95
+ (disabled || readOnly) && styles.triggerDisabled,
96
+ isNotNullishOrEmpty(error) && styles.triggerErrored,
97
+ ], children: [_jsx(Flag, { code: value.cca2, width: 16 }), _jsx(Space, { width: 8 }), _jsxs(LakeText, { color: colors.gray[600], numberOfLines: 1, userSelect: "none", variant: "smallSemibold", children: ["+", value.idd] }), _jsx(Space, { width: 8 }), _jsx(Icon, { name: "chevron-down-filled", color: colors.gray[600], size: 16 })] }), _jsx(Popover, { referenceRef: referenceRef, visible: visible, onDismiss: close, matchReferenceWidth: false, children: _jsxs(View, { style: styles.list, children: [_jsxs(Box, { direction: "row", alignItems: "center", children: [_jsx(Icon, { name: "search-filled", color: colors.gray[300], size: 18, style: styles.searchIcon }), _jsx(TextInput, { ref: inputRef, inputMode: "search", style: styles.searchInput, onChangeText: handleOnChangeText, onSubmitEditing: () => {
98
+ if (isNotNullish(filteredCountries[0])) {
99
+ onValueChange(filteredCountries[0]);
100
+ close();
101
+ }
102
+ } })] }), _jsx(Separator, {}), _jsx(FlatList, { data: filteredCountries, ItemSeparatorComponent: _jsx(Separator, {}), keyExtractor: item => item.uid, renderItem: ({ item }) => (_jsxs(Pressable, { role: "button", "aria-label": item.name, style: ({ hovered, pressed }) => [
103
+ styles.row,
104
+ hovered && styles.rowHovered,
105
+ pressed && styles.rowPressed,
106
+ ], onPress: () => {
107
+ onValueChange(item);
108
+ close();
109
+ }, children: [_jsx(Flag, { code: item.cca2, width: 16 }), _jsx(Space, { width: 12 }), _jsx(LakeText, { numberOfLines: 1, style: styles.rowName, userSelect: "none", variant: "smallRegular", children: item.name }), item.uid === value.uid && (_jsxs(_Fragment, { children: [_jsx(Space, { width: 12 }), _jsx(Icon, { name: "checkmark-filled", color: colors.positive[500], size: 16 })] })), _jsx(Space, { width: 12 }), _jsxs(LakeText, { userSelect: "none", variant: "smallRegular", children: ["+", item.idd] })] })) })] }) })] }));
110
+ };
@@ -1502,6 +1502,7 @@ export declare const france: {
1502
1502
  uid: string;
1503
1503
  flag: string;
1504
1504
  };
1505
+ export declare const phoneCountries: ("ALA" | "ASM" | "AND" | "AGO" | "AIA" | "ATA" | "ATG" | "ARG" | "ABW" | "AUS" | "AZE" | "BHS" | "BGD" | "BRB" | "BEL" | "BLZ" | "BEN" | "BMU" | "BOL" | "BES" | "BIH" | "BWA" | "BVT" | "BRA" | "VGB" | "BFA" | "BDI" | "CPV" | "CMR" | "CAN" | "CYM" | "CZE" | "CHL" | "CXR" | "CCK" | "COL" | "COK" | "CRI" | "CIV" | "CUB" | "CUW" | "DNK" | "DEU" | "DJI" | "DMA" | "ECU" | "EST" | "IRL" | "SLV" | "ESP" | "FLK" | "FJI" | "FRO" | "FRA" | "GAB" | "GMB" | "GHA" | "GIB" | "GRD" | "GLP" | "GUM" | "GTM" | "GGY" | "GNB" | "GNQ" | "GIN" | "GUY" | "GUF" | "HTI" | "HMD" | "HND" | "HRV" | "IDN" | "ISL" | "IMN" | "ITA" | "JAM" | "JEY" | "GRL" | "KHM" | "KAZ" | "KEN" | "KIR" | "CAF" | "COM" | "REU" | "LVA" | "LSO" | "LBR" | "LIE" | "LTU" | "LUX" | "MDG" | "HUN" | "MHL" | "MWI" | "MYS" | "MDV" | "MLI" | "MLT" | "MTQ" | "MUS" | "MYT" | "MEX" | "FSM" | "MOZ" | "MDA" | "MCO" | "MSR" | "NAM" | "NLD" | "BRN" | "NZL" | "NIC" | "NER" | "NGA" | "NIU" | "NFK" | "NOR" | "MNP" | "NCL" | "AUT" | "UZB" | "PAK" | "PLW" | "PAN" | "PNG" | "PRY" | "PER" | "PHL" | "PCN" | "POL" | "PYF" | "PRT" | "PRI" | "DOM" | "XKX" | "COD" | "COG" | "ROU" | "RWA" | "KNA" | "LCA" | "VCT" | "BLM" | "MAF" | "SPM" | "WSM" | "SMR" | "STP" | "CHE" | "SEN" | "SYC" | "ALB" | "SLE" | "SGP" | "SVN" | "SVK" | "SLB" | "SOM" | "ZAF" | "SGS" | "SSD" | "LKA" | "FIN" | "SUR" | "SWE" | "SWZ" | "TZA" | "TCD" | "ATF" | "TLS" | "TGO" | "TON" | "TTO" | "TUR" | "TKM" | "TCA" | "TUV" | "UGA" | "GBR" | "USA" | "UMI" | "VIR" | "URY" | "VUT" | "VAT" | "VEN" | "VNM" | "WLF" | "ZMB" | "ZWE" | "BTN" | "GRC" | "CYP" | "BLR" | "BGR" | "KGZ" | "MKD" | "MNG" | "RUS" | "SRB" | "TJK" | "UKR" | "MNE" | "ARM" | "ISR" | "AFG" | "JOR" | "DZA" | "SDN" | "ESH" | "IRQ" | "SAU" | "KWT" | "MAR" | "YEM" | "IRN" | "TUN" | "ARE" | "SYR" | "OMN" | "PSE" | "QAT" | "LBN" | "EGY" | "MRT" | "NPL" | "IND" | "THA" | "LAO" | "MMR" | "GEO" | "ETH" | "ERI" | "BHR" | "LBY" | "CHN" | "JPN" | "MAC" | "TWN" | "HKG" | "KOR" | "PRK")[];
1505
1506
  export declare const individualCountries: ("BEL" | "CZE" | "DNK" | "DEU" | "EST" | "IRL" | "ESP" | "FRA" | "HRV" | "ISL" | "ITA" | "LVA" | "LIE" | "LTU" | "LUX" | "HUN" | "MLT" | "NLD" | "NOR" | "AUT" | "POL" | "PRT" | "ROU" | "SVN" | "SVK" | "FIN" | "SWE" | "GRC" | "CYP" | "BGR")[];
1506
1507
  export type IndividualCountryCCA3 = (typeof individualCountries)[number];
1507
1508
  export declare const companyCountries: ("BEL" | "CZE" | "DNK" | "DEU" | "EST" | "IRL" | "ESP" | "FRA" | "HRV" | "ISL" | "ITA" | "LVA" | "LIE" | "LTU" | "LUX" | "HUN" | "MLT" | "NLD" | "NOR" | "AUT" | "POL" | "PRT" | "ROU" | "SVN" | "SVK" | "FIN" | "SWE" | "GRC" | "CYP" | "BGR")[];
@@ -1768,6 +1768,8 @@ export const getCCA3forCCA2 = (cca2) => countriesByCCA2[cca2].cca3;
1768
1768
  export const companyWithUboCountries = ["BEL", "DEU", "FRA", "ITA", "LTU", "LUX", "NLD"];
1769
1769
  export const allCountries = countries.map(country => country.cca3);
1770
1770
  export const france = countriesByCCA3["FRA"];
1771
+ const blockedPhoneCountries = ["CUB", "SYR", "IRN", "PRK", "RUS"];
1772
+ export const phoneCountries = allCountries.filter(cca3 => !blockedPhoneCountries.includes(cca3));
1771
1773
  export const individualCountries = [
1772
1774
  "AUT",
1773
1775
  "BEL",
@@ -11,9 +11,19 @@ export const getLanguagesHelpers = (languages) => {
11
11
  const isSupportedLanguage = (language) => (supportedLanguages !== null && supportedLanguages !== void 0 ? supportedLanguages : []).includes(language);
12
12
  const getFirstSupportedLanguage = (languages) => { var _a; return (_a = languages.find(isSupportedLanguage)) !== null && _a !== void 0 ? _a : LANGUAGE_FALLBACK; };
13
13
  const getPreferredLanguage = () => {
14
- var _a;
14
+ // @deprecated temporary keeping localStorage and migrating to cookie
15
+ const language = localStorage.getItem(PREFERRED_LANGUAGE_KEY);
16
+ if (language) {
17
+ localStorage.removeItem(PREFERRED_LANGUAGE_KEY);
18
+ const domain = "." + window.location.hostname.split(".").slice(-2).join(".");
19
+ document.cookie = `${PREFERRED_LANGUAGE_KEY}=${language}; path=/; max-age=31536000; SameSite=Lax; Secure; domain=${domain}`;
20
+ return language;
21
+ }
15
22
  try {
16
- return (_a = localStorage.getItem(PREFERRED_LANGUAGE_KEY)) !== null && _a !== void 0 ? _a : undefined;
23
+ const match = document.cookie
24
+ .split("; ")
25
+ .find(row => row.startsWith(PREFERRED_LANGUAGE_KEY + "="));
26
+ return match === null || match === void 0 ? void 0 : match.split("=")[1];
17
27
  }
18
28
  catch {
19
29
  return;
@@ -23,7 +33,8 @@ export const getLanguagesHelpers = (languages) => {
23
33
  getFirstSupportedLanguage,
24
34
  setPreferredLanguage: (language) => {
25
35
  try {
26
- localStorage.setItem(PREFERRED_LANGUAGE_KEY, language);
36
+ const domain = "." + window.location.hostname.split(".").slice(-2).join(".");
37
+ document.cookie = `${PREFERRED_LANGUAGE_KEY}=${language}; path=/; max-age=31536000; SameSite=Lax; Secure; domain=${domain}`;
27
38
  const url = new URL(window.location.href);
28
39
  url.searchParams.delete("lang");
29
40
  window.location.replace(url.toString());