Skip to content

Commit

Permalink
feat: phone input using tailwind and headlessui
Browse files Browse the repository at this point in the history
  • Loading branch information
kibolho committed Dec 9, 2023
1 parent 2bac4df commit 18c1d87
Show file tree
Hide file tree
Showing 14 changed files with 1,192 additions and 517 deletions.
2 changes: 2 additions & 0 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import '../src/stories/UiLibsExample/components/Tailwind/tailwind_output.css';

export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"@commitlint/config-conventional": "^17.4.0",
"@emotion/react": "^11",
"@emotion/styled": "^11",
"@headlessui/react": "^1.7.17",
"@mui/material": "^5.10.16",
"@playwright/test": "^1.27.1",
"@semantic-release/changelog": "^6.0.2",
Expand Down
133 changes: 133 additions & 0 deletions packages/docs/docs/04-Advanced Usage/02-useWithUiLibs.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,136 @@ export const MuiPhone: React.FC<MUIPhoneProps> = ({
</TabItem>

</Tabs>

## Tailwind UI + Headless Example

import { TailwindHeadlessUIPhone } from '@site/src/components/Tailwind/TailwindHeadlessUIPhone.tsx'

export const PhoneTailwindComponentWrapper = () => {
const [value, setValue] = useState('');
return <TailwindHeadlessUIPhone value={value} onChange={setValue}/>
};

<div style={{ margin: "3rem 0 2rem" }}>
<PhoneTailwindComponentWrapper />
</div>

```tsx
import { Menu } from '@headlessui/react';
import React from 'react';
import {
defaultCountries,
FlagImage,
parseCountry,
usePhoneInput,
} from 'react-international-phone';
import 'react-international-phone/style.css';

export interface TailwindHeadlessUIPhoneProps {
value: string;
onChange: (phone: string) => void;
}

export const TailwindHeadlessUIPhone: React.FC<TailwindHeadlessUIPhoneProps> = ({
value,
onChange,
}) => {

const { inputValue, handlePhoneValueChange, inputRef, country, setCountry } =
usePhoneInput({
defaultCountry: 'us',
value,
onChange: (data) => {
onChange(data.phone);
},
});

return (
<div className="flex items-start">
<div className="flex flex-col">
<Menu>
<Menu.Button
data-dropdown-toggle="dropdown-phone"
className="z-10 inline-flex flex-shrink-0 items-center rounded-s-lg border border-gray-300 bg-gray-100 px-4 py-2 text-center text-sm font-medium text-gray-900 hover:bg-gray-200 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-700"
>
{({ open }) => (
<>
<FlagImage iso2={country.iso2} />
{open && (
<span className="ml-1.5 text-left">
{country.name} (+{country.dialCode})
</span>
)}
<svg
className="ms-2.5 h-2.5 w-2.5 ml-1.5 flex-shrink-0"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 4 4 4-4"
/>
</svg>
</>
)}
</Menu.Button>
<Menu.Items className="z-10 h-48 w-52 divide-y divide-gray-100 overflow-y-auto rounded-lg bg-white shadow dark:bg-gray-700">
{defaultCountries.map((c) => {
const country = parseCountry(c);
return (
<Menu.Item>
{({ active }) => (
<button
type="button"
className="inline-flex w-full px-4 py-2 "
role="menuitem"
onClick={(e: any) => {
e.stopPropagation();
setCountry(country.iso2);
}}
>
<div className="inline-flex items-center">
<FlagImage
iso2={country.iso2}
style={{ marginRight: '8px' }}
/>
<span className="text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-600 dark:hover:text-white">
{country.name} (+{country.dialCode})
</span>
</div>
</button>
)}
</Menu.Item>
);
})}
</Menu.Items>
</Menu>
</div>
<label
htmlFor="phone-input"
className="sr-only mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Phone number:
</label>
<div className="relative w-full">
<input
type="text"
id="phone-input"
className="z-20 block w-full rounded-e-lg border border-s-0 border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:border-s-gray-700 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500"
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
placeholder="123-456-7890"
value={inputValue}
onChange={handlePhoneValueChange}
ref={inputRef}
required
/>
</div>
</div>
);
};
```
2 changes: 1 addition & 1 deletion packages/docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const config = {
},
blog: false,
theme: {
customCss: require.resolve('./src/css/custom.css'),
customCss: [require.resolve('./src/css/custom.css'), require.resolve('./src/components/Tailwind/tailwind_output.css')],
},
}),
],
Expand Down
3 changes: 2 additions & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"prism-react-renderer": "^1.3.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-international-phone": "workspace:^"
"react-international-phone": "workspace:^",
"@headlessui/react": "^1.7.17"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.3",
Expand Down
125 changes: 125 additions & 0 deletions packages/docs/src/components/Tailwind/TailwindHeadlessUIPhone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* ! MuiPhone component is a copypaste of component -> src/stories/UiLibsExample/components/Tailwind
* Make sure that the original component is updated if wanna make changes here
*/


import 'react-international-phone/style.css';

import { Menu } from '@headlessui/react';
import React from 'react';
import {
defaultCountries,
FlagImage,
parseCountry,
usePhoneInput,
} from 'react-international-phone';


export interface TailwindHeadlessUIPhoneProps {
value: string;
onChange: (phone: string) => void;
}

export const TailwindHeadlessUIPhone: React.FC<TailwindHeadlessUIPhoneProps> = ({
value,
onChange,
}) => {

const { inputValue, handlePhoneValueChange, inputRef, country, setCountry } =
usePhoneInput({
defaultCountry: 'us',
value,
onChange: (data) => {
onChange(data.phone);
},
});

return (
<div className="flex items-start">
<div className="flex flex-col">
<Menu>
<Menu.Button
data-dropdown-toggle="dropdown-phone"
className="z-10 inline-flex flex-shrink-0 items-center rounded-s-lg border border-gray-300 bg-gray-100 px-4 py-2 text-center text-sm font-medium text-gray-900 hover:bg-gray-200 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-700"
>
{({ open }) => (
<>
<FlagImage iso2={country.iso2} />
{open && (
<span className="ml-1.5 text-left">
{country.name} (+{country.dialCode})
</span>
)}
<svg
className="ms-2.5 h-2.5 w-2.5 ml-1.5 flex-shrink-0"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m1 1 4 4 4-4"
/>
</svg>
</>
)}
</Menu.Button>
<Menu.Items className="z-10 h-48 w-52 divide-y divide-gray-100 overflow-y-auto rounded-lg bg-white shadow dark:bg-gray-700">
{defaultCountries.map((c) => {
const country = parseCountry(c);
return (
<Menu.Item>
{({ active }) => (
<button
type="button"
className="inline-flex w-full px-4 py-2 "
role="menuitem"
onClick={(e: any) => {
e.stopPropagation();
setCountry(country.iso2);
}}
>
<div className="inline-flex items-center">
<FlagImage
iso2={country.iso2}
style={{ marginRight: '8px' }}
/>
<span className="text-left text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-600 dark:hover:text-white">
{country.name} (+{country.dialCode})
</span>
</div>
</button>
)}
</Menu.Item>
);
})}
</Menu.Items>
</Menu>
</div>
<label
htmlFor="phone-input"
className="sr-only mb-2 text-sm font-medium text-gray-900 dark:text-white"
>
Phone number:
</label>
<div className="relative w-full">
<input
type="text"
id="phone-input"
className="z-20 block w-full rounded-e-lg border border-s-0 border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:border-s-gray-700 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500"
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
placeholder="123-456-7890"
value={inputValue}
onChange={handlePhoneValueChange}
ref={inputRef}
required
/>
</div>
</div>
);
};
Loading

0 comments on commit 18c1d87

Please sign in to comment.