Merged in fix/BOOK-323-enter-details-scroll-error (pull request #2986)

Fix/BOOK-323 enter details scroll error

* fix(BOOK-323): scroll to invalid element on submit on enter details

* fix(BOOK-323): update error message design

* fix(BOOK-323): clean up

* fix(BOOK-323): scroll to fields in room in right order

* fix(BOOK-323): add id to translations

* fix(BOOK-323): remove undefined

* fix(BOOK-323): fix submitting state

* fix(BOOK-323): use ref in multiroom for scrolling to right element, add membershipNo

* fix(BOOK-323): fix invalid border country

* fix(BOOK-323): use error message component

* fix(BOOK-323): fix invalid focused styling on mobile

* fix(BOOK-323): remove redundant dependency in callback


Approved-by: Erik Tiekstra
This commit is contained in:
Bianca Widstam
2025-10-24 11:30:56 +00:00
parent 6543ca5dc3
commit c473bbc8b0
27 changed files with 692 additions and 288 deletions

View File

@@ -38,11 +38,3 @@
.topAlign {
align-items: flex-start;
}
.error {
align-items: center;
color: var(--Scandic-Red-60);
display: flex;
gap: var(--Spacing-x-half);
margin: var(--Spacing-x1) 0 0;
}

View File

@@ -10,7 +10,7 @@ import {
import styles from './checkbox.module.css'
import { MaterialIcon } from '../../Icons/MaterialIcon'
import Caption from '../../Caption'
import { ErrorMessage } from '../ErrorMessage'
interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
name: string
@@ -36,7 +36,7 @@ const Checkbox = forwardRef<
ref
) {
const { control } = useFormContext()
const { field, fieldState } = useController({
const { field, fieldState, formState } = useController({
control,
name,
rules: registerOptions,
@@ -48,6 +48,7 @@ const Checkbox = forwardRef<
isSelected={field.value}
onChange={field.onChange}
data-testid={name}
name={name}
isDisabled={registerOptions?.disabled}
excludeFromTabOrder
>
@@ -68,12 +69,15 @@ const Checkbox = forwardRef<
{children}
</span>
{fieldState.error && !hideError ? (
<Caption className={styles.error} fontOnly>
<MaterialIcon icon="info" color="Icon/Interactive/Accent" />
{(fieldState.error.message &&
errorCodeMessages?.[fieldState.error.message]) ||
fieldState.error.message}
</Caption>
<ErrorMessage
errors={formState.errors}
name={name}
messageLabel={
(fieldState.error.message &&
errorCodeMessages?.[fieldState.error.message]) ||
fieldState.error.message
}
/>
) : null}
</>
)}

View File

@@ -52,6 +52,10 @@
&[data-invalid] {
border-color: var(--Border-Interactive-Error);
}
&[data-invalid][data-focused] {
outline: 2px solid var(--Border-Interactive-Error);
}
}
.inner {

View File

@@ -10,7 +10,12 @@ export function Error({ children }: React.PropsWithChildren) {
variant="Body/Supporting text (caption)/smRegular"
>
<span>
<MaterialIcon icon="info" color="Icon/Feedback/Error" />
<MaterialIcon
icon="error"
color="Icon/Feedback/Error"
isFilled
size={20}
/>
{children}
</span>
</Typography>

View File

@@ -0,0 +1,64 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { MessageBanner } from './index'
type MessageBannerType = 'default' | 'error' | 'info'
type TextColor = 'default' | 'error'
const meta: Meta<typeof MessageBanner> = {
title: 'Components/MessageBanner',
component: MessageBanner,
argTypes: {
type: {
control: { type: 'select' },
options: ['default', 'error', 'info'] as MessageBannerType[],
},
textColor: {
control: { type: 'select' },
options: ['default', 'error'] as TextColor[],
},
text: { control: 'text' },
},
}
export default meta
type Story = StoryObj<typeof MessageBanner>
export const Default: Story = {
args: {
type: 'default',
textColor: 'default',
text: 'This is a default message',
},
}
export const Warning: Story = {
args: {
type: 'error',
textColor: 'default',
text: 'This is a warning message',
},
}
export const WarningErrorText: Story = {
args: {
type: 'error',
textColor: 'error',
text: 'Warning with error text color',
},
}
export const Info: Story = {
args: {
type: 'info',
textColor: 'default',
text: 'This is an info message',
},
}
export const InfoErrorText: Story = {
args: {
type: 'info',
textColor: 'error',
text: 'Info with error text color',
},
}

View File

@@ -0,0 +1,55 @@
import { cva } from 'class-variance-authority'
import styles from './messageBanner.module.css'
import { Typography } from '../Typography'
import { MaterialIcon } from '../Icons/MaterialIcon'
type MessageBannerType = 'default' | 'error' | 'info'
type TextColor = 'default' | 'error'
const textVariants = cva('', {
variants: {
textColor: {
default: styles.textDefault,
error: styles.textError,
},
},
defaultVariants: {
textColor: 'default',
},
})
type MessageBannerProps = {
type?: MessageBannerType
textColor?: TextColor
text: string
}
export function MessageBanner({
type = 'default',
textColor = 'default',
text,
}: MessageBannerProps) {
const textClass = textVariants({ textColor })
const iconName = type === 'error' ? 'error' : 'info'
const iconColor =
type === 'error'
? 'Icon/Feedback/Error'
: type === 'info'
? 'Icon/Feedback/Information'
: 'Icon/Default'
return (
<div className={styles.container}>
<Typography
className={textClass}
variant="Body/Supporting text (caption)/smRegular"
>
<span className={styles.content}>
<MaterialIcon size={20} icon={iconName} color={iconColor} isFilled />
{text}
</span>
</Typography>
</div>
)
}

View File

@@ -0,0 +1,21 @@
.container {
display: flex;
padding: var(--Space-x15);
background-color: var(--Surface-Primary-Default);
border-radius: var(--Corner-radius-md);
border: 1px solid var(--Border-Default);
}
.content {
display: flex;
align-items: center;
gap: var(--Space-x1);
}
.textDefault {
color: var(--Text-Default);
}
.textError {
color: var(--Text-Feedback-Error-Accent);
}

View File

@@ -36,8 +36,8 @@ export const DinersClubIcon = (props: PaymentIconProps) => (
y2="21.6"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#3479C0" />
<stop offset="1" stop-color="#133362" />
<stop stopColor="#3479C0" />
<stop offset="1" stopColor="#133362" />
</linearGradient>
<clipPath id="clip0_5382_46858">
<rect width="48" height="32" fill="white" />

View File

@@ -89,10 +89,10 @@ export const DiscoverIcon = (props: PaymentIconProps) => (
gradientUnits="userSpaceOnUse"
gradientTransform="translate(28.6 17.5998) rotate(-142.431) scale(6.56048 6.47264)"
>
<stop stop-color="#F59900" />
<stop offset="0.210082" stop-color="#F39501" />
<stop offset="0.908163" stop-color="#CE3C0B" />
<stop offset="1" stop-color="#A4420A" />
<stop stopColor="#F59900" />
<stop offset="0.210082" stopColor="#F39501" />
<stop offset="0.908163" stopColor="#CE3C0B" />
<stop offset="1" stopColor="#A4420A" />
</radialGradient>
<clipPath id="clip0_5382_46865">
<rect width="48" height="32" rx="3" fill="white" />

View File

@@ -49,10 +49,10 @@ export const JcbIcon = (props: PaymentIconProps) => (
y2="16.0328"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#007940" />
<stop offset="0.2285" stop-color="#00873F" />
<stop offset="0.7433" stop-color="#40A737" />
<stop offset="1" stop-color="#5CB531" />
<stop stopColor="#007940" />
<stop offset="0.2285" stopColor="#00873F" />
<stop offset="0.7433" stopColor="#40A737" />
<stop offset="1" stopColor="#5CB531" />
</linearGradient>
<linearGradient
id="paint1_linear_5382_46863"
@@ -62,10 +62,10 @@ export const JcbIcon = (props: PaymentIconProps) => (
y2="16.001"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#007940" />
<stop offset="0.2285" stop-color="#00873F" />
<stop offset="0.7433" stop-color="#40A737" />
<stop offset="1" stop-color="#5CB531" />
<stop stopColor="#007940" />
<stop offset="0.2285" stopColor="#00873F" />
<stop offset="0.7433" stopColor="#40A737" />
<stop offset="1" stopColor="#5CB531" />
</linearGradient>
<linearGradient
id="paint2_linear_5382_46863"
@@ -75,10 +75,10 @@ export const JcbIcon = (props: PaymentIconProps) => (
y2="14.4771"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#007940" />
<stop offset="0.2285" stop-color="#00873F" />
<stop offset="0.7433" stop-color="#40A737" />
<stop offset="1" stop-color="#5CB531" />
<stop stopColor="#007940" />
<stop offset="0.2285" stopColor="#00873F" />
<stop offset="0.7433" stopColor="#40A737" />
<stop offset="1" stopColor="#5CB531" />
</linearGradient>
<linearGradient
id="paint3_linear_5382_46863"
@@ -88,11 +88,11 @@ export const JcbIcon = (props: PaymentIconProps) => (
y2="16.001"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#6C2C2F" />
<stop offset="0.1735" stop-color="#882730" />
<stop offset="0.5731" stop-color="#BE1833" />
<stop offset="0.8585" stop-color="#DC0436" />
<stop offset="1" stop-color="#E60039" />
<stop stopColor="#6C2C2F" />
<stop offset="0.1735" stopColor="#882730" />
<stop offset="0.5731" stopColor="#BE1833" />
<stop offset="0.8585" stopColor="#DC0436" />
<stop offset="1" stopColor="#E60039" />
</linearGradient>
<linearGradient
id="paint4_linear_5382_46863"
@@ -102,10 +102,10 @@ export const JcbIcon = (props: PaymentIconProps) => (
y2="16.001"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#1F286F" />
<stop offset="0.4751" stop-color="#004E94" />
<stop offset="0.8261" stop-color="#0066B1" />
<stop offset="1" stop-color="#006FBC" />
<stop stopColor="#1F286F" />
<stop offset="0.4751" stopColor="#004E94" />
<stop offset="0.8261" stopColor="#0066B1" />
<stop offset="1" stopColor="#006FBC" />
</linearGradient>
<clipPath id="clip0_5382_46863">
<rect width="48" height="32" rx="3" fill="white" />

View File

@@ -61,8 +61,8 @@ export const SwishIcon = (props: PaymentIconProps) => {
y2="13.9987"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#EF3220" />
<stop offset="1" stop-color="#FCD205" />
<stop stopColor="#EF3220" />
<stop offset="1" stopColor="#FCD205" />
</linearGradient>
<linearGradient
id="paint1_linear_5382_46851"
@@ -72,10 +72,10 @@ export const SwishIcon = (props: PaymentIconProps) => {
y2="21.8844"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FCD205" />
<stop offset="0.263921" stop-color="#F47216" />
<stop offset="0.560797" stop-color="#B31A93" />
<stop offset="1" stop-color="#2743A0" />
<stop stopColor="#FCD205" />
<stop offset="0.263921" stopColor="#F47216" />
<stop offset="0.560797" stopColor="#B31A93" />
<stop offset="1" stopColor="#2743A0" />
</linearGradient>
<linearGradient
id="paint2_linear_5382_46851"
@@ -85,10 +85,10 @@ export const SwishIcon = (props: PaymentIconProps) => {
y2="18.0191"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#7FD3B9" />
<stop offset="0.265705" stop-color="#66CDE1" />
<stop offset="0.554471" stop-color="#6D8ED1" />
<stop offset="1" stop-color="#2743A0" />
<stop stopColor="#7FD3B9" />
<stop offset="0.265705" stopColor="#66CDE1" />
<stop offset="0.554471" stopColor="#6D8ED1" />
<stop offset="1" stopColor="#2743A0" />
</linearGradient>
<linearGradient
id="paint3_linear_5382_46851"
@@ -98,10 +98,10 @@ export const SwishIcon = (props: PaymentIconProps) => {
y2="10.1074"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#1E5CB2" />
<stop offset="0.246658" stop-color="#4DC4CE" />
<stop offset="0.564821" stop-color="#66C657" />
<stop offset="1" stop-color="#FCD205" />
<stop stopColor="#1E5CB2" />
<stop offset="0.246658" stopColor="#4DC4CE" />
<stop offset="0.564821" stopColor="#66C657" />
<stop offset="1" stopColor="#FCD205" />
</linearGradient>
<clipPath id="clip0_5382_46851">
<rect width="48" height="32" fill="white" />

View File

@@ -52,6 +52,9 @@
&[data-invalid] {
border-color: var(--Border-Interactive-Error);
}
&[data-invalid][data-focused] {
outline: 2px solid var(--Border-Interactive-Error);
}
}
.chevron {

View File

@@ -147,6 +147,7 @@
"./Map/Markers/HotelMarkerByType": "./lib/components/Map/Markers/HotelMarkerByType.tsx",
"./Map/Markers/PoiMarker": "./lib/components/Map/Markers/PoiMarker/index.tsx",
"./Map/types": "./lib/components/Map/types.ts",
"./MessageBanner": "./lib/components/MessageBanner/index.tsx",
"./Modal": "./lib/components/Modal/index.tsx",
"./Modal/ModalContentWithActions": "./lib/components/Modal/ModalContentWithActions/index.tsx",
"./NoRateAvailableCard": "./lib/components/RateCard/NoRateAvailable/index.tsx",