Skip to content

Commit

Permalink
feat(components): add basic input and story
Browse files Browse the repository at this point in the history
  • Loading branch information
maximeperrault authored and maximeperraultdev committed Jul 1, 2024
1 parent 04fbf57 commit 10cb8ee
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 35 deletions.
43 changes: 43 additions & 0 deletions src/fields/Phone/PhoneInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Field } from '@elements/Field'
import { Label } from '@elements/Label'
import { inputStyle } from '@fields/shared/StyledInputBox'
import { forwardRef, useId, type ComponentProps } from 'react'
import { IMaskInput } from 'react-imask'
import styled from 'styled-components'

import type { Promisable } from 'type-fest'

export type PhoneInputProps = {
error?: string
label: string
onChange: (nextValue: string | undefined) => Promisable<void>
value: string | undefined
} & Omit<ComponentProps<'input'>, 'onChange' | 'value'>

export const PhoneInput = forwardRef<HTMLInputElement, PhoneInputProps>(({ label, onChange, value, ...props }, ref) => {
const id = useId()

return (
<Field>
<Label htmlFor={id}>{label}</Label>
<StyledIMaskInput
ref={ref}
id={id}
mask={[
{ definitions: { '#': /[6-7]/, '@': /0/ }, mask: '@# 00 00 00 00' },
{ definitions: { '#': /[1-9]/, '+': /[\\+]/ }, mask: '+#[000] 000 000 000 000 000' }
]}
onAccept={(nextValue: string) => {
onChange(nextValue)
}}
unmask
value={value}
{...props}
/>
</Field>
)
})

const StyledIMaskInput = styled(IMaskInput)`
${inputStyle}
`
74 changes: 39 additions & 35 deletions src/fields/shared/StyledInputBox.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import styled from 'styled-components'
import styled, { css } from 'styled-components'

