Files
web/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx
Rasmus Langvad b9a62b5280 Merged in feat/use-new-input-component (pull request #3324)
feat(SW-3659): Use new input component

* Use new input component

* Update error formatter

* Merged master into feat/use-new-input-component

* Merged master into feat/use-new-input-component

* Merge branch 'master' into feat/use-new-input-component

* Merged master into feat/use-new-input-component

* Update Input stories

* Merge branch 'feat/use-new-input-component' of bitbucket.org:scandic-swap/web into feat/use-new-input-component

* Update Storybook logo

* Add some new demo icon input story

* Fix the clear content button position

* Fix broken password input icon

* Merged master into feat/use-new-input-component

* Merged master into feat/use-new-input-component

* Add aria-hidden to required asterisk

* Merge branch 'feat/use-new-input-component' of bitbucket.org:scandic-swap/web into feat/use-new-input-component

* Merge branch 'master' into feat/use-new-input-component


Approved-by: Bianca Widstam
Approved-by: Matilda Landström
2025-12-18 15:42:09 +00:00

164 lines
5.0 KiB
TypeScript

"use client"
import { useState } from "react"
import { TextField } from "react-aria-components"
import {
Controller,
type RegisterOptions,
useFormContext,
} from "react-hook-form"
import { useIntl } from "react-intl"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { Input } from "@scandic-hotels/design-system/Input"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { getErrorMessage } from "@/utils/getErrorMessage"
import { NewPasswordValidation } from "./NewPasswordValidation"
import styles from "./passwordInput.module.css"
interface PasswordInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string
registerOptions?: RegisterOptions
visibilityToggleable?: boolean
isNewPassword?: boolean
}
export default function PasswordInput({
name = "password",
label,
"aria-label": ariaLabel,
disabled = false,
placeholder,
registerOptions = {},
visibilityToggleable = true,
isNewPassword = false,
className = "",
}: PasswordInputProps) {
const { control } = useFormContext()
const intl = useIntl()
const [isPasswordVisible, setIsPasswordVisible] = useState(false)
return (
<Controller
disabled={disabled}
control={control}
name={name}
rules={registerOptions}
render={({ field, fieldState, formState }) => {
const errors = isNewPassword
? Object.values(formState.errors[name]?.types ?? []).flat()
: []
return (
<TextField
className={className}
aria-label={ariaLabel}
aria-invalid={!!fieldState.error}
aria-describedby="password-error password-requirements"
isDisabled={field.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions.required}
name={field.name}
onBlur={field.onBlur}
onChange={field.onChange}
validationBehavior="aria"
value={field.value}
type={
visibilityToggleable && isPasswordVisible ? "text" : "password"
}
>
<div className={styles.inputWrapper}>
<Input
{...field}
aria-labelledby={field.name}
id={field.name}
label={
label ||
(isNewPassword
? intl.formatMessage({
id: "passwordInput.newPasswordLabel",
defaultMessage: "New password",
})
: intl.formatMessage({
id: "common.password",
defaultMessage: "Password",
}))
}
placeholder={placeholder}
type={
visibilityToggleable && isPasswordVisible
? "text"
: "password"
}
/>
{visibilityToggleable ? (
<IconButton
variant="Muted"
emphasis
onPress={() => setIsPasswordVisible((value) => !value)}
aria-label={
isPasswordVisible
? intl.formatMessage({
id: "passwordInput.hidePassword",
defaultMessage: "Hide password",
})
: intl.formatMessage({
id: "passwordInput.showPassword",
defaultMessage: "Show password",
})
}
aria-controls={field.name}
className={styles.toggleButton}
>
<MaterialIcon
icon={isPasswordVisible ? "visibility_off" : "visibility"}
size={24}
/>
</IconButton>
) : null}
</div>
{isNewPassword ? (
<NewPasswordValidation value={field.value} errors={errors} />
) : null}
{isNewPassword ? (
!field.value && fieldState.error ? (
<ErrorMessage errorMessage={fieldState.error.message} />
) : null
) : fieldState.error ? (
<ErrorMessage errorMessage={fieldState.error.message} />
) : null}
</TextField>
)
}}
/>
)
}
function ErrorMessage({ errorMessage }: { errorMessage?: string }) {
const intl = useIntl()
return (
<Typography
variant="Body/Supporting text (caption)/smRegular"
className={styles.error}
>
<p role="alert" id="password-error">
<MaterialIcon
icon="info"
color="Icon/Feedback/Error"
aria-label={intl.formatMessage({
id: "common.error",
defaultMessage: "Error",
})}
/>
{getErrorMessage(intl, errorMessage)}
</p>
</Typography>
)
}