fix(SW-3693): Refactor Input stories to use FormInput component and showcase all controls * Refactor Input stories to use FormInput component and showcase all controls * Updated stories and added autocomplete prop to PasswordInput * Merge branch 'master' into fix/3693-group-inputs-storybook * Use FormTextArea in stories for TextArea to show description and error texts * Merged master into fix/3693-group-inputs-storybook * Merge branch 'master' into fix/3693-group-inputs-storybook * Removed redundant font name and fixed broken icons in stories * Merge branch 'fix/3693-group-inputs-storybook' of bitbucket.org:scandic-swap/web into fix/3693-group-inputs-storybook * Merged master into fix/3693-group-inputs-storybook * Merge branch 'master' into fix/3693-group-inputs-storybook Approved-by: Bianca Widstam
369 lines
9.8 KiB
TypeScript
369 lines
9.8 KiB
TypeScript
import type { Meta, StoryObj } from "@storybook/nextjs-vite"
|
|
import { useEffect } from "react"
|
|
import { FormProvider, useForm } from "react-hook-form"
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { z } from "zod"
|
|
import type { IntlShape } from "react-intl"
|
|
|
|
import { PasswordInput } from "./index"
|
|
import { Typography } from "../Typography"
|
|
import type { MaterialIconProps } from "../Icons/MaterialIcon"
|
|
import { passwordValidator } from "@scandic-hotels/common/utils/zod/passwordValidator"
|
|
|
|
// Simple error formatter for Storybook
|
|
const defaultErrorFormatter = (
|
|
_intl: IntlShape,
|
|
errorMessage?: string
|
|
): string => errorMessage ?? ""
|
|
|
|
interface PasswordInputStoryProps {
|
|
label?: string
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
visibilityToggleable?: boolean
|
|
isNewPassword?: boolean
|
|
required?: boolean
|
|
errorMessage?: string
|
|
defaultValue?: string
|
|
description?: string
|
|
descriptionIcon?: MaterialIconProps["icon"]
|
|
autoComplete?: string
|
|
}
|
|
|
|
function PasswordInputComponent({
|
|
label,
|
|
placeholder,
|
|
disabled = false,
|
|
visibilityToggleable = true,
|
|
isNewPassword = false,
|
|
required = false,
|
|
errorMessage,
|
|
defaultValue = "",
|
|
description,
|
|
descriptionIcon = "info",
|
|
autoComplete,
|
|
}: PasswordInputStoryProps) {
|
|
const schema = z.object({
|
|
password: errorMessage
|
|
? isNewPassword
|
|
? z.literal("").optional().or(passwordValidator())
|
|
: z.string().min(1, errorMessage)
|
|
: required
|
|
? isNewPassword
|
|
? passwordValidator()
|
|
: z.string().min(1, "This field is required")
|
|
: isNewPassword
|
|
? z.literal("").optional().or(passwordValidator())
|
|
: z.string().optional(),
|
|
})
|
|
|
|
const methods = useForm<{ password: string }>({
|
|
resolver: zodResolver(schema),
|
|
defaultValues: {
|
|
password: defaultValue,
|
|
},
|
|
mode: isNewPassword ? "all" : "onChange",
|
|
criteriaMode: isNewPassword ? "all" : undefined,
|
|
reValidateMode: isNewPassword ? "onChange" : undefined,
|
|
})
|
|
|
|
useEffect(() => {
|
|
if (errorMessage && !isNewPassword) {
|
|
methods.setError("password", {
|
|
type: "manual",
|
|
message: errorMessage,
|
|
})
|
|
methods.trigger("password")
|
|
} else if (isNewPassword && defaultValue) {
|
|
methods.trigger("password")
|
|
} else {
|
|
methods.clearErrors("password")
|
|
}
|
|
}, [errorMessage, methods, isNewPassword, defaultValue])
|
|
|
|
return (
|
|
<FormProvider {...methods}>
|
|
<div style={{ padding: "1rem" }}>
|
|
<PasswordInput
|
|
name="password"
|
|
label={label}
|
|
placeholder={placeholder}
|
|
disabled={disabled}
|
|
visibilityToggleable={visibilityToggleable}
|
|
isNewPassword={isNewPassword}
|
|
registerOptions={required ? { required: true } : undefined}
|
|
errorFormatter={defaultErrorFormatter}
|
|
description={description}
|
|
descriptionIcon={descriptionIcon}
|
|
autoComplete={autoComplete}
|
|
/>
|
|
</div>
|
|
</FormProvider>
|
|
)
|
|
}
|
|
|
|
const meta: Meta<typeof PasswordInputComponent> = {
|
|
title: "Core Components/Input/PasswordInput",
|
|
component: PasswordInputComponent,
|
|
argTypes: {
|
|
label: {
|
|
control: "text",
|
|
name: "Label",
|
|
description: "Label text",
|
|
table: {
|
|
type: { summary: "string" },
|
|
order: 1,
|
|
},
|
|
},
|
|
placeholder: {
|
|
control: "text",
|
|
name: "Placeholder",
|
|
description: "Placeholder text",
|
|
table: {
|
|
type: { summary: "string" },
|
|
defaultValue: { summary: "undefined" },
|
|
order: 2,
|
|
},
|
|
},
|
|
disabled: {
|
|
control: "boolean",
|
|
name: "Disabled",
|
|
description: "Disabled state",
|
|
table: {
|
|
type: { summary: "boolean" },
|
|
defaultValue: { summary: "false" },
|
|
order: 3,
|
|
},
|
|
},
|
|
visibilityToggleable: {
|
|
control: "boolean",
|
|
name: "Visibility Toggleable",
|
|
description: "Show/hide password toggle button",
|
|
table: {
|
|
type: { summary: "boolean" },
|
|
defaultValue: { summary: "true" },
|
|
order: 4,
|
|
},
|
|
},
|
|
isNewPassword: {
|
|
control: "boolean",
|
|
name: "Is New Password",
|
|
description: "Enable new password validation",
|
|
table: {
|
|
type: { summary: "boolean" },
|
|
defaultValue: { summary: "false" },
|
|
order: 5,
|
|
},
|
|
},
|
|
required: {
|
|
control: "boolean",
|
|
name: "Required",
|
|
description: "Required field",
|
|
table: {
|
|
type: { summary: "boolean" },
|
|
defaultValue: { summary: "false" },
|
|
order: 6,
|
|
},
|
|
},
|
|
errorMessage: {
|
|
control: "text",
|
|
name: "Error Message",
|
|
description: "Error message (only for non-new-password fields)",
|
|
table: {
|
|
type: { summary: "string" },
|
|
defaultValue: { summary: "undefined" },
|
|
order: 7,
|
|
},
|
|
},
|
|
defaultValue: {
|
|
control: "text",
|
|
name: "Default Value",
|
|
description: "Default password value",
|
|
table: {
|
|
type: { summary: "string" },
|
|
defaultValue: { summary: "''" },
|
|
order: 8,
|
|
},
|
|
},
|
|
description: {
|
|
control: "text",
|
|
name: "Description",
|
|
description: "Helper text (hidden when error is shown)",
|
|
table: {
|
|
type: { summary: "string" },
|
|
defaultValue: { summary: "undefined" },
|
|
order: 9,
|
|
},
|
|
},
|
|
descriptionIcon: {
|
|
control: "select",
|
|
name: "Description Icon",
|
|
options: ["info", "warning", "error"],
|
|
description: "Description icon",
|
|
table: {
|
|
type: { summary: "string" },
|
|
defaultValue: { summary: "'info'" },
|
|
order: 10,
|
|
},
|
|
},
|
|
autoComplete: {
|
|
control: "select",
|
|
name: "Autocomplete",
|
|
description:
|
|
"HTML autocomplete attribute value. If not provided, automatically sets to 'new-password' when isNewPassword is true, or 'current-password' when isNewPassword is false.",
|
|
options: [undefined, "current-password", "new-password", "off"],
|
|
table: {
|
|
type: { summary: "string" },
|
|
defaultValue: {
|
|
summary: "undefined (auto-set based on isNewPassword)",
|
|
},
|
|
order: 11,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
export default meta
|
|
|
|
type Story = StoryObj<typeof PasswordInputComponent>
|
|
|
|
export const Default: Story = {
|
|
args: {
|
|
label: undefined,
|
|
placeholder: undefined,
|
|
disabled: false,
|
|
visibilityToggleable: true,
|
|
isNewPassword: false,
|
|
required: false,
|
|
errorMessage: undefined,
|
|
defaultValue: "",
|
|
description: undefined,
|
|
descriptionIcon: "info",
|
|
autoComplete: undefined,
|
|
},
|
|
}
|
|
|
|
// ============================================================================
|
|
// Password Validation Showcase
|
|
// ============================================================================
|
|
|
|
const showcasePasswordSchema = z.object({
|
|
empty: z.literal("").optional().or(passwordValidator()),
|
|
weak: passwordValidator(),
|
|
partial: passwordValidator(),
|
|
valid: passwordValidator(),
|
|
tooLong: passwordValidator(),
|
|
})
|
|
|
|
type ShowcasePasswordFormData = z.infer<typeof showcasePasswordSchema>
|
|
|
|
function PasswordValidationShowcase() {
|
|
const methods = useForm<ShowcasePasswordFormData>({
|
|
resolver: zodResolver(showcasePasswordSchema),
|
|
defaultValues: {
|
|
empty: "",
|
|
weak: "weak",
|
|
partial: "Password1",
|
|
valid: "ValidPassword123!",
|
|
tooLong: "A".repeat(41) + "1!",
|
|
},
|
|
mode: "all",
|
|
criteriaMode: "all",
|
|
reValidateMode: "onChange",
|
|
})
|
|
|
|
// Trigger validation on mount to show validation states
|
|
useEffect(() => {
|
|
methods.trigger(["weak", "partial", "valid", "tooLong"])
|
|
}, [methods])
|
|
|
|
return (
|
|
<FormProvider {...methods}>
|
|
<div
|
|
style={{
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: "3rem",
|
|
maxWidth: "800px",
|
|
padding: "2rem",
|
|
}}
|
|
>
|
|
<section>
|
|
<Typography variant="Title/sm">
|
|
<h2>Password Validation States</h2>
|
|
</Typography>
|
|
<div
|
|
style={{
|
|
display: "grid",
|
|
gridTemplateColumns: "repeat(auto-fit, minmax(350px, 1fr))",
|
|
gap: "2rem",
|
|
marginTop: "1rem",
|
|
}}
|
|
>
|
|
<div>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>Empty Password</p>
|
|
</Typography>
|
|
<PasswordInput
|
|
name="empty"
|
|
isNewPassword
|
|
errorFormatter={defaultErrorFormatter}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>Valid Password</p>
|
|
</Typography>
|
|
<PasswordInput
|
|
name="valid"
|
|
isNewPassword
|
|
errorFormatter={defaultErrorFormatter}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>Too Short Password</p>
|
|
</Typography>
|
|
<PasswordInput
|
|
name="weak"
|
|
isNewPassword
|
|
errorFormatter={defaultErrorFormatter}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Typography variant="Body/Paragraph/mdRegular">
|
|
<p>Too Long Password</p>
|
|
</Typography>
|
|
<PasswordInput
|
|
name="tooLong"
|
|
isNewPassword
|
|
errorFormatter={defaultErrorFormatter}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</FormProvider>
|
|
)
|
|
}
|
|
|
|
const showcaseMeta: Meta<typeof PasswordValidationShowcase> = {
|
|
title: "Core Components/PasswordInput",
|
|
component: PasswordValidationShowcase,
|
|
parameters: {
|
|
layout: "fullscreen",
|
|
},
|
|
}
|
|
|
|
type ShowcaseStory = StoryObj<typeof PasswordValidationShowcase>
|
|
|
|
export const AllValidationStates: ShowcaseStory = {
|
|
render: () => <PasswordValidationShowcase />,
|
|
parameters: {
|
|
...showcaseMeta.parameters,
|
|
},
|
|
}
|