@superdispatch/phones 0.21.8 → 0.21.13

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 (48) hide show
  1. package/.babelrc.js +5 -0
  2. package/.turbo/turbo-version.log +26 -0
  3. package/package.json +60 -34
  4. package/pkg/README.md +10 -0
  5. package/{dist-types → pkg/dist-types}/index.d.ts +82 -82
  6. package/pkg/package.json +34 -0
  7. package/playroom.ts +3 -0
  8. package/src/__tests__/index.spec.ts +23 -0
  9. package/src/apn/APN.ts +40 -0
  10. package/src/country-code-metadata/CountryCodeMetadata.spec.ts +1291 -0
  11. package/src/country-code-metadata/CountryCodeMetadata.ts +281 -0
  12. package/src/formatted-phone-number/FormattedPhoneNumber.ts +20 -0
  13. package/src/index.ts +6 -0
  14. package/src/phone-field/PhoneField.playroom.tsx +28 -0
  15. package/src/phone-field/PhoneField.spec.tsx +171 -0
  16. package/src/phone-field/PhoneField.stories.tsx +6 -0
  17. package/src/phone-field/PhoneField.tsx +200 -0
  18. package/src/phone-field/PhoneFieldFlag.tsx +54 -0
  19. package/src/phone-field/PhoneFieldMenu.tsx +53 -0
  20. package/src/phone-field/PhoneFieldMenuItem.spec.tsx +12 -0
  21. package/src/phone-field/PhoneFieldMenuItem.tsx +61 -0
  22. package/src/phone-field/PhoneFieldStartAdornment.tsx +68 -0
  23. package/src/phone-link/PhoneLink.spec.tsx +60 -0
  24. package/src/phone-link/PhoneLink.stories.tsx +9 -0
  25. package/src/phone-link/PhoneLink.tsx +59 -0
  26. package/src/phone-service/PhoneService.spec.ts +277 -0
  27. package/src/phone-service/PhoneService.ts +287 -0
  28. package/src/phone-text/PhoneText.spec.tsx +65 -0
  29. package/src/phone-text/PhoneText.stories.tsx +9 -0
  30. package/src/phone-text/PhoneText.tsx +38 -0
  31. package/tsconfig.json +19 -0
  32. package/LICENSE +0 -21
  33. /package/{dist-node → pkg/dist-node}/index.js +0 -0
  34. /package/{dist-node → pkg/dist-node}/index.js.map +0 -0
  35. /package/{dist-src → pkg/dist-src}/apn/APN.js +0 -0
  36. /package/{dist-src → pkg/dist-src}/country-code-metadata/CountryCodeMetadata.js +0 -0
  37. /package/{dist-src → pkg/dist-src}/formatted-phone-number/FormattedPhoneNumber.js +0 -0
  38. /package/{dist-src → pkg/dist-src}/index.js +0 -0
  39. /package/{dist-src → pkg/dist-src}/phone-field/PhoneField.js +0 -0
  40. /package/{dist-src → pkg/dist-src}/phone-field/PhoneFieldFlag.js +0 -0
  41. /package/{dist-src → pkg/dist-src}/phone-field/PhoneFieldMenu.js +0 -0
  42. /package/{dist-src → pkg/dist-src}/phone-field/PhoneFieldMenuItem.js +0 -0
  43. /package/{dist-src → pkg/dist-src}/phone-field/PhoneFieldStartAdornment.js +0 -0
  44. /package/{dist-src → pkg/dist-src}/phone-link/PhoneLink.js +0 -0
  45. /package/{dist-src → pkg/dist-src}/phone-service/PhoneService.js +0 -0
  46. /package/{dist-src → pkg/dist-src}/phone-text/PhoneText.js +0 -0
  47. /package/{dist-web → pkg/dist-web}/index.js +0 -0
  48. /package/{dist-web → pkg/dist-web}/index.js.map +0 -0
