Merge branch 'develop' into feature/tracking
This commit is contained in:
@@ -14,7 +14,7 @@ export default function CardImage({
|
||||
return (
|
||||
<article className={`${styles.container} ${className}`}>
|
||||
<div className={styles.imageContainer}>
|
||||
{imageCards.map(
|
||||
{imageCards?.map(
|
||||
({ backgroundImage }) =>
|
||||
backgroundImage && (
|
||||
<Image
|
||||
|
||||
@@ -2,6 +2,7 @@ import { cardVariants } from "./variants"
|
||||
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
import type { ApiImage } from "@/types/components/image"
|
||||
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
||||
|
||||
export interface CardProps
|
||||
@@ -22,9 +23,10 @@ export interface CardProps
|
||||
scriptedTopTitle?: string | null
|
||||
heading?: string | null
|
||||
bodyText?: string | null
|
||||
backgroundImage?: ImageVaultAsset
|
||||
imageHeight?: number
|
||||
imageWidth?: number
|
||||
imageGradient?: boolean
|
||||
onPrimaryButtonClick?: () => void
|
||||
onSecondaryButtonClick?: () => void
|
||||
backgroundImage?: ImageVaultAsset | ApiImage
|
||||
}
|
||||
|
||||
@@ -24,15 +24,17 @@ export default function Card({
|
||||
backgroundImage,
|
||||
imageHeight,
|
||||
imageWidth,
|
||||
imageGradient,
|
||||
onPrimaryButtonClick,
|
||||
onSecondaryButtonClick,
|
||||
}: CardProps) {
|
||||
const buttonTheme = getTheme(theme)
|
||||
|
||||
imageHeight = imageHeight || 320
|
||||
|
||||
imageWidth =
|
||||
imageWidth ||
|
||||
(backgroundImage
|
||||
(backgroundImage && "dimensions" in backgroundImage
|
||||
? backgroundImage.dimensions.aspectRatio * imageHeight
|
||||
: 420)
|
||||
|
||||
@@ -44,7 +46,7 @@ export default function Card({
|
||||
})}
|
||||
>
|
||||
{backgroundImage && (
|
||||
<div className={styles.imageWrapper}>
|
||||
<div className={imageGradient ? styles.imageWrapper : ""}>
|
||||
<Image
|
||||
src={backgroundImage.url}
|
||||
className={styles.image}
|
||||
|
||||
7
components/TempDesignSystem/Form/Card/Checkbox.tsx
Normal file
7
components/TempDesignSystem/Form/Card/Checkbox.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import Card from "."
|
||||
|
||||
import type { CheckboxProps } from "./card"
|
||||
|
||||
export default function CheckboxCard(props: CheckboxProps) {
|
||||
return <Card {...props} type="checkbox" />
|
||||
}
|
||||
7
components/TempDesignSystem/Form/Card/Radio.tsx
Normal file
7
components/TempDesignSystem/Form/Card/Radio.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import Card from "."
|
||||
|
||||
import type { RadioProps } from "./card"
|
||||
|
||||
export default function RadioCard(props: RadioProps) {
|
||||
return <Card {...props} type="radio" />
|
||||
}
|
||||
72
components/TempDesignSystem/Form/Card/card.module.css
Normal file
72
components/TempDesignSystem/Form/Card/card.module.css
Normal file
@@ -0,0 +1,72 @@
|
||||
.label {
|
||||
align-self: flex-start;
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
border: 1px solid var(--Base-Border-Subtle);
|
||||
border-radius: var(--Corner-radius-Large);
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
|
||||
transition: all 200ms ease;
|
||||
width: min(100%, 600px);
|
||||
}
|
||||
|
||||
.label:hover {
|
||||
background-color: var(--Base-Surface-Secondary-light-Hover);
|
||||
}
|
||||
|
||||
.label:has(:checked) {
|
||||
background-color: var(--Primary-Light-Surface-Normal);
|
||||
border-color: var(--Base-Border-Hover);
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: center;
|
||||
grid-column: 2/3;
|
||||
grid-row: 1/3;
|
||||
justify-self: flex-end;
|
||||
transition: fill 200ms ease;
|
||||
}
|
||||
|
||||
.label:hover .icon,
|
||||
.label:hover .icon *,
|
||||
.label:has(:checked) .icon,
|
||||
.label:has(:checked) .icon * {
|
||||
fill: var(--Base-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.label[data-declined="true"]:hover .icon,
|
||||
.label[data-declined="true"]:hover .icon *,
|
||||
.label[data-declined="true"]:has(:checked) .icon,
|
||||
.label[data-declined="true"]:has(:checked) .icon * {
|
||||
fill: var(--Base-Text-Disabled);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.title {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
|
||||
.label .text {
|
||||
margin-top: var(--Spacing-x1);
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--Spacing-x-quarter);
|
||||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
.listItem:first-of-type {
|
||||
margin-top: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.listItem:nth-of-type(n + 2) {
|
||||
margin-top: var(--Spacing-x-quarter);
|
||||
}
|
||||
35
components/TempDesignSystem/Form/Card/card.ts
Normal file
35
components/TempDesignSystem/Form/Card/card.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
interface BaseCardProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
||||
Icon?: (props: IconProps) => JSX.Element
|
||||
declined?: boolean
|
||||
iconHeight?: number
|
||||
iconWidth?: number
|
||||
name?: string
|
||||
saving?: boolean
|
||||
subtitle?: string
|
||||
title: string
|
||||
type: "checkbox" | "radio"
|
||||
value?: string
|
||||
}
|
||||
|
||||
interface ListCardProps extends BaseCardProps {
|
||||
list: {
|
||||
title: string
|
||||
}[]
|
||||
text?: never
|
||||
}
|
||||
|
||||
interface TextCardProps extends BaseCardProps {
|
||||
list?: never
|
||||
text: string
|
||||
}
|
||||
|
||||
export type CardProps = ListCardProps | TextCardProps
|
||||
|
||||
export type CheckboxProps =
|
||||
| Omit<ListCardProps, "type">
|
||||
| Omit<TextCardProps, "type">
|
||||
export type RadioProps =
|
||||
| Omit<ListCardProps, "type">
|
||||
| Omit<TextCardProps, "type">
|
||||
77
components/TempDesignSystem/Form/Card/index.tsx
Normal file
77
components/TempDesignSystem/Form/Card/index.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
"use client"
|
||||
|
||||
import { CheckIcon, CloseIcon, HeartIcon } from "@/components/Icons"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
|
||||
import styles from "./card.module.css"
|
||||
|
||||
import type { CardProps } from "./card"
|
||||
|
||||
export default function Card({
|
||||
Icon = HeartIcon,
|
||||
iconHeight = 32,
|
||||
iconWidth = 32,
|
||||
declined = false,
|
||||
id,
|
||||
list,
|
||||
name = "join",
|
||||
saving = false,
|
||||
subtitle,
|
||||
text,
|
||||
title,
|
||||
type,
|
||||
value,
|
||||
}: CardProps) {
|
||||
return (
|
||||
<label className={styles.label} data-declined={declined}>
|
||||
<Caption className={styles.title} textTransform="bold" uppercase>
|
||||
{title}
|
||||
</Caption>
|
||||
{subtitle ? (
|
||||
<Caption
|
||||
className={styles.subtitle}
|
||||
color={saving ? "baseTextAccent" : "uiTextHighContrast"}
|
||||
textTransform="bold"
|
||||
>
|
||||
{subtitle}
|
||||
</Caption>
|
||||
) : null}
|
||||
<Icon
|
||||
className={styles.icon}
|
||||
color="uiTextHighContrast"
|
||||
height={iconHeight}
|
||||
width={iconWidth}
|
||||
/>
|
||||
{list
|
||||
? list.map((listItem) => (
|
||||
<span key={listItem.title} className={styles.listItem}>
|
||||
{declined ? (
|
||||
<CloseIcon
|
||||
color="uiTextMediumContrast"
|
||||
height={20}
|
||||
width={20}
|
||||
/>
|
||||
) : (
|
||||
<CheckIcon color="baseIconLowContrast" height={20} width={20} />
|
||||
)}
|
||||
<Footnote color="uiTextMediumContrast">{listItem.title}</Footnote>
|
||||
</span>
|
||||
))
|
||||
: null}
|
||||
{text ? (
|
||||
<Footnote className={styles.text} color="uiTextMediumContrast">
|
||||
{text}
|
||||
</Footnote>
|
||||
) : null}
|
||||
<input
|
||||
aria-hidden
|
||||
id={id || name}
|
||||
hidden
|
||||
name={name}
|
||||
type={type}
|
||||
value={value}
|
||||
/>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { RegisterOptions } from "react-hook-form"
|
||||
|
||||
export type CountryProps = {
|
||||
className?: string
|
||||
label: string
|
||||
name?: string
|
||||
placeholder?: string
|
||||
readOnly?: boolean
|
||||
registerOptions?: RegisterOptions
|
||||
}
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@ import type {
|
||||
} from "./country"
|
||||
|
||||
export default function CountrySelect({
|
||||
className = "",
|
||||
label,
|
||||
name = "country",
|
||||
readOnly = false,
|
||||
registerOptions = {},
|
||||
}: CountryProps) {
|
||||
const { formatMessage } = useIntl()
|
||||
@@ -54,12 +56,13 @@ export default function CountrySelect({
|
||||
const selectCountryLabel = formatMessage({ id: "Select a country" })
|
||||
|
||||
return (
|
||||
<div className={styles.container} ref={setRef}>
|
||||
<div className={`${styles.container} ${className}`} ref={setRef}>
|
||||
<ComboBox
|
||||
aria-label={formatMessage({ id: "Select country of residence" })}
|
||||
className={styles.select}
|
||||
isRequired={!!registerOptions?.required}
|
||||
isInvalid={fieldState.invalid}
|
||||
isReadOnly={readOnly}
|
||||
isRequired={!!registerOptions?.required}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
onSelectionChange={handleChange}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { type ForwardedRef, forwardRef } from "react"
|
||||
import { Input as AriaInput, Label as AriaLabel } from "react-aria-components"
|
||||
|
||||
import Label from "@/components/TempDesignSystem/Form/Label"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
|
||||
import styles from "./input.module.css"
|
||||
|
||||
import type { AriaInputWithLabelProps } from "./input"
|
||||
|
||||
const AriaInputWithLabel = forwardRef(function AriaInputWithLabelComponent(
|
||||
{ label, ...props }: AriaInputWithLabelProps,
|
||||
ref: ForwardedRef<HTMLInputElement>
|
||||
) {
|
||||
return (
|
||||
<AriaLabel className={styles.container} htmlFor={props.name}>
|
||||
<Body asChild fontOnly>
|
||||
<AriaInput {...props} className={styles.input} ref={ref} />
|
||||
</Body>
|
||||
<Label required={!!props.required}>{label}</Label>
|
||||
</AriaLabel>
|
||||
)
|
||||
})
|
||||
|
||||
export default AriaInputWithLabel
|
||||
@@ -0,0 +1,55 @@
|
||||
.container {
|
||||
align-content: center;
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-color: var(--Scandic-Beige-40);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
display: grid;
|
||||
height: 60px;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
|
||||
.container:has(.input:active, .input:focus) {
|
||||
border-color: var(--Scandic-Blue-90);
|
||||
}
|
||||
|
||||
.container:has(.input:disabled) {
|
||||
background-color: var(--Main-Grey-10);
|
||||
border: none;
|
||||
color: var(--Main-Grey-40);
|
||||
}
|
||||
|
||||
.container:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
|
||||
border-color: var(--Scandic-Red-60);
|
||||
}
|
||||
|
||||
.input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--Main-Grey-100);
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
order: 2;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input:not(:active, :focus):placeholder-shown {
|
||||
height: 0px;
|
||||
transition: height 150ms ease;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.input:focus:placeholder-shown,
|
||||
.input:active,
|
||||
.input:active:placeholder-shown {
|
||||
height: 18px;
|
||||
transition: height 150ms ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.input:disabled {
|
||||
color: var(--Main-Grey-40);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface AriaInputWithLabelProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label: string
|
||||
}
|
||||
@@ -1,15 +1,9 @@
|
||||
"use client"
|
||||
import {
|
||||
Input as AriaInput,
|
||||
Label as AriaLabel,
|
||||
Text,
|
||||
TextField,
|
||||
} from "react-aria-components"
|
||||
import { Text, TextField } from "react-aria-components"
|
||||
import { Controller, useFormContext } from "react-hook-form"
|
||||
|
||||
import { CheckIcon, InfoCircleIcon } from "@/components/Icons"
|
||||
import Label from "@/components/TempDesignSystem/Form/Label"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import styles from "./input.module.css"
|
||||
@@ -20,11 +14,13 @@ import type { InputProps } from "./input"
|
||||
|
||||
export default function Input({
|
||||
"aria-label": ariaLabel,
|
||||
className = "",
|
||||
disabled = false,
|
||||
helpText = "",
|
||||
label,
|
||||
name,
|
||||
placeholder = "",
|
||||
readOnly = false,
|
||||
registerOptions = {},
|
||||
type = "text",
|
||||
}: InputProps) {
|
||||
@@ -44,6 +40,7 @@ export default function Input({
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
aria-label={ariaLabel}
|
||||
className={className}
|
||||
isDisabled={field.disabled}
|
||||
isInvalid={fieldState.invalid}
|
||||
isRequired={!!registerOptions.required}
|
||||
@@ -53,19 +50,16 @@ export default function Input({
|
||||
validationBehavior="aria"
|
||||
value={field.value}
|
||||
>
|
||||
<AriaLabel className={styles.container} htmlFor={field.name}>
|
||||
<Body asChild fontOnly>
|
||||
<AriaInput
|
||||
{...numberAttributes}
|
||||
aria-labelledby={field.name}
|
||||
className={styles.input}
|
||||
id={field.name}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
/>
|
||||
</Body>
|
||||
<Label required={!!registerOptions.required}>{label}</Label>
|
||||
</AriaLabel>
|
||||
<AriaInputWithLabel
|
||||
{...field}
|
||||
aria-labelledby={field.name}
|
||||
id={field.name}
|
||||
label={label}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
required={!!registerOptions.required}
|
||||
type={type}
|
||||
/>
|
||||
{helpText && !fieldState.error ? (
|
||||
<Caption asChild color="black">
|
||||
<Text className={styles.helpText} slot="description">
|
||||
|
||||
@@ -1,59 +1,3 @@
|
||||
.container {
|
||||
align-content: center;
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-color: var(--Scandic-Beige-40);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
display: grid;
|
||||
height: 60px;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
|
||||
.container:has(.input:active, .input:focus) {
|
||||
border-color: var(--Scandic-Blue-90);
|
||||
}
|
||||
|
||||
.container:has(.input:disabled) {
|
||||
background-color: var(--Main-Grey-10);
|
||||
border: none;
|
||||
color: var(--Main-Grey-40);
|
||||
}
|
||||
|
||||
.container:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
|
||||
border-color: var(--Scandic-Red-60);
|
||||
}
|
||||
|
||||
.input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--Main-Grey-100);
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
order: 2;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input:not(:active, :focus):placeholder-shown {
|
||||
height: 0px;
|
||||
transition: height 150ms ease;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.input:focus:placeholder-shown,
|
||||
.input:active,
|
||||
.input:active:placeholder-shown {
|
||||
height: 18px;
|
||||
transition: height 150ms ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.input:disabled {
|
||||
color: var(--Main-Grey-40);
|
||||
}
|
||||
|
||||
.helpText {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { RegisterOptions, UseFormRegister } from "react-hook-form"
|
||||
import type { RegisterOptions } from "react-hook-form"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
letter-spacing: 0.03px;
|
||||
line-height: 120%;
|
||||
text-align: left;
|
||||
transition: font-size 100ms ease;
|
||||
}
|
||||
|
||||
span.small {
|
||||
@@ -21,7 +22,6 @@ input:active ~ .label,
|
||||
input:not(:placeholder-shown) ~ .label {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
transition: font-size 100ms ease;
|
||||
}
|
||||
|
||||
input:focus ~ .label {
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
"use client"
|
||||
import {
|
||||
Input as AriaInput,
|
||||
Label as AriaLabel,
|
||||
Text,
|
||||
TextField,
|
||||
} from "react-aria-components"
|
||||
import { Text, TextField } from "react-aria-components"
|
||||
import { Controller, useFormContext } from "react-hook-form"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { CheckIcon, CloseIcon } from "@/components/Icons"
|
||||
import Error from "@/components/TempDesignSystem/Form/ErrorMessage/Error"
|
||||
import Label from "@/components/TempDesignSystem/Form/Label"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import { type IconProps, Key, type NewPasswordProps } from "./newPassword"
|
||||
@@ -47,20 +41,14 @@ export default function NewPassword({
|
||||
value={field.value}
|
||||
type="password"
|
||||
>
|
||||
<AriaLabel className={styles.container} htmlFor={field.name}>
|
||||
<Body asChild fontOnly>
|
||||
<AriaInput
|
||||
aria-labelledby={field.name}
|
||||
className={styles.input}
|
||||
id={field.name}
|
||||
placeholder={placeholder}
|
||||
type="password"
|
||||
/>
|
||||
</Body>
|
||||
<Label required={!!registerOptions.required}>
|
||||
{formatMessage({ id: "New password" })}
|
||||
</Label>
|
||||
</AriaLabel>
|
||||
<AriaInputWithLabel
|
||||
{...field}
|
||||
aria-labelledby={field.name}
|
||||
id={field.name}
|
||||
label={formatMessage({ id: "New password" })}
|
||||
placeholder={placeholder}
|
||||
type="password"
|
||||
/>
|
||||
{field.value ? (
|
||||
<div className={styles.errors}>
|
||||
<Caption asChild color="black">
|
||||
|
||||
@@ -1,59 +1,3 @@
|
||||
.container {
|
||||
align-content: center;
|
||||
background-color: var(--Main-Grey-White);
|
||||
border-color: var(--Scandic-Beige-40);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
display: grid;
|
||||
height: 60px;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||
transition: border-color 200ms ease;
|
||||
}
|
||||
|
||||
.container:has(.input:active, .input:focus) {
|
||||
border-color: var(--Scandic-Blue-90);
|
||||
}
|
||||
|
||||
.container:has(.input:disabled) {
|
||||
background-color: var(--Main-Grey-10);
|
||||
border: none;
|
||||
color: var(--Main-Grey-40);
|
||||
}
|
||||
|
||||
.container:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
|
||||
border-color: var(--Scandic-Red-60);
|
||||
}
|
||||
|
||||
.input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--Main-Grey-100);
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
order: 2;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input:not(:active, :focus):placeholder-shown {
|
||||
height: 0px;
|
||||
transition: height 150ms ease;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.input:focus:placeholder-shown,
|
||||
.input:active,
|
||||
.input:active:placeholder-shown {
|
||||
height: 18px;
|
||||
transition: height 150ms ease;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.input:disabled {
|
||||
color: var(--Main-Grey-40);
|
||||
}
|
||||
|
||||
.helpText {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
|
||||
@@ -2,11 +2,7 @@
|
||||
import "react-international-phone/style.css"
|
||||
|
||||
import { isValidPhoneNumber, parsePhoneNumber } from "libphonenumber-js"
|
||||
import {
|
||||
Input as AriaInput,
|
||||
Label as AriaLabel,
|
||||
TextField,
|
||||
} from "react-aria-components"
|
||||
import { TextField } from "react-aria-components"
|
||||
import { useController, useFormContext, useWatch } from "react-hook-form"
|
||||
import {
|
||||
CountrySelector,
|
||||
@@ -18,6 +14,7 @@ import { useIntl } from "react-intl"
|
||||
|
||||
import { ChevronDownIcon } from "@/components/Icons"
|
||||
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
|
||||
import AriaInputWithLabel from "@/components/TempDesignSystem/Form/Input/AriaInputWithLabel"
|
||||
import Label from "@/components/TempDesignSystem/Form/Label"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
|
||||
@@ -29,10 +26,12 @@ import type { PhoneProps } from "./phone"
|
||||
|
||||
export default function Phone({
|
||||
ariaLabel = "Phone number input",
|
||||
className = "",
|
||||
disabled = false,
|
||||
label,
|
||||
name = "phoneNumber",
|
||||
placeholder = "",
|
||||
readOnly = false,
|
||||
registerOptions = {
|
||||
required: true,
|
||||
},
|
||||
@@ -72,8 +71,9 @@ export default function Phone({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.phone}>
|
||||
<div className={`${styles.phone} ${className}`}>
|
||||
<CountrySelector
|
||||
disabled={readOnly}
|
||||
dropdownArrowClassName={styles.arrow}
|
||||
flagClassName={styles.flag}
|
||||
onSelect={handleSelectCountry}
|
||||
@@ -114,25 +114,21 @@ export default function Phone({
|
||||
isDisabled={disabled ?? field.disabled}
|
||||
isInvalid={fieldState.invalid}
|
||||
isRequired={!!registerOptions?.required}
|
||||
isReadOnly={readOnly}
|
||||
name={field.name}
|
||||
type="tel"
|
||||
>
|
||||
<AriaLabel className={styles.inputContainer} htmlFor={field.name}>
|
||||
<Body asChild fontOnly>
|
||||
<AriaInput
|
||||
className={styles.input}
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
ref={field.ref}
|
||||
required={!!registerOptions.required}
|
||||
value={inputValue}
|
||||
/>
|
||||
</Body>
|
||||
<Label required={!!registerOptions.required}>{label}</Label>
|
||||
</AriaLabel>
|
||||
<AriaInputWithLabel
|
||||
{...field}
|
||||
id={field.name}
|
||||
label={label}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
readOnly={readOnly}
|
||||
required={!!registerOptions.required}
|
||||
type="tel"
|
||||
value={inputValue}
|
||||
/>
|
||||
<ErrorMessage errors={formState.errors} name={field.name} />
|
||||
</TextField>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
--react-international-phone-dropdown-top: calc(
|
||||
var(--react-international-phone-height) + var(--Spacing-x1)
|
||||
);
|
||||
--react-international-phone-dial-code-preview-font-size: var(
|
||||
--typography-Body-Regular-fontSize
|
||||
);
|
||||
}
|
||||
|
||||
.phone:has(.input:active, .input:focus) {
|
||||
@@ -46,7 +49,6 @@
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.inputContainer,
|
||||
.select {
|
||||
align-content: center;
|
||||
background-color: var(--Main-Grey-White);
|
||||
@@ -93,42 +95,8 @@
|
||||
|
||||
.select .dialCode {
|
||||
border: none;
|
||||
color: var(--Main-Grey-100);
|
||||
color: var(--UI-Text-High-contrast);
|
||||
line-height: 1;
|
||||
justify-self: flex-start;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.inputContainer:has(.input:not(:focus):placeholder-shown) {
|
||||
gap: 0;
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.inputContainer:has(.input:active, .input:focus) {
|
||||
border-color: var(--Scandic-Blue-90);
|
||||
}
|
||||
|
||||
.inputContainer:has(.input[data-invalid="true"], .input[aria-invalid="true"]) {
|
||||
border-color: var(--Scandic-Red-60);
|
||||
}
|
||||
|
||||
.input {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--Main-Grey-100);
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
order: 2;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.input:not(:active, :focus):placeholder-shown {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
.input:focus,
|
||||
.input:focus:placeholder-shown {
|
||||
height: 18px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@ import type { RegisterOptions } from "react-hook-form"
|
||||
|
||||
export type PhoneProps = {
|
||||
ariaLabel?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
label: string
|
||||
name?: string
|
||||
placeholder?: string
|
||||
readOnly?: boolean
|
||||
registerOptions?: RegisterOptions
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import React from "react"
|
||||
|
||||
import { ChevronRightIcon } from "@/components/Icons"
|
||||
import Image from "@/components/Image"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
|
||||
@@ -92,6 +92,10 @@
|
||||
color: var(--Base-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.uiTextHighContrast {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
}
|
||||
|
||||
.uiTextPlaceholder {
|
||||
color: var(--UI-Text-Placeholder);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ const config = {
|
||||
white: styles.white,
|
||||
peach50: styles.peach50,
|
||||
peach80: styles.peach80,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||
},
|
||||
textAlign: {
|
||||
|
||||
@@ -35,6 +35,10 @@ p.caption {
|
||||
text-decoration: var(--typography-Caption-Regular-textDecoration);
|
||||
}
|
||||
|
||||
.baseTextAccent {
|
||||
color: var(--Base-Text-Accent);
|
||||
}
|
||||
|
||||
.black {
|
||||
color: var(--Main-Grey-100);
|
||||
}
|
||||
@@ -67,6 +71,14 @@ p.caption {
|
||||
color: var(--UI-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
.uiTextHighContrast {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
}
|
||||
|
||||
.disabled {
|
||||
color: var(--Base-Text-Disabled);
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ export default function Caption({
|
||||
fontOnly = false,
|
||||
textAlign,
|
||||
textTransform,
|
||||
uppercase,
|
||||
...props
|
||||
}: CaptionProps) {
|
||||
const Comp = asChild ? Slot : "p"
|
||||
@@ -18,12 +19,14 @@ export default function Caption({
|
||||
? fontOnlycaptionVariants({
|
||||
className,
|
||||
textTransform,
|
||||
uppercase,
|
||||
})
|
||||
: captionVariants({
|
||||
className,
|
||||
color,
|
||||
textTransform,
|
||||
textAlign,
|
||||
uppercase,
|
||||
})
|
||||
return <Comp className={classNames} {...props} />
|
||||
}
|
||||
|
||||
@@ -5,14 +5,17 @@ import styles from "./caption.module.css"
|
||||
const config = {
|
||||
variants: {
|
||||
color: {
|
||||
baseTextAccent: styles.baseTextAccent,
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
textMediumContrast: styles.textMediumContrast,
|
||||
red: styles.red,
|
||||
white: styles.white,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
uiTextActive: styles.uiTextActive,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
disabled: styles.disabled,
|
||||
},
|
||||
textTransform: {
|
||||
bold: styles.bold,
|
||||
@@ -23,6 +26,9 @@ const config = {
|
||||
center: styles.center,
|
||||
left: styles.left,
|
||||
},
|
||||
uppercase: {
|
||||
true: styles.uppercase,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "black",
|
||||
@@ -39,6 +45,9 @@ const fontOnlyConfig = {
|
||||
regular: styles.regular,
|
||||
uppercase: styles.uppercase,
|
||||
},
|
||||
uppercase: {
|
||||
true: styles.uppercase,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
textTransform: "regular",
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
color: var(--Scandic-Peach-50);
|
||||
}
|
||||
|
||||
.textMediumContrast {
|
||||
.uiTextMediumContrast {
|
||||
color: var(--UI-Text-Medium-contrast);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ const config = {
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
peach50: styles.peach50,
|
||||
textMediumContrast: styles.textMediumContrast,
|
||||
uiTextMediumContrast: styles.uiTextMediumContrast,
|
||||
uiTextPlaceholder: styles.uiTextPlaceholder,
|
||||
},
|
||||
textAlign: {
|
||||
|
||||
@@ -58,3 +58,7 @@
|
||||
.pale {
|
||||
color: var(--Scandic-Brand-Pale-Peach);
|
||||
}
|
||||
|
||||
.uiTextHighContrast {
|
||||
color: var(--UI-Text-High-contrast);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const config = {
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
uiTextHighContrast: styles.uiTextHighContrast,
|
||||
},
|
||||
textAlign: {
|
||||
center: styles.center,
|
||||
|
||||
32
components/TempDesignSystem/Tooltip/index.tsx
Normal file
32
components/TempDesignSystem/Tooltip/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { PropsWithChildren } from "react"
|
||||
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
|
||||
import { tooltipVariants } from "./variants"
|
||||
|
||||
import styles from "./tooltip.module.css"
|
||||
|
||||
import { TooltipPosition, TooltipProps } from "@/types/components/tooltip"
|
||||
|
||||
export function Tooltip<P extends TooltipPosition>({
|
||||
heading,
|
||||
text,
|
||||
position,
|
||||
arrow,
|
||||
children,
|
||||
}: PropsWithChildren<TooltipProps<P>>) {
|
||||
const className = tooltipVariants({ position, arrow })
|
||||
return (
|
||||
<div className={styles.tooltipContainer} role="tooltip" aria-label={text}>
|
||||
<div className={className}>
|
||||
{heading && (
|
||||
<Caption textTransform="bold" color="white">
|
||||
{heading}
|
||||
</Caption>
|
||||
)}
|
||||
{text && <Caption color="white">{text}</Caption>}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
137
components/TempDesignSystem/Tooltip/tooltip.module.css
Normal file
137
components/TempDesignSystem/Tooltip/tooltip.module.css
Normal file
@@ -0,0 +1,137 @@
|
||||
.tooltipContainer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
padding: var(--Spacing-x1);
|
||||
background-color: var(--UI-Text-Active);
|
||||
border: 0.5px solid var(--UI-Border-Active);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
color: var(--Base-Text-Inverted);
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.tooltipContainer:hover .tooltip {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.left {
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
.right {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.top {
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.tooltip::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.bottom.arrowLeft::before {
|
||||
top: -8px;
|
||||
left: 16px;
|
||||
border-width: 0 7px 8px 7px;
|
||||
border-color: transparent transparent var(--UI-Text-Active) transparent;
|
||||
}
|
||||
|
||||
.bottom.arrowCenter::before {
|
||||
top: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 0 7px 8px 7px;
|
||||
border-color: transparent transparent var(--UI-Text-Active) transparent;
|
||||
}
|
||||
|
||||
.bottom.arrowRight::before {
|
||||
top: -8px;
|
||||
right: 16px;
|
||||
border-width: 0 7px 8px 7px;
|
||||
border-color: transparent transparent var(--UI-Text-Active) transparent;
|
||||
}
|
||||
|
||||
.top.arrowLeft::before {
|
||||
bottom: -8px;
|
||||
left: 16px;
|
||||
border-width: 8px 7px 0 7px;
|
||||
border-color: var(--UI-Text-Active) transparent transparent transparent;
|
||||
}
|
||||
|
||||
.top.arrowCenter::before {
|
||||
bottom: -8px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-width: 8px 7px 0 7px;
|
||||
border-color: var(--UI-Text-Active) transparent transparent transparent;
|
||||
}
|
||||
|
||||
.top.arrowRight::before {
|
||||
bottom: -8px;
|
||||
right: 16px;
|
||||
border-width: 8px 7px 0 7px;
|
||||
border-color: var(--UI-Text-Active) transparent transparent transparent;
|
||||
}
|
||||
|
||||
.left.arrowTop::before {
|
||||
top: 16px;
|
||||
right: -8px;
|
||||
transform: translateY(-50%);
|
||||
border-width: 7px 0 7px 8px;
|
||||
border-color: transparent transparent transparent var(--UI-Text-Active);
|
||||
}
|
||||
|
||||
.left.arrowCenter::before {
|
||||
top: 50%;
|
||||
right: -8px;
|
||||
transform: translateY(-50%);
|
||||
border-width: 7px 0 7px 8px;
|
||||
border-color: transparent transparent transparent var(--UI-Text-Active);
|
||||
}
|
||||
|
||||
.left.arrowBottom::before {
|
||||
bottom: 16px;
|
||||
right: -8px;
|
||||
transform: translateY(50%);
|
||||
border-width: 7px 0 7px 8px;
|
||||
border-color: transparent transparent transparent var(--UI-Text-Active);
|
||||
}
|
||||
|
||||
.right.arrowTop::before {
|
||||
top: 16px;
|
||||
left: -8px;
|
||||
transform: translateY(-50%);
|
||||
border-width: 7px 8px 7px 0;
|
||||
border-color: transparent var(--UI-Text-Active) transparent transparent;
|
||||
}
|
||||
|
||||
.right.arrowCenter::before {
|
||||
top: 50%;
|
||||
left: -8px;
|
||||
transform: translateY(-50%);
|
||||
border-width: 7px 8px 7px 0;
|
||||
border-color: transparent var(--UI-Text-Active) transparent transparent;
|
||||
}
|
||||
|
||||
.right.arrowBottom::before {
|
||||
bottom: 16px;
|
||||
left: -8px;
|
||||
transform: translateY(50%);
|
||||
border-width: 7px 8px 7px 0;
|
||||
border-color: transparent var(--UI-Text-Active) transparent transparent;
|
||||
}
|
||||
21
components/TempDesignSystem/Tooltip/variants.ts
Normal file
21
components/TempDesignSystem/Tooltip/variants.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./tooltip.module.css"
|
||||
|
||||
export const tooltipVariants = cva(styles.tooltip, {
|
||||
variants: {
|
||||
position: {
|
||||
left: styles.left,
|
||||
right: styles.right,
|
||||
top: styles.top,
|
||||
bottom: styles.bottom,
|
||||
},
|
||||
arrow: {
|
||||
left: styles.arrowLeft,
|
||||
right: styles.arrowRight,
|
||||
center: styles.arrowCenter,
|
||||
top: styles.arrowTop,
|
||||
bottom: styles.arrowBottom,
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user