Skip to content

Conversation

@adityajha2005
Copy link
Contributor

PR Description:

What does this PR do?

  • Add PhoneInput component with international phone number formatting and validation
  • Support for EN, DE, and PL countries with minimal bundle size
  • Real-time formatting and validation using libphonenumber-js
  • Paste handling with automatic phone number detection and formatting
  • Default country support and international/national format display options

Related Ticket(s)

Key Changes

  • Core Implementation: Created PhoneInput component in packages/ui/src/elements/phone-input.tsx that extends the existing Input component for design system consistency
  • Dependencies: Added libphonenumber-js@^1.11.18 and input-format@^0.3.14 to handle phone number parsing and real-time formatting
  • Bundle Optimization: Implemented custom metadata bundle (packages/ui/src/lib/phone/metadata.json) containing only EN, DE, and PL country data to minimize bundle size instead of importing full libphonenumber-js metadata
  • Component Export: Added proper TypeScript exports via packages/ui/src/components/PhoneInput/index.ts making the component available as @o2s/ui/components/PhoneInput
  • Side Effects: No breaking changes - this is purely additive functionality that doesn't affect existing components or APIs

How to test

  • Setup: No migrations needed. Dependencies will be installed automatically via the updated package.json
  • Installation: Run npm install in the root directory to install new dependencies

Step-by-step testing:

  1. Import the PhoneInput component:

    import { PhoneInput } from '@o2s/ui/components/PhoneInput';
  2. Test basic functionality:

    <PhoneInput 
      defaultCountry="PL" 
      placeholder="Enter phone number"
      onChange={({ e164, national, isValid, country }) => {
        console.log({ e164, national, isValid, country });
      }}
    />
  3. Test different scenarios:

    • Default country formatting: Type 500500500 with defaultCountry="PL" - should format to +48 500 500 500
    • International prefix: Type +49123456789 - should format according to German rules
    • Paste functionality: Paste 0048123456789 - should convert 00 to + and format properly
    • Validation: Enter invalid numbers - should show aria-invalid and return isValid: false
    • Different countries: Test with defaultCountry="DE" and defaultCountry="GB"
  4. Test accessibility:

    • Verify aria-invalid attribute is set for invalid inputs
    • Check that inputMode="tel" and type="tel" are applied

Framework Experience Feedback

Working with this framework was quite pleasant overall:

Getting Started:

  • The monorepo structure was intuitive and well-organized
  • Package dependencies were clearly defined and easy to understand
  • The existing Input component provided a solid foundation to build upon

Development Experience:

  • TypeScript configuration was well set up, making type safety straightforward
  • The existing design system patterns (like extending Input props) were clear and consistent
  • ESLint and Prettier configurations made code formatting seamless
  • The component export structure in package.json was well thought out

Challenges:

  • Initially took some time to understand the custom metadata approach for libphonenumber-js, but the bundle size optimization was worth it
  • Had to work around some TypeScript compatibility issues with input-format/react, but the solution was clean

Overall: The framework strikes a good balance between flexibility and structure. The monorepo setup makes it easy to work across packages, and the existing patterns provide good guidance for new components. Having a reference implementation to build upon was extremely valuable and accelerated development..

Media (Loom or gif)

  • NA

/claim #306

@vercel
Copy link

vercel bot commented Oct 17, 2025

@adityajha2005 is attempting to deploy a commit to the Hycom Team Team on Vercel.

A member of the Team first needs to authorize it.

@opirebot
Copy link

opirebot bot commented Oct 17, 2025

👀 We've notified the reward creators here.
Make sure your payment account is ready to receive the payment for your hard work 💪

@marcinkrasowski marcinkrasowski self-requested a review October 20, 2025 06:13
"baseUrl": "./",
"jsx": "preserve",
"resolveJsonModule": true,
"types": ["react", "react-dom"],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these changes really necessary? This config extends ui.json where e.g. jsx:preserver is already declared; did you add these here to solve some issue?

@@ -0,0 +1,4 @@
export { default as PhoneInput } from '../../elements/phone-input';
export type { PhoneInputProps, PhoneInputChangePayload } from '../../elements/phone-input';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placing this in the components folder is not necessary - this is still a rather simple component, and, like a regular Input, it's enough to have this one in elements as well


const NAME = 'PhoneInput';

export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please update this component to not rely on forwardRef - you've already done that for other components in the other PR, so let's keep things consistent :)

}}
type="tel"
inputMode="tel"
name={props.name ?? NAME}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAME here is not needed - in the example code provided it was for adding data-test attributes for e2e testing, but we don't have that here (yet)


const NAME = 'PhoneInput';

export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a Storybook story for this component; once that is prepared I'll take a look how it works


import InputFormat from 'input-format/react';
import { AsYouType, CountryCode, getCountryCallingCode, parsePhoneNumberCharacter } from 'libphonenumber-js/core';
import type { MetadataJson } from 'libphonenumber-js/types';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint reports here Cannot find module 'libphonenumber-js/types' or its corresponding type declarations - see if it can be imported in another way (perhaps in newer version exports have changed)

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore input-format expects a different component type
inputComponent={Input}
ref={ref}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS reports here Property 'ref' does not exist on type 'IntrinsicAttributes & DefaultInputComponentProps & { parse: ParseFunction; format: FormatFunction; ... 5 more ...; onKeyDown?(value?: string | undefined): void; }' - please check how this can be fixed

useEffect(() => {
if (value === undefined) return; // uncontrolled external value not provided
const formatted = format(value).text;
setText(formatted);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my IDE reports here an ESLint warning Error: Calling setState synchronously within an effect can trigger cascading renders - please see how this can be fixed

const formatted = format(value).text;
setText(formatted);
// emit change based on incoming value as well
const asYouType = new AsYouType(undefined, metadata as any);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please see if/how casting as any can be removed from metadata as any and other places in this component; we'd like to have everything properly typed

@marcinkrasowski
Copy link
Collaborator

hi @adityajha2005, are you still interested in contributing to this PR and bounty?

@adityajha2005
Copy link
Contributor Author

hey @marcinkrasowski really sorry for the delay, yeah I will be working on this issue.

@marcinkrasowski
Copy link
Collaborator

@adityajha2005 no worries, take your time, I just wanted to know if you are still working on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants