diff --git a/libs/ui/src/atoms/index.ts b/libs/ui/src/atoms/index.ts index 55d2082..07e967b 100644 --- a/libs/ui/src/atoms/index.ts +++ b/libs/ui/src/atoms/index.ts @@ -1,2 +1,3 @@ export * from './button'; export * from './input'; +export * from './password-input'; diff --git a/libs/ui/src/atoms/password-input/index.ts b/libs/ui/src/atoms/password-input/index.ts new file mode 100644 index 0000000..aa25c3b --- /dev/null +++ b/libs/ui/src/atoms/password-input/index.ts @@ -0,0 +1 @@ +export * from "./password-input"; \ No newline at end of file diff --git a/libs/ui/src/atoms/password-input/password-input.stories.tsx b/libs/ui/src/atoms/password-input/password-input.stories.tsx new file mode 100644 index 0000000..d2db711 --- /dev/null +++ b/libs/ui/src/atoms/password-input/password-input.stories.tsx @@ -0,0 +1,58 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { PasswordInput } from './password-input'; + +const meta: Meta = { + title: 'Components/Password Input', + component: PasswordInput, + argTypes: { + size: { + control: 'select', + options: ['sm', 'md', 'lg'], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Large: Story = { + args: { + size: 'lg', + placeholder: 'Placeholder', + }, +}; + +export const Medium: Story = { + args: { + size: 'md', + placeholder: 'Placeholder', + }, +}; + +export const Small: Story = { + args: { + size: 'sm', + placeholder: 'Placeholder', + }, +}; + +export const Input: Story = { + args: { + size: 'md', + placeholder: 'Placeholder', + }, +}; + +export const Disabled: Story = { + args: { + size: 'md', + disabled: true, + }, +}; + +export const WithError: Story = { + args: { + size: 'md', + error: 'This field is required', + }, +}; diff --git a/libs/ui/src/atoms/password-input/password-input.tsx b/libs/ui/src/atoms/password-input/password-input.tsx new file mode 100644 index 0000000..85372fa --- /dev/null +++ b/libs/ui/src/atoms/password-input/password-input.tsx @@ -0,0 +1,111 @@ +import { + DetailedHTMLProps, + FC, + InputHTMLAttributes, + ReactElement, + useState, +} from 'react'; +import { cn } from '@imphnen-frontend-service/utils'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; + +type TPasswordInputType = 'password'; +type TPasswordInputSize = 'sm' | 'md' | 'lg'; + +type TPasswordInputProps = Omit< + DetailedHTMLProps, HTMLInputElement>, + 'size' | 'type' +> & { + type?: TPasswordInputType; + size?: TPasswordInputSize; + error?: string; +}; + +const sizeClasses: Record = { + sm: 'text-[10px] max-h-[28px]', + md: 'text-[12px] max-h-[30px]', + lg: 'text-[15px] max-h-[34px]', +}; + +const iconSizeClasses: Record = { + sm: 'text-[10px]', + md: 'text-[12px]', + lg: 'text-[16px]', +}; + +const disabledClass = 'opacity-50 hover:border-neutral-200 cursor-not-allowed'; +const errorClass = + 'border-danger-500 hover:border-danger-500 focus:outline-danger-500'; + +export const PasswordInput: FC = ({ + size = 'md', + placeholder = 'Placeholder', + disabled, + error, + className, + ...rest +}): ReactElement => { + const [showPassword, setShowPassword] = useState(false); + + const togglePasswordVisibility = () => { + if (!disabled) setShowPassword((prev) => !prev); + }; + + const mergedClassName = cn( + 'px-[12px] py-[8px] text-neutral-800 placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree', + sizeClasses[size], + disabled && disabledClass, + error && errorClass, + className + ); + + return ( + <> +
+ +
+ +
+
+ {error &&

{error}

} + + ); +};