From 90ab974e034e400044d2e0467715b4233075016d Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 27 Mar 2025 02:42:57 +0700 Subject: [PATCH 01/30] feat: backoffice login page --- apps/backoffice/public/logos/logo.svg | 9 ++++++++ apps/backoffice/src/app/layout.tsx | 2 -- apps/backoffice/src/app/login/page.tsx | 30 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 apps/backoffice/public/logos/logo.svg create mode 100644 apps/backoffice/src/app/login/page.tsx diff --git a/apps/backoffice/public/logos/logo.svg b/apps/backoffice/public/logos/logo.svg new file mode 100644 index 0000000..ca0f36e --- /dev/null +++ b/apps/backoffice/public/logos/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/apps/backoffice/src/app/layout.tsx b/apps/backoffice/src/app/layout.tsx index 7428943..2d8a74d 100644 --- a/apps/backoffice/src/app/layout.tsx +++ b/apps/backoffice/src/app/layout.tsx @@ -1,11 +1,9 @@ import { FC, ReactElement } from 'react'; import { Outlet } from 'react-router-dom'; -import { Navbar } from '@imphnen-frontend-service/ui/organisms'; export const AppLayout: FC = (): ReactElement => { return (
-
); diff --git a/apps/backoffice/src/app/login/page.tsx b/apps/backoffice/src/app/login/page.tsx new file mode 100644 index 0000000..a78a20c --- /dev/null +++ b/apps/backoffice/src/app/login/page.tsx @@ -0,0 +1,30 @@ +import { FC, ReactElement } from 'react'; +import { + Input, + Button, + PasswordInput, +} from '@imphnen-frontend-service/ui/atoms'; + +export const Components: FC = (): ReactElement => { + return ( +
+
+ +

+ Welcome to IMPHNEN Backoffice +

+
+
Email
+ +
+
+
Password
+ +
+ +
+
+ ); +}; + +export default Components; From ca933f624abf362d235b8d15a0a7803ab8209283 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 27 Mar 2025 04:35:41 +0700 Subject: [PATCH 02/30] feat: Create a separate "InputForm" molecule component - Develop the "InputForm" molecule component - Remove empty modal-gacha component stories and test files that prevent Storybook from running --- apps/backoffice/src/app/login/page.tsx | 17 +++---- libs/ui/src/molecules/index.ts | 2 +- libs/ui/src/molecules/input-form/index.ts | 1 + .../input-form/input-form.stories.tsx | 51 +++++++++++++++++++ .../src/molecules/input-form/input-form.tsx | 44 ++++++++++++++++ .../modals-gacha/modals-gacha.spec.tsx | 0 .../modals-gacha/modals-gacha.stories.tsx | 0 7 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 libs/ui/src/molecules/input-form/index.ts create mode 100644 libs/ui/src/molecules/input-form/input-form.stories.tsx create mode 100644 libs/ui/src/molecules/input-form/input-form.tsx delete mode 100644 libs/ui/src/organisms/modals-gacha/modals-gacha.spec.tsx delete mode 100644 libs/ui/src/organisms/modals-gacha/modals-gacha.stories.tsx diff --git a/apps/backoffice/src/app/login/page.tsx b/apps/backoffice/src/app/login/page.tsx index a78a20c..cbdc657 100644 --- a/apps/backoffice/src/app/login/page.tsx +++ b/apps/backoffice/src/app/login/page.tsx @@ -1,9 +1,6 @@ import { FC, ReactElement } from 'react'; -import { - Input, - Button, - PasswordInput, -} from '@imphnen-frontend-service/ui/atoms'; +import { Button, PasswordInput } from '@imphnen-frontend-service/ui/atoms'; +import { InputForm } from '@imphnen-frontend-service/ui/molecules'; export const Components: FC = (): ReactElement => { return ( @@ -13,10 +10,12 @@ export const Components: FC = (): ReactElement => {

Welcome to IMPHNEN Backoffice

-
-
Email
- -
+
Password
diff --git a/libs/ui/src/molecules/index.ts b/libs/ui/src/molecules/index.ts index cb0ff5c..14dcfcb 100644 --- a/libs/ui/src/molecules/index.ts +++ b/libs/ui/src/molecules/index.ts @@ -1 +1 @@ -export {}; +export * from './input-form'; diff --git a/libs/ui/src/molecules/input-form/index.ts b/libs/ui/src/molecules/input-form/index.ts new file mode 100644 index 0000000..14dcfcb --- /dev/null +++ b/libs/ui/src/molecules/input-form/index.ts @@ -0,0 +1 @@ +export * from './input-form'; diff --git a/libs/ui/src/molecules/input-form/input-form.stories.tsx b/libs/ui/src/molecules/input-form/input-form.stories.tsx new file mode 100644 index 0000000..e74538c --- /dev/null +++ b/libs/ui/src/molecules/input-form/input-form.stories.tsx @@ -0,0 +1,51 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { InputForm } from './input-form'; + +const meta = { + title: 'Molecules/InputForm', + component: InputForm, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'Email', + placeholder: 'Enter your email', + type: 'email', + size: 'md', + }, +}; + +export const WithError: Story = { + args: { + label: 'Username', + placeholder: 'Enter your username', + type: 'text', + size: 'md', + error: 'This field is required', + }, +}; + +export const Large: Story = { + args: { + label: 'Full Name', + placeholder: 'Enter your full name', + type: 'text', + size: 'lg', + }, +}; + +export const Small: Story = { + args: { + label: 'Phone', + placeholder: 'Enter your phone number', + type: 'text', + size: 'sm', + }, +}; diff --git a/libs/ui/src/molecules/input-form/input-form.tsx b/libs/ui/src/molecules/input-form/input-form.tsx new file mode 100644 index 0000000..26d080e --- /dev/null +++ b/libs/ui/src/molecules/input-form/input-form.tsx @@ -0,0 +1,44 @@ +import { + DetailedHTMLProps, + FC, + InputHTMLAttributes, + ReactElement, +} from 'react'; +import { Input } from '../../atoms'; + +type TInputType = 'text' | 'email'; +type TInputSize = 'sm' | 'md' | 'lg'; + +type TInputFormProps = Omit< + DetailedHTMLProps, HTMLInputElement>, + 'size' | 'type' +> & { + label: string; + type?: TInputType; + size?: TInputSize; + error?: string; +}; + +export const InputForm: FC = ({ + label, + placeholder, + type = 'text', + size = 'md', + error, + ...rest +}): ReactElement => { + return ( +
+
{label}
+ +
+ ); +}; + +export default InputForm; diff --git a/libs/ui/src/organisms/modals-gacha/modals-gacha.spec.tsx b/libs/ui/src/organisms/modals-gacha/modals-gacha.spec.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/libs/ui/src/organisms/modals-gacha/modals-gacha.stories.tsx b/libs/ui/src/organisms/modals-gacha/modals-gacha.stories.tsx deleted file mode 100644 index e69de29..0000000 From 54f4572533ef5e256e98a7a12cd3027f9e5cef89 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 27 Mar 2025 05:25:02 +0700 Subject: [PATCH 03/30] feat: Update InputForm component - Perbarui docs untuk component Input dan InputForm - Perbarui component InputForm agar bekerja dengan helper text, error, dan label/input --- libs/ui/src/atoms/input/input.stories.tsx | 8 -- libs/ui/src/atoms/input/input.tsx | 24 ++---- .../input-form/input-form.stories.tsx | 80 ++++++++++++++++--- .../src/molecules/input-form/input-form.tsx | 24 +++++- 4 files changed, 99 insertions(+), 37 deletions(-) diff --git a/libs/ui/src/atoms/input/input.stories.tsx b/libs/ui/src/atoms/input/input.stories.tsx index 7bea8f5..43ba621 100644 --- a/libs/ui/src/atoms/input/input.stories.tsx +++ b/libs/ui/src/atoms/input/input.stories.tsx @@ -61,11 +61,3 @@ export const Disabled: Story = { disabled: true, }, }; - -export const WithError: Story = { - args: { - size: 'md', - type: 'text', - error: 'This field is required', - }, -}; diff --git a/libs/ui/src/atoms/input/input.tsx b/libs/ui/src/atoms/input/input.tsx index 3a80d49..da288f5 100644 --- a/libs/ui/src/atoms/input/input.tsx +++ b/libs/ui/src/atoms/input/input.tsx @@ -15,7 +15,6 @@ type TInputProps = Omit< > & { type?: TInputType; size?: TInputSize; - error?: string; }; const sizeClasses: Record = { @@ -25,15 +24,12 @@ const sizeClasses: Record = { }; 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 Input: FC = ({ size = 'md', - type, + type = 'text', placeholder = 'Placeholder', disabled, - error, className, ...rest }): ReactElement => { @@ -41,20 +37,16 @@ export const Input: FC = ({ '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}

} - + ); }; diff --git a/libs/ui/src/molecules/input-form/input-form.stories.tsx b/libs/ui/src/molecules/input-form/input-form.stories.tsx index e74538c..53dd42c 100644 --- a/libs/ui/src/molecules/input-form/input-form.stories.tsx +++ b/libs/ui/src/molecules/input-form/input-form.stories.tsx @@ -6,6 +6,25 @@ const meta = { component: InputForm, parameters: { layout: 'centered', + docs: { + description: { + component: ` +Komponen input form yang menggabungkan label dengan kolom input. + +## Aksesibilitas +Ketika prop \`htmlFor\` disediakan: +- Atribut \`htmlFor\` pada label akan diatur ke nilai tersebut +- Atribut \`id\` pada input akan otomatis diatur ke nilai yang sama +- Ini menciptakan asosiasi label-input yang tepat untuk aksesibilitas + +Cek dan inspect element pada story With HtmlFor untuk melihat hasilnya. + +## Helper Text dan Error +- Jika prop \`error\` disediakan, akan ditampilkan dalam warna merah di bawah input +- Jika prop \`helperText\` disediakan dan tidak ada error, akan ditampilkan dalam warna abu-abu di bawah input + `, + }, + }, }, tags: ['autodocs'], } satisfies Meta; @@ -13,6 +32,33 @@ const meta = { export default meta; type Story = StoryObj; +export const Large: Story = { + args: { + label: 'Description', + placeholder: 'Enter description', + type: 'text', + size: 'lg', + }, +}; + +export const Medium: Story = { + args: { + label: 'Address', + placeholder: 'Enter your address', + type: 'text', + size: 'md', + }, +}; + +export const Small: Story = { + args: { + label: 'Phone', + placeholder: 'Enter your phone number', + type: 'text', + size: 'sm', + }, +}; + export const Default: Story = { args: { label: 'Email', @@ -22,6 +68,25 @@ export const Default: Story = { }, }; +export const WithHelperText: Story = { + args: { + label: 'Email', + placeholder: 'Enter your email', + type: 'email', + size: 'md', + helperText: 'We will never share your email', + }, +}; + +export const WithoutHelperText: Story = { + args: { + label: 'Password', + placeholder: 'Enter your password', + type: 'text', + size: 'md', + }, +}; + export const WithError: Story = { args: { label: 'Username', @@ -32,20 +97,13 @@ export const WithError: Story = { }, }; -export const Large: Story = { +export const WithHtmlFor: Story = { args: { label: 'Full Name', placeholder: 'Enter your full name', type: 'text', - size: 'lg', - }, -}; - -export const Small: Story = { - args: { - label: 'Phone', - placeholder: 'Enter your phone number', - type: 'text', - size: 'sm', + size: 'md', + htmlFor: 'fullname-input', + helperText: 'Enter your legal full name', }, }; diff --git a/libs/ui/src/molecules/input-form/input-form.tsx b/libs/ui/src/molecules/input-form/input-form.tsx index 26d080e..1aa625c 100644 --- a/libs/ui/src/molecules/input-form/input-form.tsx +++ b/libs/ui/src/molecules/input-form/input-form.tsx @@ -5,6 +5,7 @@ import { ReactElement, } from 'react'; import { Input } from '../../atoms'; +import { cn } from '@imphnen-frontend-service/utils'; type TInputType = 'text' | 'email'; type TInputSize = 'sm' | 'md' | 'lg'; @@ -17,6 +18,8 @@ type TInputFormProps = Omit< type?: TInputType; size?: TInputSize; error?: string; + helperText?: string; + htmlFor?: string; }; export const InputForm: FC = ({ @@ -25,18 +28,35 @@ export const InputForm: FC = ({ type = 'text', size = 'md', error, + helperText, + htmlFor, + className, ...rest }): ReactElement => { return (
-
{label}
+ + {error ? ( +

{error}

+ ) : ( + helperText && ( +

{helperText}

+ ) + )}
); }; From 631684cb05347b3e646e6ba6de3866b519aeb9f2 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 27 Mar 2025 06:00:03 +0700 Subject: [PATCH 04/30] feat: Update InputForm to handle different sizes - Tambah spec test pada komponen InputForm - Tambah autodocs pada komponen Input --- libs/ui/src/atoms/input/input.stories.tsx | 3 +- .../molecules/input-form/input-form.spec.tsx | 20 +++++++++++++ .../src/molecules/input-form/input-form.tsx | 28 +++++++++++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 libs/ui/src/molecules/input-form/input-form.spec.tsx diff --git a/libs/ui/src/atoms/input/input.stories.tsx b/libs/ui/src/atoms/input/input.stories.tsx index 43ba621..1e2b442 100644 --- a/libs/ui/src/atoms/input/input.stories.tsx +++ b/libs/ui/src/atoms/input/input.stories.tsx @@ -14,7 +14,8 @@ const meta: Meta = { options: ['sm', 'md', 'lg'], }, }, -}; + tags: ['autodocs'], +} satisfies Meta; export default meta; type Story = StoryObj; diff --git a/libs/ui/src/molecules/input-form/input-form.spec.tsx b/libs/ui/src/molecules/input-form/input-form.spec.tsx new file mode 100644 index 0000000..f0dc650 --- /dev/null +++ b/libs/ui/src/molecules/input-form/input-form.spec.tsx @@ -0,0 +1,20 @@ +import { render, screen } from '@testing-library/react'; +import InputForm from './input-form'; + +describe('InputForm Component', () => { + it('renders correctly with disabled prop', () => { + render(); + + const input = screen.getByLabelText('Test Label'); + expect(input).toBeDisabled(); + expect(input).toHaveClass('opacity-50 cursor-not-allowed'); + }); + + it('renders correctly without disabled prop', () => { + render(); + + const input = screen.getByLabelText('Test Label'); + expect(input).not.toBeDisabled(); + expect(input).not.toHaveClass('opacity-50 cursor-not-allowed'); + }); +}); diff --git a/libs/ui/src/molecules/input-form/input-form.tsx b/libs/ui/src/molecules/input-form/input-form.tsx index 1aa625c..f7078cd 100644 --- a/libs/ui/src/molecules/input-form/input-form.tsx +++ b/libs/ui/src/molecules/input-form/input-form.tsx @@ -18,10 +18,27 @@ type TInputFormProps = Omit< type?: TInputType; size?: TInputSize; error?: string; + disabled?: boolean; + helperText?: string; htmlFor?: string; }; +const sizeClasses: Record = { + lg: { + label: 'text-p3 font-medium', + helperText: 'text-label2 font-normal', + }, + md: { + label: 'text-label1 font-medium', + helperText: 'text-label2 font-normal', + }, + sm: { + label: 'text-label2 font-medium', + helperText: 'text-label3 font-normal', + }, +}; + export const InputForm: FC = ({ label, placeholder, @@ -31,11 +48,12 @@ export const InputForm: FC = ({ helperText, htmlFor, className, + disabled, ...rest }): ReactElement => { return (
-
From 542469bc672ad1398b112b087e759e79520922c9 Mon Sep 17 00:00:00 2001 From: Hafid Nur Date: Thu, 27 Mar 2025 07:19:46 +0700 Subject: [PATCH 05/30] feat: Update InputForm, Input, and auth page - Perbaiki page login ke halaman utama backoffice - Perbaiki component Input dan InputForm --- apps/backoffice/src/app/login/page.tsx | 29 ----- apps/backoffice/src/app/page.tsx | 26 +++- libs/ui/src/atoms/index.ts | 1 - libs/ui/src/atoms/input/input.spec.tsx | 35 ++---- libs/ui/src/atoms/input/input.stories.tsx | 20 ++-- libs/ui/src/atoms/input/input.tsx | 67 ++++++++--- libs/ui/src/atoms/password-input/index.ts | 1 - .../password-input/password-input.stories.tsx | 58 --------- .../atoms/password-input/password-input.tsx | 111 ------------------ .../input-form/input-form.stories.tsx | 46 ++++++-- .../src/molecules/input-form/input-form.tsx | 16 ++- 11 files changed, 138 insertions(+), 272 deletions(-) delete mode 100644 apps/backoffice/src/app/login/page.tsx delete mode 100644 libs/ui/src/atoms/password-input/index.ts delete mode 100644 libs/ui/src/atoms/password-input/password-input.stories.tsx delete mode 100644 libs/ui/src/atoms/password-input/password-input.tsx diff --git a/apps/backoffice/src/app/login/page.tsx b/apps/backoffice/src/app/login/page.tsx deleted file mode 100644 index cbdc657..0000000 --- a/apps/backoffice/src/app/login/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { FC, ReactElement } from 'react'; -import { Button, PasswordInput } from '@imphnen-frontend-service/ui/atoms'; -import { InputForm } from '@imphnen-frontend-service/ui/molecules'; - -export const Components: FC = (): ReactElement => { - return ( -
-
- -

- Welcome to IMPHNEN Backoffice -

- -
-
Password
- -
- -
-
- ); -}; - -export default Components; diff --git a/apps/backoffice/src/app/page.tsx b/apps/backoffice/src/app/page.tsx index f135208..1cc55a0 100644 --- a/apps/backoffice/src/app/page.tsx +++ b/apps/backoffice/src/app/page.tsx @@ -1,7 +1,31 @@ import { FC, ReactElement } from 'react'; +import { Button } from '@imphnen-frontend-service/ui/atoms'; +import { InputForm } from '@imphnen-frontend-service/ui/molecules'; export const Components: FC = (): ReactElement => { - return <>Hallo; + return ( +
+
+ +

+ Welcome to IMPHNEN Backoffice +

+ + + +
+
+ ); }; export default Components; diff --git a/libs/ui/src/atoms/index.ts b/libs/ui/src/atoms/index.ts index c107e65..dc8c232 100644 --- a/libs/ui/src/atoms/index.ts +++ b/libs/ui/src/atoms/index.ts @@ -1,4 +1,3 @@ export * from './button'; export * from './input'; -export * from './password-input'; export * from './textarea'; diff --git a/libs/ui/src/atoms/input/input.spec.tsx b/libs/ui/src/atoms/input/input.spec.tsx index 4fdf16b..a0d57cc 100644 --- a/libs/ui/src/atoms/input/input.spec.tsx +++ b/libs/ui/src/atoms/input/input.spec.tsx @@ -1,36 +1,15 @@ -import { describe, it, expect } from "vitest"; -import { render, screen } from "@testing-library/react"; -import { Input } from "./input"; +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { Input } from './input'; -describe("Test Input Component", () => { - it("renders the input with placeholder text", () => { +describe('Test Input Component', () => { + it('renders the input with placeholder text', () => { render(); - expect(screen.getByPlaceholderText("Placeholder")).toBeInTheDocument(); + expect(screen.getByPlaceholderText('Placeholder')).toBeInTheDocument(); }); - // it("renders the input with different sizes", () => { - // const sizeClasses = { - // sm: "text-[10px] max-h-[28px]", - // md: "text-[12px] max-h-[30px]", - // lg: "text-[15px] max-h-[34px]", - // }; - // Object.entries(sizeClasses).forEach(([size, className]) => { - // render(); - // expect(screen.getByRole("textbox")).toHaveClass(className); - // }); - // }); - - // it("renders the input with different types", () => { - // const types = ["text", "password", "email", "number"]; - // types.forEach((type) => { - // render(); - // const input = screen.getByRole("textbox"); - // expect(input).toHaveAttribute("type", type); - // }); - // }); - it("disables the input field when 'disabled' prop is set", () => { render(); - expect(screen.getByRole("textbox")).toBeDisabled(); + expect(screen.getByRole('textbox')).toBeDisabled(); }); }); diff --git a/libs/ui/src/atoms/input/input.stories.tsx b/libs/ui/src/atoms/input/input.stories.tsx index 1e2b442..371aaa7 100644 --- a/libs/ui/src/atoms/input/input.stories.tsx +++ b/libs/ui/src/atoms/input/input.stories.tsx @@ -4,16 +4,6 @@ import { Input } from './input'; const meta: Meta = { title: 'Components/Input', component: Input, - argTypes: { - type: { - control: 'select', - options: ['text', 'email'], - }, - size: { - control: 'select', - options: ['sm', 'md', 'lg'], - }, - }, tags: ['autodocs'], } satisfies Meta; @@ -23,6 +13,7 @@ type Story = StoryObj; export const Large: Story = { args: { size: 'lg', + type: 'text', placeholder: 'Placeholder', }, }; @@ -30,6 +21,7 @@ export const Large: Story = { export const Medium: Story = { args: { size: 'md', + type: 'text', placeholder: 'Placeholder', }, }; @@ -37,6 +29,7 @@ export const Medium: Story = { export const Small: Story = { args: { size: 'sm', + type: 'text', placeholder: 'Placeholder', }, }; @@ -55,6 +48,13 @@ export const EmailInput: Story = { }, }; +export const PasswordInput: Story = { + args: { + size: 'md', + type: 'password', + }, +}; + export const Disabled: Story = { args: { size: 'md', diff --git a/libs/ui/src/atoms/input/input.tsx b/libs/ui/src/atoms/input/input.tsx index da288f5..69006b8 100644 --- a/libs/ui/src/atoms/input/input.tsx +++ b/libs/ui/src/atoms/input/input.tsx @@ -3,10 +3,13 @@ import { FC, InputHTMLAttributes, ReactElement, + useState, } from 'react'; +import { EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons'; // Import Ant Design icons import { cn } from '@imphnen-frontend-service/utils'; +import { Button } from '../button'; -type TInputType = 'text' | 'email'; +type TInputType = 'text' | 'email' | 'password'; type TInputSize = 'sm' | 'md' | 'lg'; type TInputProps = Omit< @@ -15,38 +18,70 @@ type TInputProps = Omit< > & { type?: TInputType; size?: TInputSize; + disabled?: boolean; }; -const sizeClasses: Record = { - sm: 'text-[10px] max-h-[28px]', - md: 'text-[12px] max-h-[30px]', - lg: 'text-[15px] max-h-[34px]', -}; +const sizeClasses: Record = + { + sm: { textSize: 'text-[10px] max-h-[28px]', iconSize: 'text-[10px]' }, + md: { textSize: 'text-[12px] max-h-[30px]', iconSize: 'text-[12px]' }, + lg: { textSize: 'text-[15px] max-h-[34px]', iconSize: 'text-[15px]' }, + }; const disabledClass = 'opacity-50 hover:border-neutral-200 cursor-not-allowed'; export const Input: FC = ({ - size = 'md', type = 'text', + size = 'md', placeholder = 'Placeholder', disabled, className, ...rest }): ReactElement => { + const [showPassword, setShowPassword] = useState(false); // State for password visibility + + 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], + '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 w-full', + sizeClasses[size].textSize, disabled && disabledClass, className ); return ( - +
+ + {type === 'password' && ( +
+ +
+ )} +
); }; diff --git a/libs/ui/src/atoms/password-input/index.ts b/libs/ui/src/atoms/password-input/index.ts deleted file mode 100644 index aa25c3b..0000000 --- a/libs/ui/src/atoms/password-input/index.ts +++ /dev/null @@ -1 +0,0 @@ -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 deleted file mode 100644 index d2db711..0000000 --- a/libs/ui/src/atoms/password-input/password-input.stories.tsx +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index 85372fa..0000000 --- a/libs/ui/src/atoms/password-input/password-input.tsx +++ /dev/null @@ -1,111 +0,0 @@ -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}

} - - ); -}; diff --git a/libs/ui/src/molecules/input-form/input-form.stories.tsx b/libs/ui/src/molecules/input-form/input-form.stories.tsx index 53dd42c..024805a 100644 --- a/libs/ui/src/molecules/input-form/input-form.stories.tsx +++ b/libs/ui/src/molecules/input-form/input-form.stories.tsx @@ -34,36 +34,48 @@ type Story = StoryObj; export const Large: Story = { args: { - label: 'Description', - placeholder: 'Enter description', + label: 'Label', + placeholder: 'Placeholder', type: 'text', size: 'lg', + helperText: 'Helper Text', }, }; export const Medium: Story = { args: { - label: 'Address', - placeholder: 'Enter your address', + label: 'Label', + placeholder: 'Placeholder', type: 'text', size: 'md', + helperText: 'Helper Text', }, }; export const Small: Story = { args: { - label: 'Phone', - placeholder: 'Enter your phone number', + label: 'Label', + placeholder: 'Placeholder', type: 'text', size: 'sm', + helperText: 'Helper Text', }, }; export const Default: Story = { args: { - label: 'Email', - placeholder: 'Enter your email', - type: 'email', + label: 'Default', + placeholder: 'Enter your name', + type: 'text', + size: 'md', + }, +}; + +export const PasswordInput: Story = { + args: { + label: 'Password', + placeholder: 'Enter your password', + type: 'password', size: 'md', }, }; @@ -80,8 +92,8 @@ export const WithHelperText: Story = { export const WithoutHelperText: Story = { args: { - label: 'Password', - placeholder: 'Enter your password', + label: 'Username', + placeholder: 'Enter your username', type: 'text', size: 'md', }, @@ -104,6 +116,16 @@ export const WithHtmlFor: Story = { type: 'text', size: 'md', htmlFor: 'fullname-input', - helperText: 'Enter your legal full name', + helperText: 'Click on the label', + }, +}; + +export const Disabled: Story = { + args: { + label: 'Disabled', + placeholder: 'This field is disabled', + type: 'text', + size: 'md', + disabled: true, }, }; diff --git a/libs/ui/src/molecules/input-form/input-form.tsx b/libs/ui/src/molecules/input-form/input-form.tsx index f7078cd..1af658d 100644 --- a/libs/ui/src/molecules/input-form/input-form.tsx +++ b/libs/ui/src/molecules/input-form/input-form.tsx @@ -7,7 +7,7 @@ import { import { Input } from '../../atoms'; import { cn } from '@imphnen-frontend-service/utils'; -type TInputType = 'text' | 'email'; +type TInputType = 'text' | 'email' | 'password'; type TInputSize = 'sm' | 'md' | 'lg'; type TInputFormProps = Omit< @@ -27,7 +27,7 @@ type TInputFormProps = Omit< const sizeClasses: Record = { lg: { label: 'text-p3 font-medium', - helperText: 'text-label2 font-normal', + helperText: 'text-label3 font-normal', }, md: { label: 'text-label1 font-medium', @@ -35,7 +35,7 @@ const sizeClasses: Record = { }, sm: { label: 'text-label2 font-medium', - helperText: 'text-label3 font-normal', + helperText: 'text-label2 font-normal', }, }; @@ -53,7 +53,13 @@ export const InputForm: FC = ({ }): ReactElement => { return (
-