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
This commit is contained in:
@@ -15,9 +15,9 @@ import {
|
||||
import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import Image from "@scandic-hotels/design-system/Image"
|
||||
import { Input } from "@scandic-hotels/design-system/Input"
|
||||
import Modal from "@scandic-hotels/design-system/Modal"
|
||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
@@ -87,7 +87,8 @@ export function TransferPointsFormClient({
|
||||
</I18nProvider>
|
||||
<div className={styles.inputsWrapper}>
|
||||
<TextField type="number" isDisabled={disabled}>
|
||||
<Input
|
||||
<FormInput
|
||||
name="ebPointsToExchange"
|
||||
label={intl.formatMessage({
|
||||
id: "partnerSas.ebPointsToExchange",
|
||||
defaultMessage: "EB points to exchange",
|
||||
|
||||
@@ -6,17 +6,20 @@ import { getDefaultCountryFromLang } from "@scandic-hotels/common/utils/phone"
|
||||
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
|
||||
import DateSelect from "@scandic-hotels/design-system/Form/Date"
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
import Phone from "@scandic-hotels/design-system/Form/Phone"
|
||||
import { FormSelect } from "@scandic-hotels/design-system/Form/Select"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { getLocalizedLanguageOptions } from "@/constants/languages"
|
||||
|
||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getFormattedCountryList } from "@/utils/countries"
|
||||
import { getErrorMessage } from "@/utils/getErrorMessage"
|
||||
import {
|
||||
formatFormErrorMessage,
|
||||
getErrorMessage,
|
||||
} from "@/utils/getErrorMessage"
|
||||
|
||||
import styles from "./formContent.module.css"
|
||||
|
||||
@@ -63,7 +66,7 @@ export default function FormContent({ errors }: { errors: FieldErrors }) {
|
||||
name="dateOfBirth"
|
||||
registerOptions={{ required: true }}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
data-hj-suppress
|
||||
label={`${intl.formatMessage({
|
||||
id: "common.address",
|
||||
@@ -71,7 +74,7 @@ export default function FormContent({ errors }: { errors: FieldErrors }) {
|
||||
})} 1`}
|
||||
name="address.streetAddress"
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
data-hj-suppress
|
||||
label={intl.formatMessage({
|
||||
id: "common.city",
|
||||
@@ -80,12 +83,13 @@ export default function FormContent({ errors }: { errors: FieldErrors }) {
|
||||
name="address.city"
|
||||
/>
|
||||
<div className={styles.container}>
|
||||
<Input
|
||||
<FormInput
|
||||
data-hj-suppress
|
||||
label={intl.formatMessage({
|
||||
id: "common.zipCode",
|
||||
defaultMessage: "Zip code",
|
||||
})}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
name="address.zipCode"
|
||||
registerOptions={{ required: true }}
|
||||
/>
|
||||
@@ -105,7 +109,7 @@ export default function FormContent({ errors }: { errors: FieldErrors }) {
|
||||
registerOptions={{ required: true }}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.emailAddress",
|
||||
defaultMessage: "Email address",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
|
||||
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
|
||||
import DateSelect from "@scandic-hotels/design-system/Form/Date"
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
import Phone from "@scandic-hotels/design-system/Form/Phone"
|
||||
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||
import { TextLinkButton } from "@scandic-hotels/design-system/TextLinkButton"
|
||||
@@ -29,11 +30,13 @@ import {
|
||||
} from "@scandic-hotels/trpc/routers/user/schemas"
|
||||
|
||||
import ProfilingConsentModalReadOnly from "@/components/MyPages/ProfilingConsent/Modal/ReadOnly"
|
||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||
import PasswordInput from "@/components/TempDesignSystem/Form/PasswordInput"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getFormattedCountryList } from "@/utils/countries"
|
||||
import { getErrorMessage } from "@/utils/getErrorMessage"
|
||||
import {
|
||||
formatFormErrorMessage,
|
||||
getErrorMessage,
|
||||
} from "@/utils/getErrorMessage"
|
||||
import { requestOpen } from "@/utils/profilingConsent"
|
||||
import { trackLinkClick } from "@/utils/tracking/profilingConsent"
|
||||
|
||||
@@ -162,7 +165,8 @@ export default function SignupForm({
|
||||
</Typography>
|
||||
</header>
|
||||
<div className={styles.nameInputs}>
|
||||
<Input
|
||||
<FormInput
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
label={intl.formatMessage({
|
||||
id: "common.firstName",
|
||||
defaultMessage: "First name",
|
||||
@@ -170,7 +174,8 @@ export default function SignupForm({
|
||||
name="firstName"
|
||||
registerOptions={{ required: true }}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
label={intl.formatMessage({
|
||||
id: "common.lastName",
|
||||
defaultMessage: "Last name",
|
||||
@@ -214,7 +219,8 @@ export default function SignupForm({
|
||||
/>
|
||||
</div>
|
||||
<div className={cx(styles.container, styles.additional)}>
|
||||
<Input
|
||||
<FormInput
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
label={intl.formatMessage({
|
||||
id: "common.zipCode",
|
||||
defaultMessage: "Zip code",
|
||||
@@ -236,7 +242,8 @@ export default function SignupForm({
|
||||
name="address.countryCode"
|
||||
registerOptions={{ required: true }}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
label={intl.formatMessage({
|
||||
id: "common.emailAddress",
|
||||
defaultMessage: "Email address",
|
||||
|
||||
@@ -6,8 +6,9 @@ import { FormProvider, useForm } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
|
||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||
import { formatFormErrorMessage } from "@/utils/getErrorMessage"
|
||||
|
||||
import {
|
||||
type AdditionalInfoFormSchema,
|
||||
@@ -53,15 +54,16 @@ export default function AdditionalInfoForm({
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className={styles.form}>
|
||||
<Title isAdditional />
|
||||
<div className={styles.inputs}>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.firstName",
|
||||
defaultMessage: "First name",
|
||||
})}
|
||||
name="firstName"
|
||||
registerOptions={{ required: true }}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.email",
|
||||
defaultMessage: "Email",
|
||||
@@ -69,6 +71,7 @@ export default function AdditionalInfoForm({
|
||||
name="email"
|
||||
type="email"
|
||||
registerOptions={{ required: true }}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
|
||||
@@ -11,13 +11,14 @@ import { myStay } from "@scandic-hotels/common/constants/routes/myStay"
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
import { Alert } from "@scandic-hotels/design-system/Alert"
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
import { TextLink } from "@scandic-hotels/design-system/TextLink"
|
||||
import { toast } from "@scandic-hotels/design-system/Toast"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { trpc } from "@scandic-hotels/trpc/client"
|
||||
|
||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { formatFormErrorMessage } from "@/utils/getErrorMessage"
|
||||
|
||||
import { type FindMyBookingFormSchema, findMyBookingFormSchema } from "./schema"
|
||||
import { Title } from "./Title"
|
||||
@@ -95,31 +96,34 @@ export default function FindMyBooking({
|
||||
/>
|
||||
) : null}
|
||||
<div className={[styles.inputs, styles.grid].join(" ")}>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.bookingNumber",
|
||||
defaultMessage: "Booking number",
|
||||
})}
|
||||
name="confirmationNumber"
|
||||
registerOptions={{ required: true }}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.firstName",
|
||||
defaultMessage: "First name",
|
||||
})}
|
||||
name="firstName"
|
||||
registerOptions={{ required: true }}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.lastName",
|
||||
defaultMessage: "Last name",
|
||||
})}
|
||||
name="lastName"
|
||||
registerOptions={{ required: true }}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.email",
|
||||
defaultMessage: "Email",
|
||||
@@ -127,6 +131,7 @@ export default function FindMyBooking({
|
||||
name="email"
|
||||
type="email"
|
||||
registerOptions={{ required: true }}
|
||||
errorFormatter={formatFormErrorMessage}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttons}>
|
||||
|
||||
@@ -7,10 +7,10 @@ import {
|
||||
getDefaultCountryFromLang,
|
||||
} from "@scandic-hotels/common/utils/phone"
|
||||
import CountrySelect from "@scandic-hotels/design-system/Form/Country"
|
||||
import { FormInput } from "@scandic-hotels/design-system/Form/FormInput"
|
||||
import Phone from "@scandic-hotels/design-system/Form/Phone"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import Input from "@/components/TempDesignSystem/Form/Input"
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { getFormattedCountryList } from "@/utils/countries"
|
||||
import { getErrorMessage } from "@/utils/getErrorMessage"
|
||||
@@ -45,7 +45,7 @@ export default function ModifyContact({
|
||||
{isFirstStep ? (
|
||||
<div className={styles.container}>
|
||||
<div className={`${styles.row} ${styles.gridEqual}`}>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.firstName",
|
||||
defaultMessage: "First name",
|
||||
@@ -54,7 +54,7 @@ export default function ModifyContact({
|
||||
name="firstName"
|
||||
disabled={!!guest.firstName}
|
||||
/>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.lastName",
|
||||
defaultMessage: "Last name",
|
||||
@@ -80,7 +80,7 @@ export default function ModifyContact({
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.row}>
|
||||
<Input
|
||||
<FormInput
|
||||
label={intl.formatMessage({
|
||||
id: "common.email",
|
||||
defaultMessage: "Email",
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { forwardRef, type HTMLAttributes, type WheelEvent } from "react"
|
||||
import { Text, TextField } from "react-aria-components"
|
||||
import { Controller, useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Caption from "@scandic-hotels/design-system/Caption"
|
||||
import { ErrorMessage } from "@scandic-hotels/design-system/Form/ErrorMessage"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Input as InputWithLabel } from "@scandic-hotels/design-system/Input"
|
||||
|
||||
import { getErrorMessage } from "@/utils/getErrorMessage"
|
||||
|
||||
import styles from "./input.module.css"
|
||||
|
||||
import type { InputProps } from "./input"
|
||||
|
||||
const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
|
||||
{
|
||||
"aria-label": ariaLabel,
|
||||
autoComplete,
|
||||
className = "",
|
||||
disabled = false,
|
||||
helpText = "",
|
||||
label,
|
||||
maxLength,
|
||||
name,
|
||||
placeholder,
|
||||
readOnly = false,
|
||||
registerOptions = {},
|
||||
type = "text",
|
||||
hideError,
|
||||
inputMode,
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const intl = useIntl()
|
||||
const { control } = useFormContext()
|
||||
const numberAttributes: HTMLAttributes<HTMLInputElement> = {}
|
||||
if (type === "number") {
|
||||
numberAttributes.onWheel = function (evt: WheelEvent<HTMLInputElement>) {
|
||||
evt.currentTarget.blur()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Controller
|
||||
disabled={disabled}
|
||||
control={control}
|
||||
name={name}
|
||||
rules={registerOptions}
|
||||
render={({ field, fieldState, formState }) => (
|
||||
<TextField
|
||||
aria-label={ariaLabel}
|
||||
className={className}
|
||||
isDisabled={field.disabled}
|
||||
isReadOnly={readOnly}
|
||||
isInvalid={fieldState.invalid}
|
||||
isRequired={!!registerOptions.required}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
onChange={field.onChange}
|
||||
validationBehavior="aria"
|
||||
value={field.value}
|
||||
>
|
||||
<InputWithLabel
|
||||
{...field}
|
||||
ref={ref}
|
||||
aria-labelledby={field.name}
|
||||
autoComplete={autoComplete}
|
||||
id={field.name}
|
||||
label={label}
|
||||
maxLength={maxLength}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
disabled={disabled}
|
||||
required={!!registerOptions.required}
|
||||
type={type}
|
||||
inputMode={inputMode}
|
||||
/>
|
||||
{helpText && !fieldState.error ? (
|
||||
<Caption asChild color="black">
|
||||
<Text className={styles.helpText} slot="description">
|
||||
<MaterialIcon icon="check" size={20} />
|
||||
{helpText}
|
||||
</Text>
|
||||
</Caption>
|
||||
) : null}
|
||||
{fieldState.error && !hideError ? (
|
||||
<ErrorMessage
|
||||
errors={formState.errors}
|
||||
name={name}
|
||||
messageLabel={getErrorMessage(intl, fieldState.error.message)}
|
||||
/>
|
||||
) : null}
|
||||
</TextField>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
export default Input
|
||||
@@ -1,17 +0,0 @@
|
||||
.helpText {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: var(--Space-x05);
|
||||
}
|
||||
|
||||
.error {
|
||||
align-items: center;
|
||||
color: var(--Text-Interactive-Error);
|
||||
display: flex;
|
||||
gap: var(--Space-x05);
|
||||
margin: var(--Space-x1) 0 0;
|
||||
}
|
||||
|
||||
.error svg {
|
||||
min-width: 20px;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { RegisterOptions } from "react-hook-form"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
helpText?: string
|
||||
label: string
|
||||
name: string
|
||||
registerOptions?: RegisterOptions
|
||||
hideError?: boolean
|
||||
}
|
||||
@@ -20,8 +20,7 @@ import { NewPasswordValidation } from "./NewPasswordValidation"
|
||||
|
||||
import styles from "./passwordInput.module.css"
|
||||
|
||||
interface PasswordInputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
interface PasswordInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string
|
||||
registerOptions?: RegisterOptions
|
||||
visibilityToggleable?: boolean
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toggleButton {
|
||||
.inputWrapper .toggleButton {
|
||||
position: absolute;
|
||||
right: var(--Space-x2);
|
||||
top: 50%;
|
||||
|
||||
@@ -196,3 +196,14 @@ export function getErrorMessage(intl: IntlShape, errorCode?: string) {
|
||||
return errorCode
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for getErrorMessage that ensures a string is always returned.
|
||||
* Can be used directly as errorFormatter prop for FormInput components.
|
||||
*/
|
||||
export function formatFormErrorMessage(
|
||||
intl: IntlShape,
|
||||
errorMessage?: string
|
||||
): string {
|
||||
return getErrorMessage(intl, errorMessage) ?? ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user