diff --git a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/NewPasswordValidation.tsx b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/NewPasswordValidation.tsx new file mode 100644 index 000000000..781cd59cf --- /dev/null +++ b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/NewPasswordValidation.tsx @@ -0,0 +1,119 @@ +import { useIntl } from "react-intl" + +import { passwordValidators } from "@scandic-hotels/common/utils/zod/passwordValidator" +import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon" +import { Typography } from "@scandic-hotels/design-system/Typography" + +import styles from "./passwordInput.module.css" + +import type { PasswordValidatorKey } from "@/types/components/form/newPassword" + +export function NewPasswordValidation({ + value, + errors, +}: { + value: string + errors: string[] +}) { + const intl = useIntl() + + if (!value) return null + + function getErrorMessage(key: PasswordValidatorKey) { + switch (key) { + case "length": + return intl.formatMessage( + { + id: "passwordInput.lengthRequirement", + defaultMessage: "{min} to {max} characters", + }, + { + min: 10, + max: 40, + } + ) + case "hasUppercase": + return intl.formatMessage( + { + id: "passwordInput.uppercaseRequirement", + defaultMessage: "{count} uppercase letter", + }, + { count: 1 } + ) + case "hasLowercase": + return intl.formatMessage( + { + id: "passwordInput.lowercaseRequirement", + defaultMessage: "{count} lowercase letter", + }, + { count: 1 } + ) + case "hasNumber": + return intl.formatMessage( + { + id: "passwordInput.numberRequirement", + defaultMessage: "{count} number", + }, + { count: 1 } + ) + case "hasSpecialChar": + return intl.formatMessage( + { + id: "passwordInput.specialCharacterRequirement", + defaultMessage: "{count} special character", + }, + { count: 1 } + ) + case "allowedCharacters": + return intl.formatMessage({ + id: "passwordInput.allowedCharactersRequirement", + defaultMessage: "Only allowed characters", + }) + } + } + + return ( +
+ {Object.entries(passwordValidators).map(([key, { message }]) => ( + + + + {getErrorMessage(key as PasswordValidatorKey)} + + + ))} +
+ ) +} + +interface IconProps { + errorMessage: string + errors: string[] +} + +function Icon({ errorMessage, errors }: IconProps) { + const intl = useIntl() + return errors.includes(errorMessage) ? ( + + ) : ( + + ) +} diff --git a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx index 5c8453d1c..9e442e910 100644 --- a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx +++ b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/index.tsx @@ -1,22 +1,32 @@ "use client" import { useState } from "react" -import { Text, TextField } from "react-aria-components" -import { Controller, useFormContext } from "react-hook-form" +import { TextField } from "react-aria-components" +import { + Controller, + type RegisterOptions, + useFormContext, +} from "react-hook-form" import { useIntl } from "react-intl" -import { passwordValidators } from "@scandic-hotels/common/utils/zod/passwordValidator" -import Caption from "@scandic-hotels/design-system/Caption" +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 { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton" +import { Typography } from "@scandic-hotels/design-system/Typography" import { getErrorMessage } from "@/utils/getErrorMessage" +import { NewPasswordValidation } from "./NewPasswordValidation" + import styles from "./passwordInput.module.css" -import type { PasswordValidatorKey } from "@/types/components/form/newPassword" -import type { IconProps, PasswordInputProps } from "./passwordInput" +interface PasswordInputProps + extends React.InputHTMLAttributes { + label?: string + registerOptions?: RegisterOptions + visibilityToggleable?: boolean + isNewPassword?: boolean +} export default function PasswordInput({ name = "password", @@ -48,6 +58,8 @@ export default function PasswordInput({ {visibilityToggleable ? ( - + + ) : null} @@ -120,16 +128,10 @@ export default function PasswordInput({ {isNewPassword ? ( !field.value && fieldState.error ? ( - - - {getErrorMessage(intl, fieldState.error.message)} - + ) : null ) : fieldState.error ? ( - - - {getErrorMessage(intl, fieldState.error.message)} - + ) : null} ) @@ -138,88 +140,24 @@ export default function PasswordInput({ ) } -function Icon({ errorMessage, errors }: IconProps) { - return errors.includes(errorMessage) ? ( - - ) : ( - - ) -} - -function NewPasswordValidation({ - value, - errors, -}: { - value: string - errors: string[] -}) { +function ErrorMessage({ errorMessage }: { errorMessage?: string }) { const intl = useIntl() - - if (!value) return null - - function getErrorMessage(key: PasswordValidatorKey) { - switch (key) { - case "length": - return intl.formatMessage( - { - id: "passwordInput.lengthRequirement", - defaultMessage: "{min} to {max} characters", - }, - { - min: 10, - max: 40, - } - ) - case "hasUppercase": - return intl.formatMessage( - { - id: "passwordInput.uppercaseRequirement", - defaultMessage: "{count} uppercase letter", - }, - { count: 1 } - ) - case "hasLowercase": - return intl.formatMessage( - { - id: "passwordInput.lowercaseRequirement", - defaultMessage: "{count} lowercase letter", - }, - { count: 1 } - ) - case "hasNumber": - return intl.formatMessage( - { - id: "passwordInput.numberRequirement", - defaultMessage: "{count} number", - }, - { count: 1 } - ) - case "hasSpecialChar": - return intl.formatMessage( - { - id: "passwordInput.specialCharacterRequirement", - defaultMessage: "{count} special character", - }, - { count: 1 } - ) - case "allowedCharacters": - return intl.formatMessage({ - id: "passwordInput.allowedCharactersRequirement", - defaultMessage: "Only allowed characters", - }) - } - } - return ( -
- {Object.entries(passwordValidators).map(([key, { message }]) => ( - - - - {getErrorMessage(key as PasswordValidatorKey)} - - - ))} -
+ + + ) } diff --git a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/passwordInput.ts b/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/passwordInput.ts deleted file mode 100644 index 21c08a51e..000000000 --- a/apps/scandic-web/components/TempDesignSystem/Form/PasswordInput/passwordInput.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { RegisterOptions } from "react-hook-form" - -export interface PasswordInputProps - extends React.InputHTMLAttributes { - label?: string - registerOptions?: RegisterOptions - visibilityToggleable?: boolean - isNewPassword?: boolean -} - -export interface IconProps { - errorMessage: string - errors: string[] -} diff --git a/packages/design-system/lib/components/Icons/MaterialIcon/MaterialIcon.tsx b/packages/design-system/lib/components/Icons/MaterialIcon/MaterialIcon.tsx index e9ad0465c..c4570f263 100644 --- a/packages/design-system/lib/components/Icons/MaterialIcon/MaterialIcon.tsx +++ b/packages/design-system/lib/components/Icons/MaterialIcon/MaterialIcon.tsx @@ -3,9 +3,11 @@ import { MaterialSymbol, type MaterialSymbolProps } from './MaterialSymbol' import { iconVariants } from '../variants' import type { VariantProps } from 'class-variance-authority' +import { HTMLAttributes } from 'react' export interface MaterialIconProps extends Pick, + Omit, 'color' | 'id'>, VariantProps { isFilled?: boolean } @@ -18,6 +20,12 @@ export function MaterialIcon({ ...props }: MaterialIconProps) { const classNames = iconVariants({ className, color }) + const { role, 'aria-label': ariaLabel, 'aria-hidden': ariaHidden } = props + + // Automatically decide whether to hide from assistive tech + const computedAriaHidden = + ariaHidden !== undefined ? ariaHidden : ariaLabel || role ? false : true + return ( // The span is used to prevent the MaterialSymbol from being underlined when used inside a link or button @@ -25,8 +33,9 @@ export function MaterialIcon({ className={classNames} data-testid="MaterialIcon" size={size} - {...props} fill={isFilled} + aria-hidden={computedAriaHidden} + {...props} /> )