Penambahan Fitur Login Form Component dan Stories
73
front_end_portserver/src/components/Login/assets/LoginBg.svg
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 722 B After Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 446 B After Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 425 B |
@@ -0,0 +1,9 @@
|
|||||||
|
import loginBg from '@/components/Login/assets/LoginBg.svg'
|
||||||
|
|
||||||
|
export const Background = (
|
||||||
|
) => {
|
||||||
|
|
||||||
|
return(
|
||||||
|
<img src={loginBg} className='absolute w-full h-full no-repeat bg-cover' ></img>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Button , buttonVariants } from "@/components/ui/button";
|
||||||
|
|
||||||
|
interface ButtonProps {
|
||||||
|
variant ?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
||||||
|
size ?: 'default' | 'sm' | 'lg' | 'icon' ;
|
||||||
|
label ?: string;
|
||||||
|
styling?: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Buttons = ( {...props}:ButtonProps )=> {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
className={
|
||||||
|
(buttonVariants({
|
||||||
|
variant: props.variant,
|
||||||
|
size: props.size
|
||||||
|
}),
|
||||||
|
props.styling
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
import mail from "@/components/Login/assets/mail.svg"
|
||||||
|
import lock from "@/components/Login/assets/lock-closed.svg"
|
||||||
|
import eyeOff from "@/components/Login/assets/eye-off.svg"
|
||||||
|
import eyeOn from "@/components/Login/assets/eye-on.svg"
|
||||||
|
|
||||||
|
export interface InputWithLabelProps {
|
||||||
|
labelValue ?: string;
|
||||||
|
// typeName ?: string;
|
||||||
|
placeHolderName ?: string;
|
||||||
|
togglePasswordVisibility ?:boolean;
|
||||||
|
type ?: 'password' | 'email';
|
||||||
|
onClick ?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputWithLabel = ({
|
||||||
|
...props
|
||||||
|
}:InputWithLabelProps) => {
|
||||||
|
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
|
const togglePasswordVisibility = () => {
|
||||||
|
setShowPassword(!showPassword);
|
||||||
|
};
|
||||||
|
|
||||||
|
const srcIcon = props.type?.toLowerCase() === 'email' ? mail : props.type?.toLowerCase() === 'password' ? lock : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className= "flex flex-col items-start gap-4">
|
||||||
|
<Label > { props.labelValue } </Label>
|
||||||
|
<div className= "relative">
|
||||||
|
<img src= { srcIcon } className="w-10% absolute top-0 left-0 py-2.5 pl-2" />
|
||||||
|
{(props.type === 'password') && (
|
||||||
|
<img
|
||||||
|
src={props.togglePasswordVisibility ? eyeOn : eyeOff}
|
||||||
|
className="w-10% absolute py-2.5 right-0 pr-2 "
|
||||||
|
onClick={togglePasswordVisibility}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
// type= { props.typeName }
|
||||||
|
placeholder= { props.placeHolderName }
|
||||||
|
className={`w-full px-8 `}
|
||||||
|
type={props.type?.toLowerCase() === 'password' && showPassword ? 'text' : props.type}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
interface LinkBtnProps {
|
||||||
|
value ?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LinkBtn = ({
|
||||||
|
...props
|
||||||
|
}:LinkBtnProps ) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-end gap-8">
|
||||||
|
<a className="text-black font-inter text-sm font-normal underline">{props.value}</a>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Background } from "./Background"
|
||||||
|
import { LogoSismedika } from "./Logo"
|
||||||
|
import { EmailInput, PasswordInput } from "../stories/Input.stories"
|
||||||
|
import { ForgetPasswordLink } from "../stories/Link.stories"
|
||||||
|
import { LoginTitle } from "../stories/Title.stories"
|
||||||
|
import { LoginButton } from "../stories/Button.stories"
|
||||||
|
|
||||||
|
export const LoginForm = (
|
||||||
|
) => {
|
||||||
|
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<div className="relative w-full h-full">
|
||||||
|
<div className="object-cover"><Background /></div>
|
||||||
|
<div className="flex justify-center item-center object-contain">
|
||||||
|
<div className="inline-grid scale-75 gap-5">
|
||||||
|
<div className="flex justify-center items-center pb-4">
|
||||||
|
<LogoSismedika />
|
||||||
|
</div>
|
||||||
|
<LoginTitle />
|
||||||
|
<EmailInput />
|
||||||
|
<div className="gap-5">
|
||||||
|
<PasswordInput />
|
||||||
|
<ForgetPasswordLink />
|
||||||
|
</div>
|
||||||
|
<LoginButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
import logoSismedika from '@/components/Login/assets/LogoSismedika.svg'
|
||||||
|
|
||||||
|
export const LogoSismedika = (
|
||||||
|
) => {
|
||||||
|
|
||||||
|
return(
|
||||||
|
<img src={logoSismedika} ></img>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
|
||||||
|
|
||||||
|
interface TitleProps {
|
||||||
|
|
||||||
|
value : string;
|
||||||
|
value2 ?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Title = ({
|
||||||
|
...props
|
||||||
|
}: TitleProps ) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col justify-center items-center gap-1">
|
||||||
|
<Label className="text-primary-text text-3xl font-inter font-bold">{props.value}</Label>
|
||||||
|
<Label className="text-secondary-text text-xs font-inter font-normal text-[#637381]">{props.value2}</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Background } from "../loginComponents/Background";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Component/Login/LoginBackground'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const LoginBg= () => {
|
||||||
|
return(
|
||||||
|
<Background/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { Buttons } from '../loginComponents/Button';
|
||||||
|
|
||||||
|
// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||||
|
const meta = {
|
||||||
|
title: 'Component/Login/Button',
|
||||||
|
component: Buttons,
|
||||||
|
parameters: {
|
||||||
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs
|
||||||
|
tags: ['autodocs'],
|
||||||
|
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||||
|
args : {
|
||||||
|
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Buttons>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
// More on writing stories with args: https://storybook.js.org/docs/react/writing-stories/args
|
||||||
|
|
||||||
|
export const LoginButton = (args:Story) => {
|
||||||
|
return(
|
||||||
|
<Buttons
|
||||||
|
variant='default'
|
||||||
|
size='lg'
|
||||||
|
label='Log In'
|
||||||
|
styling='flex w-full p-3.5 justify-center items-center gap-2.5 rounded-md border border-black bg-[#F15A29]'
|
||||||
|
|
||||||
|
{...args}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { InputWithLabel } from "../loginComponents/Inputs";
|
||||||
|
import { boolean } from "zod";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Component/Login/Input",
|
||||||
|
component: InputWithLabel,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
|
||||||
|
parameters: {
|
||||||
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
labelValue: "Label Name",
|
||||||
|
placeHolderName : "Place Holder Name",
|
||||||
|
},
|
||||||
|
|
||||||
|
argTypes: {
|
||||||
|
type: {
|
||||||
|
options: ['email', 'password'],
|
||||||
|
control: { type: 'radio' },
|
||||||
|
},
|
||||||
|
togglePasswordVisibility : {action : boolean},
|
||||||
|
onClick: { action: 'clicked' }
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} satisfies Meta<typeof InputWithLabel>
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
// export const ArgumentEmailInput: Story = {
|
||||||
|
// args: {
|
||||||
|
// labelValue: "Email",
|
||||||
|
// typeName: "Email",
|
||||||
|
// placeHolderName: "Input your Email",
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export const ArgumentPasswordInput: Story = {
|
||||||
|
// args:{
|
||||||
|
// labelValue: "Password",
|
||||||
|
// typeName: "Password",
|
||||||
|
// placeHolderName: "Input your Password",
|
||||||
|
// togglePasswordVisibility: false,
|
||||||
|
// },
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const EmailInput = (args:Story) => {
|
||||||
|
return(
|
||||||
|
<InputWithLabel
|
||||||
|
labelValue="Email"
|
||||||
|
type="email"
|
||||||
|
placeHolderName="Input your Email"
|
||||||
|
|
||||||
|
{...args}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordInput = (args:Story) => {
|
||||||
|
return(
|
||||||
|
<InputWithLabel
|
||||||
|
labelValue="Password"
|
||||||
|
type="password"
|
||||||
|
placeHolderName="Input your Password"
|
||||||
|
{...args}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { LinkBtn } from "../loginComponents/LinkButton";
|
||||||
|
|
||||||
|
const meta= {
|
||||||
|
title: 'Component/Login/LinkButton',
|
||||||
|
component: LinkBtn,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
parameters: {
|
||||||
|
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/react/configure/story-layout
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
args:{
|
||||||
|
value : "Forget password",
|
||||||
|
}
|
||||||
|
|
||||||
|
}satisfies Meta<typeof LinkBtn>
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const ForgetPasswordLink = (args:Story) => {
|
||||||
|
return(
|
||||||
|
<LinkBtn value="Forget Password" {...args} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { LoginForm } from "../loginComponents/LoginForm";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Component/Login/Forms',
|
||||||
|
tags: ['autodocs'],
|
||||||
|
component: LoginForm,
|
||||||
|
|
||||||
|
args:{
|
||||||
|
value: "value",
|
||||||
|
value2: "value2"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const LoginForms = () => {
|
||||||
|
return(
|
||||||
|
<LoginForm/>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import {Title} from "../loginComponents/Title";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Component/Login/Title',
|
||||||
|
component: Title,
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
|
||||||
|
argTypes:{
|
||||||
|
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
value: 'value1',
|
||||||
|
value2: 'value2',
|
||||||
|
},
|
||||||
|
}satisfies Meta<typeof Title>
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const LoginTitle = (args:Story) => {
|
||||||
|
return(
|
||||||
|
<Title value={"Port Server"} value2="Welcome back! Enter your account details" {...args} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||