@@ -0,0 +1,200 @@
1
+ import { BaseTextFieldProps, TextField } from '@material-ui/core';
2
+ import { mergeRefs } from '@superdispatch/ui';
3
+ import {
4
+ ChangeEvent,
5
+ forwardRef,
6
+ ReactNode,
7
+ Suspense,
8
+ useCallback,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from 'react';
14
+ import { CountryISO } from '../country-code-metadata/CountryCodeMetadata';
15
+ import { usePhoneService } from '../phone-service/PhoneService';
16
+ import { PhoneFieldMenu } from './PhoneFieldMenu';
17
+ import { PhoneFieldStartAdornment } from './PhoneFieldStartAdornment';
18
+
19
+ function normalizeValue(value: unknown): string {
20
+ return typeof value === 'string' ? value : '';
21
+ }
22
+
23
+ interface State {
24
+ value: string;
25
+ nationalNumber: string;
26
+ country: CountryISO;
27
+ }
28
+
29
+ export interface PhoneFieldProps
30
+ extends Pick<
31
+ BaseTextFieldProps,
32
+ | 'disabled'
33
+ | 'error'
34
+ | 'fullWidth'
35
+ | 'helperText'
36
+ | 'id'
37
+ | 'label'
38
+ | 'name'
39
+ | 'placeholder'
40
+ | 'required'
41
+ > {
42
+ value?: null | string;
43
+ onBlur?: (value: string) => void;
44
+ onFocus?: (value: string) => void;
45
+ onChange?: (value: string) => void;
46
+ }
47
+
48
+ export const PhoneField = forwardRef<HTMLDivElement, PhoneFieldProps>(
49
+ ({ value: valueProp, onBlur, onFocus, onChange, ...props }, ref) => {
50
+ const rootRef = useRef<HTMLDivElement>(null);
51
+ const inputRef = useRef<HTMLInputElement>(null);
52
+ const [menuAnchor, setMenuAnchor] = useState<null | HTMLDivElement>(null);
53
+
54
+ const phoneService = usePhoneService();
55
+ const createState = useCallback(
56
+ (value: string): State => ({ value, ...phoneService.getInfo(value) }),
57
+ [phoneService],
58
+ );
59
+
60
+ const value = useMemo(() => normalizeValue(valueProp), [valueProp]);
61
+ const [{ country, nationalNumber }, setValue] = useState(() =>
62
+ createState(value),
63
+ );
64
+
65
+ const placeholder = useMemo(
66
+ () => phoneService.APN.getExample(country).getNumber('international'),
67
+ [country, phoneService.APN],
68
+ );
69
+
70
+ function handleChange(
71
+ fn: undefined | ((value: string) => void),
72
+ nextCountry: CountryISO,
73
+ nextNationalNumber: string,
74
+ ): void {
75
+ if (fn) {
76
+ const nextValue = phoneService.format(nextNationalNumber, {
77
+ country: nextCountry,
78
+ });
79
+
80
+ setValue({
81
+ value: nextValue,
82
+ country: nextCountry,
83
+ nationalNumber: nextNationalNumber,
84
+ });
85
+
86
+ fn(nextValue);
87
+ }
88
+ }
89
+
90
+ function handleChangeEvent(
91
+ fn: undefined | ((value: string) => void),
92
+ {
93
+ target: { value: nextValue },
94
+ }: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
95
+ ): void {
96
+ if (fn) {
97
+ handleChange(
98
+ fn,
99
+ country,
100
+ phoneService.format(nextValue, { country, format: 'national' }),
101
+ );
102
+ }
103
+ }
104
+
105
+ useEffect(() => {
106
+ setValue((prev) =>
107
+ // Ignore `props.value` changes when they were performed from inside.
108
+ prev.value === value ? prev : createState(value),
109
+ );
110
+ }, [value, createState]);
111
+
112
+ return (
113
+ <>
114
+ <PhoneFieldMenu
115
+ value={country}
116
+ anchorEl={menuAnchor}
117
+ onClose={() => {
118
+ setMenuAnchor(null);
119
+ }}
120
+ onChange={(nextRegion) => {
121
+ handleChange(onChange, nextRegion, nationalNumber);
122
+ }}
123
+ />
124
+
125
+ <TextField
126
+ {...props}
127
+ type="tel"
128
+ variant="outlined"
129
+ autoComplete="off"
130
+ value={nationalNumber}
131
+ placeholder={phoneService.deletePrefix(placeholder, country)}
132
+ ref={mergeRefs(ref, rootRef)}
133
+ inputRef={inputRef}
134
+ onBlur={(event) => {
135
+ handleChangeEvent(onBlur, event);
136
+ }}
137
+ onFocus={(event) => {
138
+ handleChangeEvent(onFocus, event);
139
+ }}
140
+ onChange={(event) => {
141
+ handleChangeEvent(onChange, event);
142
+ }}
143
+ InputProps={{
144
+ startAdornment: (
145
+ <PhoneFieldStartAdornment
146
+ country={country}
147
+ isExpanded={!!menuAnchor}
148
+ onClick={() => {
149
+ // `FocusTrap` inside of `Menu` will restore focus to
150
+ // the last focused element. We want to manually focus input
151
+ // to trick the `FocusTrap`.
152
+ inputRef.current?.focus();
153
+ setMenuAnchor(rootRef.current);
154
+ }}
155
+ />
156
+ ),
157
+ }}
158
+ />
159
+ </>
160
+ );
161
+ },
162
+ );
163
+
164
+ export interface SuspendedPhoneFieldProps extends PhoneFieldProps {
165
+ suspenseFallback?: ReactNode;
166
+ }
167
+
168
+ export const SuspendedPhoneField = forwardRef<
169
+ HTMLDivElement,
170
+ SuspendedPhoneFieldProps
171
+ >(
172
+ (
173
+ {
174
+ label,
175
+ fullWidth,
176
+ helperText,
177
+ suspenseFallback = (
178
+ <TextField
179
+ disabled={true}
180
+ label={label}
181
+ fullWidth={fullWidth}
182
+ helperText={helperText}
183
+ placeholder="Loading…"
184
+ />
185
+ ),
186
+ ...props
187
+ },
188
+ ref,
189
+ ) => (
190
+ <Suspense fallback={suspenseFallback}>
191
+ <PhoneField
192
+ {...props}
193
+ ref={ref}
194
+ label={label}
195
+ fullWidth={fullWidth}
196
+ helperText={helperText}
197
+ />
198
+ </Suspense>
199
+ ),
200
+ );
@@ -0,0 +1,54 @@
1
+ import { Typography } from '@material-ui/core';
2
+ import { makeStyles } from '@material-ui/styles';
3
+ import { SuperDispatchTheme } from '@superdispatch/ui';
4
+ import clsx from 'clsx';
5
+ import { forwardRef, ImgHTMLAttributes, Ref } from 'react';
6
+ import { CountryISO } from '../country-code-metadata/CountryCodeMetadata';
7
+
8
+ const useStyles = makeStyles(
9
+ (theme: SuperDispatchTheme) => ({
10
+ root: { minHeight: theme.spacing(2), minWidth: theme.spacing(2.75) },
11
+ }),
12
+ { name: 'SD-PhoneFieldFlag' },
13
+ );
14
+
15
+ export interface PhoneFieldFlagProps
16
+ extends Omit<ImgHTMLAttributes<HTMLImageElement>, 'src'> {
17
+ country: CountryISO;
18
+ }
19
+
20
+ export const PhoneFieldFlag = forwardRef<HTMLElement, PhoneFieldFlagProps>(
21
+ ({ country, alt = country, className, ...props }, ref) => {
22
+ const styles = useStyles();
23
+
24
+ if (
25
+ country === 'AC' ||
26
+ country === 'BQ' ||
27
+ country === 'EH' ||
28
+ country === 'TA'
29
+ ) {
30
+ return (
31
+ <Typography
32
+ ref={ref}
33
+ variant="h6"
34
+ align="center"
35
+ component="span"
36
+ color="textSecondary"
37
+ className={clsx(styles.root, className)}
38
+ >
39
+ {country}
40
+ </Typography>
41
+ );
42
+ }
43
+
44
+ return (
45
+ <img
46
+ {...props}
47
+ alt={alt}
48
+ ref={ref as Ref<HTMLImageElement>}
49
+ className={clsx(styles.root, className)}
50
+ src={`https://cdn.jsdelivr.net/gh/madebybowtie/FlagKit@2.2/Assets/SVG/${country}.svg`}
51
+ />
52
+ );
53
+ },
54
+ );
@@ -0,0 +1,53 @@
1
+ import { Divider, Menu, MenuProps } from '@material-ui/core';
2
+ import { makeStyles } from '@material-ui/styles';
3
+ import { SuperDispatchTheme } from '@superdispatch/ui';
4
+ import { forwardRef } from 'react';
5
+ import {
6
+ CountryISO,
7
+ listCountries,
8
+ } from '../country-code-metadata/CountryCodeMetadata';
9
+ import { PhoneFieldMenuItem } from './PhoneFieldMenuItem';
10
+
11
+ const useStyles = makeStyles(
12
+ (theme: SuperDispatchTheme) => ({
13
+ paper: {
14
+ maxHeight: theme.spacing(30),
15
+ },
16
+ }),
17
+ { name: 'SD-PhoneFieldMenu' },
18
+ );
19
+
20
+ export interface PhoneFieldMenuProps extends Pick<MenuProps, 'anchorEl'> {
21
+ onClose: () => void;
22
+ value: CountryISO;
23
+ onChange: (country: CountryISO) => void;
24
+ }
25
+
26
+ export const PhoneFieldMenu = forwardRef<unknown, PhoneFieldMenuProps>(
27
+ ({ anchorEl, value, onClose, onChange }, ref) => {
28
+ const styles = useStyles();
29
+
30
+ return (
31
+ <Menu
32
+ ref={ref}
33
+ open={!!anchorEl}
34
+ onClose={onClose}
35
+ anchorEl={anchorEl}
36
+ classes={{ paper: styles.paper }}
37
+ >
38
+ {listCountries().map((country) => [
39
+ <PhoneFieldMenuItem
40
+ key={country}
41
+ country={country}
42
+ selected={country === value}
43
+ onClick={() => {
44
+ onChange(country);
45
+ onClose();
46
+ }}
47
+ />,
48
+ country === 'NZ' && <Divider key="divider" />,
49
+ ])}
50
+ </Menu>
51
+ );
52
+ },
53
+ );
@@ -0,0 +1,12 @@
1
+ import { renderCSS } from '@superdispatch/ui-testutils';
2
+ import { PhoneFieldMenuItem } from './PhoneFieldMenuItem';
3
+
4
+ test('css', () => {
5
+ expect(
6
+ renderCSS(<PhoneFieldMenuItem country="US" />, ['SD-PhoneFieldMenuItem']),
7
+ ).toMatchInlineSnapshot(`
8
+ .SD-PhoneFieldMenuItem-flag {
9
+ margin-right: 8px;
10
+ }
11
+ `);
12
+ });
@@ -0,0 +1,61 @@
1
+ import {
2
+ MenuItem,
3
+ MenuItemClassKey,
4
+ MenuItemProps,
5
+ Theme,
6
+ Typography,
7
+ } from '@material-ui/core';
8
+ import { ClassNameMap, makeStyles } from '@material-ui/styles';
9
+ import {
10
+ forwardRef,
11
+ ForwardRefExoticComponent,
12
+ RefAttributes,
13
+ useMemo,
14
+ } from 'react';
15
+ import {
16
+ CountryISO,
17
+ formatCountry,
18
+ getCountryCode,
19
+ } from '../country-code-metadata/CountryCodeMetadata';
20
+ import { PhoneFieldFlag } from './PhoneFieldFlag';
21
+
22
+ export type PhoneFieldMenuItemClassKey = MenuItemClassKey | 'flag';
23
+
24
+ const useStyles = makeStyles<
25
+ Theme,
26
+ { classes?: Partial<ClassNameMap<PhoneFieldMenuItemClassKey>> },
27
+ PhoneFieldMenuItemClassKey
28
+ >(
29
+ (theme) => ({
30
+ dense: {},
31
+ gutters: {},
32
+ root: {},
33
+ selected: {},
34
+ flag: { marginRight: theme.spacing(1) },
35
+ }),
36
+ { name: 'SD-PhoneFieldMenuItem' },
37
+ );
38
+
39
+ export interface PhoneFieldMenuItemProps
40
+ extends RefAttributes<HTMLLIElement>,
41
+ Omit<MenuItemProps, 'classes' | 'children'> {
42
+ country: CountryISO;
43
+ classes?: Partial<ClassNameMap<PhoneFieldMenuItemClassKey>>;
44
+ }
45
+
46
+ export const PhoneFieldMenuItem: ForwardRefExoticComponent<PhoneFieldMenuItemProps> =
47
+ forwardRef<HTMLLIElement, PhoneFieldMenuItemProps>(
48
+ ({ country, classes, ...props }, ref) => {
49
+ const { flag: flagClassName, ...styles } = useStyles({ classes });
50
+ const countryCode = useMemo(() => getCountryCode(country), [country]);
51
+
52
+ return (
53
+ <MenuItem {...props} ref={ref} button={true} classes={styles}>
54
+ <PhoneFieldFlag country={country} className={flagClassName} />
55
+ {formatCountry(country)}
56
+ &nbsp;
57
+ <Typography color="textSecondary">{countryCode}</Typography>
58
+ </MenuItem>
59
+ );
60
+ },
61
+ );
@@ -0,0 +1,68 @@
1
+ import { ButtonBase, InputAdornment, Typography } from '@material-ui/core';
2
+ import { ArrowDropDown, ArrowDropUp } from '@material-ui/icons';
3
+ import { makeStyles } from '@material-ui/styles';
4
+ import { Color, SuperDispatchTheme } from '@superdispatch/ui';
5
+ import { forwardRef, useMemo } from 'react';
6
+ import {
7
+ CountryISO,
8
+ formatCountry,
9
+ getCountryCode,
10
+ } from '../country-code-metadata/CountryCodeMetadata';
11
+ import { PhoneFieldFlag } from './PhoneFieldFlag';
12
+
13
+ const useStyles = makeStyles(
14
+ (theme: SuperDispatchTheme) => ({
15
+ root: {
16
+ marginLeft: theme.spacing(-1),
17
+ marginRight: 0,
18
+ },
19
+ button: {
20
+ color: Color.Blue300,
21
+ padding: theme.spacing(0.5, 0.5, 0.5, 1),
22
+ borderRadius: theme.spacing(0.5, 0, 0, 0.5),
23
+ '&:hover, &:focus': {
24
+ backgroundColor: Color.Blue50,
25
+ },
26
+ },
27
+ }),
28
+ { name: 'SD-PhoneFieldStartAdornment' },
29
+ );
30
+
31
+ export interface PhoneFieldProps {
32
+ onClick?: () => void;
33
+ isExpanded?: boolean;
34
+ country: CountryISO;
35
+ }
36
+
37
+ export const PhoneFieldStartAdornment = forwardRef<
38
+ HTMLDivElement,
39
+ PhoneFieldProps
40
+ >(({ country, onClick, isExpanded }, ref) => {
41
+ const styles = useStyles();
42
+ const [title, countryCode] = useMemo(() => {
43
+ const code = `+${getCountryCode(country)}`;
44
+
45
+ return [`${formatCountry(country)}: ${code}`, code];
46
+ }, [country]);
47
+
48
+ return (
49
+ <InputAdornment ref={ref} position="start" className={styles.root}>
50
+ <ButtonBase
51
+ title={title}
52
+ onClick={onClick}
53
+ className={styles.button}
54
+ aria-expanded={isExpanded}
55
+ >
56
+ <PhoneFieldFlag country={country} />
57
+
58
+ {isExpanded ? (
59
+ <ArrowDropUp htmlColor={Color.Dark200} />
60
+ ) : (
61
+ <ArrowDropDown htmlColor={Color.Dark200} />
62
+ )}
63
+
64
+ <Typography color="textPrimary">{countryCode}</Typography>
65
+ </ButtonBase>
66
+ </InputAdornment>
67
+ );
68
+ });
@@ -0,0 +1,60 @@
1
+ import { renderComponent } from '@superdispatch/ui-testutils';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import { PhoneLink } from './PhoneLink';
4
+
5
+ test('basic', async () => {
6
+ renderComponent(<PhoneLink phone="+12015550123" />);
7
+ const link = await screen.findByRole('link');
8
+
9
+ expect(link).toHaveTextContent('+1 201-555-0123');
10
+ expect(link).toHaveAttribute('href', 'tel:+1-201-555-0123');
11
+ });
12
+
13
+ test('format', async () => {
14
+ renderComponent(<PhoneLink phone="+12015550123" format="national" />);
15
+
16
+ const link = await screen.findByRole('link');
17
+
18
+ expect(link).toHaveTextContent('201-555-0123');
19
+ expect(link).toHaveAttribute('href', 'tel:+1-201-555-0123');
20
+ });
21
+
22
+ test('country', async () => {
23
+ renderComponent(<PhoneLink phone="2015550123" country="NZ" />);
24
+
25
+ const link = await screen.findByRole('link');
26
+
27
+ expect(link).toHaveTextContent('+64 20 1555 0123');
28
+ expect(link).toHaveAttribute('href', 'tel:+64-20-1555-0123');
29
+ });
30
+
31
+ test('invalid', async () => {
32
+ const view = renderComponent(
33
+ <PhoneLink phone="Phone: (585) 617-1234 (Home) | (585) 489-1234 (Cell)" />,
34
+ );
35
+
36
+ await waitFor(() => {
37
+ expect(screen.queryByText('Suspended…')).toBeNull();
38
+ });
39
+
40
+ expect(view.container).toMatchInlineSnapshot(`<div />`);
41
+ });
42
+
43
+ test('fallback', async () => {
44
+ const view = renderComponent(
45
+ <PhoneLink
46
+ phone="Phone: (585) 617-1234 (Home) | (585) 489-1234 (Cell)"
47
+ fallback="Phone: (585) 617-1234 (Home) | (585) 489-1234 (Cell)"
48
+ />,
49
+ );
50
+
51
+ await waitFor(() => {
52
+ expect(screen.queryByText('Suspended…')).toBeNull();
53
+ });
54
+
55
+ expect(view.container).toMatchInlineSnapshot(`
56
+ <div>
57
+ Phone: (585) 617-1234 (Home) | (585) 489-1234 (Cell)
58
+ </div>
59
+ `);
60
+ });
@@ -0,0 +1,9 @@
1
+ import { Meta } from '@storybook/react';
2
+ import { PhoneLink } from './PhoneLink';
3
+
4
+ export default { title: 'Phones/PhoneLink', component: PhoneLink } as Meta;
5
+
6
+ export const basic = () => <PhoneLink phone="+12015550123" />;
7
+ export const fallback = () => (
8
+ <PhoneLink phone="noop" fallback="Invalid Phone Number" />
9
+ );
@@ -0,0 +1,59 @@
1
+ import { Link, LinkProps } from '@material-ui/core';
2
+ import { renderChildren } from '@superdispatch/ui';
3
+ import {
4
+ forwardRef,
5
+ ForwardRefExoticComponent,
6
+ ReactNode,
7
+ RefAttributes,
8
+ Suspense,
9
+ useMemo,
10
+ } from 'react';
11
+ import { CountryISO } from '../country-code-metadata/CountryCodeMetadata';
12
+ import {
13
+ PhoneNumberFormat,
14
+ usePhoneService,
15
+ } from '../phone-service/PhoneService';
16
+
17
+ export interface PhoneLinkProps
18
+ extends RefAttributes<HTMLAnchorElement>,
19
+ Omit<LinkProps, 'ref' | 'href' | 'children'> {
20
+ phone: unknown;
21
+ country?: CountryISO;
22
+ format?: PhoneNumberFormat;
23
+ fallback?: ReactNode;
24
+ }
25
+
26
+ export const PhoneLink: ForwardRefExoticComponent<PhoneLinkProps> = forwardRef(
27
+ ({ phone, country, fallback, format = 'international', ...props }, ref) => {
28
+ const service = usePhoneService();
29
+ const [text, href] = useMemo(() => {
30
+ if (service.checkPossibility(phone) !== 'is-possible') {
31
+ return [undefined, undefined];
32
+ }
33
+
34
+ return [
35
+ service.format(phone, { country, format }),
36
+ service.format(phone, { country, format: 'rfc3966' }),
37
+ ];
38
+ }, [phone, country, format, service]);
39
+
40
+ return !href ? (
41
+ renderChildren(fallback)
42
+ ) : (
43
+ <Link {...props} ref={ref} href={href}>
44
+ {text}
45
+ </Link>
46
+ );
47
+ },
48
+ );
49
+
50
+ export interface SuspendedPhoneLinkProps extends PhoneLinkProps {
51
+ suspenseFallback?: ReactNode;
52
+ }
53
+
54
+ export const SuspendedPhoneLink: ForwardRefExoticComponent<SuspendedPhoneLinkProps> =
55
+ forwardRef(({ suspenseFallback = null, ...props }, ref) => (
56
+ <Suspense fallback={suspenseFallback}>
57
+ <PhoneLink {...props} ref={ref} />
58
+ </Suspense>
59
+ ));