Merged in feat/new-passwordinput-component (pull request #3376)
feat(SW-3672): Update PasswordInput component * Update PasswordInput component * Removed some tests not working as expected * Remove IconButton from PasswordInput * Remove IconButton from Input * Merge branch 'master' into feat/new-passwordinput-component Approved-by: Linus Flood
This commit is contained in:
@@ -0,0 +1,281 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
|
||||
import { fn } from 'storybook/test'
|
||||
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 { Button } from '../Button'
|
||||
import { Typography } from '../Typography'
|
||||
import { passwordValidator } from '@scandic-hotels/common/utils/zod/passwordValidator'
|
||||
|
||||
// Simple error formatter for Storybook
|
||||
const defaultErrorFormatter = (
|
||||
_intl: IntlShape,
|
||||
errorMessage?: string
|
||||
): string => errorMessage ?? ''
|
||||
|
||||
// ============================================================================
|
||||
// Password Form with New Password Validation
|
||||
// ============================================================================
|
||||
|
||||
const passwordFormSchema = z
|
||||
.object({
|
||||
currentPassword: z.string().optional(),
|
||||
newPassword: z.literal('').optional().or(passwordValidator()),
|
||||
confirmPassword: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.newPassword && data.newPassword !== data.confirmPassword) {
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
message: 'Passwords do not match',
|
||||
path: ['confirmPassword'],
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
type PasswordFormData = z.infer<typeof passwordFormSchema>
|
||||
|
||||
interface PasswordInputProps {
|
||||
onSubmit?: (data: PasswordFormData) => void
|
||||
showErrors?: boolean
|
||||
defaultNewPassword?: string
|
||||
}
|
||||
|
||||
function PasswordInputComponent({
|
||||
onSubmit,
|
||||
showErrors = false,
|
||||
defaultNewPassword = '',
|
||||
}: PasswordInputProps) {
|
||||
const methods = useForm<PasswordFormData>({
|
||||
resolver: zodResolver(passwordFormSchema),
|
||||
defaultValues: {
|
||||
currentPassword: '',
|
||||
newPassword: defaultNewPassword,
|
||||
confirmPassword: '',
|
||||
},
|
||||
mode: 'all',
|
||||
criteriaMode: 'all',
|
||||
reValidateMode: 'onChange',
|
||||
})
|
||||
|
||||
// Trigger validation on mount if showErrors is true
|
||||
useEffect(() => {
|
||||
if (showErrors) {
|
||||
methods.trigger()
|
||||
}
|
||||
}, [showErrors, methods])
|
||||
|
||||
const handleSubmit = methods.handleSubmit((data) => {
|
||||
onSubmit?.(data)
|
||||
})
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '1.5rem',
|
||||
maxWidth: '500px',
|
||||
}}
|
||||
>
|
||||
<Typography variant="Title/md">
|
||||
<h2>Change Password</h2>
|
||||
</Typography>
|
||||
|
||||
<PasswordInput
|
||||
name="currentPassword"
|
||||
label="Current password"
|
||||
errorFormatter={defaultErrorFormatter}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
name="newPassword"
|
||||
isNewPassword
|
||||
errorFormatter={defaultErrorFormatter}
|
||||
/>
|
||||
|
||||
<PasswordInput
|
||||
name="confirmPassword"
|
||||
label="Confirm new password"
|
||||
errorFormatter={defaultErrorFormatter}
|
||||
/>
|
||||
|
||||
<Button type="submit" variant="Primary" size="lg">
|
||||
Update password
|
||||
</Button>
|
||||
</form>
|
||||
</FormProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const passwordFormMeta: Meta<typeof PasswordInputComponent> = {
|
||||
title: 'Core Components/PasswordInput',
|
||||
component: PasswordInputComponent,
|
||||
parameters: {
|
||||
layout: 'padded',
|
||||
},
|
||||
argTypes: {
|
||||
showErrors: {
|
||||
control: 'boolean',
|
||||
description: 'Show validation errors on mount',
|
||||
},
|
||||
defaultNewPassword: {
|
||||
control: 'text',
|
||||
description: 'Default value for new password field',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default passwordFormMeta
|
||||
|
||||
type PasswordFormStory = StoryObj<typeof PasswordInputComponent>
|
||||
|
||||
export const Default: PasswordFormStory = {
|
||||
render: (args) => <PasswordInputComponent {...args} />,
|
||||
args: {
|
||||
onSubmit: fn(),
|
||||
showErrors: false,
|
||||
defaultNewPassword: '',
|
||||
},
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 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/md">
|
||||
<h2>New 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>Weak Password (too short)</p>
|
||||
</Typography>
|
||||
<PasswordInput
|
||||
name="weak"
|
||||
isNewPassword
|
||||
errorFormatter={defaultErrorFormatter}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p>Partial Password (missing requirements)</p>
|
||||
</Typography>
|
||||
<PasswordInput
|
||||
name="partial"
|
||||
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 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,
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user