import {
getFieldBackgroundColorFactory,
Expand All @@ -25,6 +25,43 @@ const PADDING_WITH_ICON: Record<Size, string> = {
[Size.NORMAL]: '3px 38px 6px 8px',
[Size.SMALL]: '3px 38px 6px 8px'
}
export const inputStyle = css<StyledInputBoxProps>`
background-color: ${getFieldBackgroundColorFactory()};
border: solid 1px ${getFieldBorderColorFactoryForState('default')};
border-radius: 0;
color: ${p => p.theme.color.gunMetal};
${p => p.$isReadOnly && `cursor: default;`}
font-size: 13px;
font-weight: 500;
line-height: 19px;
padding: 3px 8px 6px;
vertical-align: center;
width: 100%;
&::placeholder {
color: ${getFieldPlaceholderColorFactoryForState('default')};
}
&:hover {
background-color: ${getFieldBackgroundColorFactory()};
border: solid 1px ${getFieldBorderColorFactoryForState('hover')} !important;
&::placeholder {
color: ${getFieldPlaceholderColorFactoryForState('hover')};
}
}
&:active,
&:focus {
background-color: ${getFieldBackgroundColorFactory()};
border: solid 1px ${getFieldBorderColorFactoryForState('focus')} !important;
outline: 0;
&::placeholder {
color: ${getFieldPlaceholderColorFactoryForState('focus')};
}
}
`

export const StyledInputBox = styled.div<StyledInputBoxProps>`
display: flex;
Expand All @@ -38,41 +75,8 @@ export const StyledInputBox = styled.div<StyledInputBoxProps>`
> input,
> .rs-auto-complete > input {
background-color: ${getFieldBackgroundColorFactory()};
border: solid 1px ${getFieldBorderColorFactoryForState('default')};
border-radius: 0;
color: ${p => p.theme.color.gunMetal};
${p => p.$isReadOnly && `cursor: default;`}
flex-grow: 1;
font-size: 13px;
font-weight: 500;
line-height: 1;
padding: ${p => (p.$hasIcon ? PADDING_WITH_ICON[p.$size] : PADDING[p.$size])};
vertical-align: center;
&::placeholder {
color: ${getFieldPlaceholderColorFactoryForState('default')};
}
&:hover {
background-color: ${getFieldBackgroundColorFactory()};
border: solid 1px ${getFieldBorderColorFactoryForState('hover')} !important;
&::placeholder {
color: ${getFieldPlaceholderColorFactoryForState('hover')};
}
}
&:active,
&:focus {
background-color: ${getFieldBackgroundColorFactory()};
border: solid 1px ${getFieldBorderColorFactoryForState('focus')} !important;
outline: 0;
&::placeholder {
color: ${getFieldPlaceholderColorFactoryForState('focus')};
}
}
${inputStyle}
}
> .Element-IconBox {
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export { MultiSelect } from './fields/MultiSelect'
export { MultiRadio } from './fields/MultiRadio'
export { MultiZoneEditor } from './fields/MultiZoneEditor'
export { NumberInput } from './fields/NumberInput'
export { PhoneInput } from './fields/Phone/PhoneInput'
export { Radio } from './fields/Radio'
export { RichBooleanCheckbox } from './fields/RichBooleanCheckbox'
export { Search } from './fields/Search'
Expand Down Expand Up @@ -230,6 +231,7 @@ export type { MultiSelectProps } from './fields/MultiSelect'
export type { MultiRadioProps } from './fields/MultiRadio'
export type { MultiZoneEditorProps } from './fields/MultiZoneEditor'
export type { NumberInputProps } from './fields/NumberInput'
export type { PhoneInputProps } from './fields/Phone/PhoneInput'
export type { RichBooleanCheckboxProps } from './fields/RichBooleanCheckbox'
export type { RadioProps } from './fields/Radio'
export type { SearchProps } from './fields/Search'
Expand Down
68 changes: 68 additions & 0 deletions stories/fields/PhoneInput.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useState } from 'react'

import { Output } from '../../.storybook/components/Output'
import { ARG_TYPE } from '../../.storybook/constants'
import { generateStoryDecorator } from '../../.storybook/utils/generateStoryDecorator'
import { PhoneInput, useFieldControl } from '../../src'

import type { PhoneInputProps } from '../../src'
import type { Meta } from '@storybook/react'

/* eslint-disable sort-keys-fix/sort-keys-fix */
const meta: Meta<PhoneInputProps> = {
title: 'Fields/PhoneInput',
component: PhoneInput,

argTypes: {
// disabled: ARG_TYPE.OPTIONAL_BOOLEAN,
// error: ARG_TYPE.OPTIONAL_STRING,
// isErrorMessageHidden: ARG_TYPE.OPTIONAL_BOOLEAN,
// isLabelHidden: ARG_TYPE.OPTIONAL_BOOLEAN,
// isLight: ARG_TYPE.OPTIONAL_BOOLEAN,
// isRequired: ARG_TYPE.OPTIONAL_BOOLEAN,
// isTransparent: ARG_TYPE.OPTIONAL_BOOLEAN,
// isUndefinedWhenDisabled: ARG_TYPE.OPTIONAL_BOOLEAN,
onChange: ARG_TYPE.NO_CONTROL_INPUT,
// readOnly: ARG_TYPE.OPTIONAL_BOOLEAN,
value: ARG_TYPE.OPTIONAL_STRING
},

args: {
// disabled: false,
// error: '',
// isErrorMessageHidden: false,
// isLabelHidden: false,
// isLight: false,
// isRequired: true,
// isTransparent: false,
// isUndefinedWhenDisabled: false,
label: 'A phone number input'
// name: 'myNumberInput',
// placeholder: 'Pick a number',
// readOnly: false
},

decorators: [
generateStoryDecorator({
box: { width: 640 },
withBackgroundButton: true
})
]
}
/* eslint-enable sort-keys-fix/sort-keys-fix */

export default meta

export function _PhoneInput(props: PhoneInputProps) {
const [outputValue, setOutputValue] = useState<string | undefined | '∅'>('∅')

const { controlledOnChange, controlledValue } = useFieldControl(props.value, setOutputValue, '' as any)

return (
<>
<PhoneInput {...props} onChange={controlledOnChange} value={controlledValue} />

{outputValue !== '∅' && <Output value={outputValue} />}
</>
)
}

0 comments on commit 10cb8ee

Please sign in to comment.