Skip to content

Commit

Permalink
feat(usephoneinput): usePhoneInput supports national format (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
trinhthinh388 authored Feb 20, 2024
1 parent df77bac commit 5642c4e
Show file tree
Hide file tree
Showing 13 changed files with 236 additions and 22 deletions.
2 changes: 1 addition & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"clean": "rimraf .turbo && rimraf node_modules && rimraf .next"
},
"dependencies": {
"@react-awesome/components": "1.0.11",
"@react-awesome/components": "1.0.12",
"classnames": "^2.5.1",
"lodash": "^4.17.21",
"lucide-react": "^0.315.0",
Expand Down
87 changes: 87 additions & 0 deletions apps/docs/src/pages/docs/phone-input/use-phone-input.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,81 @@ const Example = () => {
}
```

## Local Phone Input

By default **usePhoneInput** has `mode` is set to `international`.

When `mode` is `international` the value will be formatted as `e164`.

When `mode` is `national` the country code and the `+` sign will be ignored. Value is formatted as national format of the current selected country which provided via `country` property.

export const LocalExample = () => {
const [value, setValue] = useState({
isPossible: false,
isValid: false,
e164Value: '',
country: 'VN',
phoneCode: '84',
formattedValue: '',
isSupported: true,
});
const { register } = usePhoneInput({
onChange: (_, m) => {
setValue(m);
},
mode: 'local',
country: value.country
});

return (

<div>
<input
className="w-full border rounded-md px-3 py-2"
placeholder="I am a local phone input"
{...register('use-phone-input')}
/>
<h3 className="mt-3 font-bold text-xl underline underline-offset-4">
onChange event
</h3>
<ul className="mt-2">
{Object.keys(value).map((key) => {
const v = value[key]
return (
<li key={key}>
<span className="font-medium">👉 {key}</span>
<code className="ml-2 nx-border-black nx-border-opacity-[0.04] nx-bg-opacity-[0.03] nx-bg-black nx-break-words nx-rounded-md nx-border nx-py-0.5 nx-px-[.25em] nx-text-[.9em] dark:nx-border-white/10 dark:nx-bg-white/10">
{v.toString()}
</code>
</li>
)
})}
</ul>
</div>
); };

<Container>
<LocalExample />
</Container>

```jsx
import { usePhoneInput } from '@react-awesome/phone-input'

const Example = () => {
const { register } = usePhoneInput({
mode: 'national',
country: 'VN',
})

return (
<input
placeholder="I am a local phone input"
{...register('use-phone-input')}
/>
)
}
```

## Parameters

The `usePhoneInput` takes the following parameters:
Expand Down Expand Up @@ -123,6 +198,18 @@ Use smart caret.
- Type: `boolean`
- Default: `true`

#### `mode` (optional)

- Type: `international` | `national`
- Default: `international`

#### `country` (optional)

When country is provided, the country detection behavior will be disabled.

- Type: `CountryCode`
- Default: `undefined`

## API

The `usePhoneInput` returns the following props:
Expand Down
6 changes: 3 additions & 3 deletions apps/docs/src/pages/docs/use-breakpoint.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { useBreakpoint } from '@react-awesome/use-breakpoint'
const Example = () => {
const ref = useRef(null)

const { currentBreakpoint } = useBreakpoint(ref.curent)
const { currentBreakpoint } = useBreakpoint(ref.current)

return (
<div ref={ref}>
Expand Down Expand Up @@ -99,7 +99,7 @@ import { useBreakpoint } from '@react-awesome/use-breakpoint'
const Example = () => {
const ref = useRef(null)

const { currentBreakpoint } = useBreakpoint(ref.curent, {
const { currentBreakpoint } = useBreakpoint(ref.current, {
breakpoints: {
'📱': 320,
'💻': 480,
Expand Down Expand Up @@ -157,7 +157,7 @@ import { useBreakpoint } from '@react-awesome/use-breakpoint'
const Example = () => {
const ref = useRef(null)

const { smaller } = useBreakpoint(ref.curent)
const { smaller } = useBreakpoint(ref.current)

return (
<div ref={setRef}>
Expand Down
7 changes: 7 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @react-awesome/components

## 1.0.12

### Patch Changes

- Updated dependencies
- @react-awesome/phone-input@1.1.0

## 1.0.11

### Patch Changes
Expand Down
4 changes: 2 additions & 2 deletions packages/components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@react-awesome/components",
"version": "1.0.11",
"version": "1.0.12",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down Expand Up @@ -39,7 +39,7 @@
"access": "public"
},
"dependencies": {
"@react-awesome/phone-input": "1.0.7",
"@react-awesome/phone-input": "1.1.0",
"@react-awesome/use-click-outside": "0.0.3",
"@react-awesome/use-preserve-input-caret-position": "0.0.3",
"@react-awesome/use-selection-range": "0.0.3",
Expand Down
6 changes: 6 additions & 0 deletions packages/phone-input/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @react-awesome/phone-input

## 1.1.0

### Minor Changes

- Supports `national` format

## 1.0.7

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/phone-input/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@react-awesome/phone-input",
"version": "1.0.7",
"version": "1.1.0",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { formatInternational } from './formatInternational'

describe('formatInternational', () => {
it('Should return empty string when input value is not valid', () => {
expect(formatInternational('')).toBe('')
})

it('Should leave as-is when the value is already in international format.', () => {
expect(formatInternational('+123456')).toBe('+123456')
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { formatNational } from './formatNational'

describe('formatNational', () => {
it('Should leave as-is when the value is already in national format.', () => {
expect(formatNational('+123456')).toBe('123456')
})

it('Should auto remove + sign.', () => {
expect(formatNational('123456')).toBe('123456')
})

it('Should auto remove + sign and remove first 0 digit.', () => {
expect(formatNational('0123456')).toBe('123456')
})

it('Should return empty string when input value is not valid', () => {
expect(formatNational('')).toBe('')
})
})
12 changes: 12 additions & 0 deletions packages/phone-input/src/helpers/formatNational/formatNational.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { parseIncompletePhoneNumber } from 'libphonenumber-js'

export const formatNational = (phoneValue: string) => {
if (!phoneValue) return ''
if (phoneValue.startsWith('+'))
return parseIncompletePhoneNumber(phoneValue.replace(/\+/g, ''))

if (phoneValue.startsWith('0'))
return parseIncompletePhoneNumber(phoneValue.slice(1))

return parseIncompletePhoneNumber(phoneValue)
}
1 change: 1 addition & 0 deletions packages/phone-input/src/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './guessPhoneCountryByIncompleteNumber/guessCountryByIncompleteNum
export * from './formatInternational/formatInternational'
export * from './getPossibleCountriesByCallingCode/getPossibleCountriesByCallingCode'
export * from './checkCountryValidity/checkCountryValidity'
export * from './formatNational/formatNational'
60 changes: 56 additions & 4 deletions packages/phone-input/src/hooks/usePhoneInput.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@ const user = userEvent.setup({
})

const Comp = ({
country,
supportedCountries,
defaultCountry,
smartCaret,
value,
mode = 'international',
country,
}: {
country?: CountryCode[]
country?: CountryCode
supportedCountries?: CountryCode[]
defaultCountry?: CountryCode
smartCaret?: boolean
value?: string
mode?: any
}) => {
const { register, selectedCountry, setSelectedCountry } = usePhoneInput({
supportedCountries: country,
supportedCountries: supportedCountries,
defaultCountry,
smartCaret,
value,
mode,
country,
})

return (
Expand Down Expand Up @@ -89,7 +95,7 @@ describe('usePhoneInput', () => {
})

it('Should only allow supported country to be detected', async () => {
const { container } = render(<Comp country={['VN']} />)
const { container } = render(<Comp supportedCountries={['VN']} />)

const input = container.querySelector('input')

Expand Down Expand Up @@ -207,4 +213,50 @@ describe('usePhoneInput', () => {

expect(container.querySelector('#FI')).toBeVisible()
})

/**
* National format
*/
it('Should format value in national when mode is national', async () => {
const { container } = render(<Comp mode="national" country="VN" />)

const input = container.querySelector('input')

if (!input) {
throw new Error('input is not a valid element.')
}

expect(container.querySelector('#VN')).toBeVisible()

await act(async () => {
input.focus()
await user.keyboard('{1},{2},{3}')
})

// Should be treated as VN number +84 123 since country detector has been disabled.
expect(input.getAttribute('value')).toBe('123')

expect(container.querySelector('#VN')).toBeVisible()
})

it('Should only allow national phone number character', async () => {
const { container } = render(<Comp mode="national" country="VN" />)

const input = container.querySelector('input')

if (!input) {
throw new Error('input is not a valid element.')
}

expect(container.querySelector('#VN')).toBeVisible()

await act(async () => {
input.focus()
await user.keyboard('{+},{1},{2},{3}')
})

expect(input.getAttribute('value')).toBe('123')

expect(container.querySelector('#VN')).toBeVisible()
})
})
Loading

0 comments on commit 5642c4e

Please sign in to comment.