Merge branch 'develop' into feature/tracking

This commit is contained in:
Linus Flood
2024-10-30 14:12:24 +01:00
261 changed files with 3397 additions and 2854 deletions

View File

@@ -1,40 +0,0 @@
.container {
display: flex;
flex-direction: column;
color: var(--text-color);
}
.container[data-selected] .checkbox {
border: none;
background: var(--UI-Input-Controls-Fill-Selected);
}
.checkboxContainer {
display: flex;
align-items: flex-start;
gap: var(--Spacing-x-one-and-half);
}
.checkbox {
width: 24px;
height: 24px;
min-width: 24px;
background-color: var(--UI-Input-Controls-Surface-Normal);
border: 2px solid var(--UI-Input-Controls-Border-Normal);
border-radius: var(--Corner-radius-Small);
transition: all 200ms;
display: flex;
align-items: center;
justify-content: center;
transition: all 200ms;
forced-color-adjust: none;
cursor: pointer;
}
.error {
align-items: center;
color: var(--Scandic-Red-60);
display: flex;
gap: var(--Spacing-x-half);
margin-top: var(--Spacing-x1);
}

View File

@@ -1,7 +0,0 @@
import { RegisterOptions } from "react-hook-form"
export interface CheckboxProps
extends React.InputHTMLAttributes<HTMLInputElement> {
name: string
registerOptions?: RegisterOptions
}

View File

@@ -1,49 +0,0 @@
import { Checkbox as AriaCheckbox } from "react-aria-components"
import { useController, useFormContext } from "react-hook-form"
import { InfoCircleIcon } from "@/components/Icons"
import CheckIcon from "@/components/Icons/Check"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import { CheckboxProps } from "./checkbox"
import styles from "./checkbox.module.css"
export default function Checkbox({
name,
children,
registerOptions,
}: React.PropsWithChildren<CheckboxProps>) {
const { control } = useFormContext()
const { field, fieldState } = useController({
control,
name,
rules: registerOptions,
})
return (
<AriaCheckbox
className={styles.container}
isSelected={field.value}
onChange={field.onChange}
data-testid={name}
>
{({ isSelected }) => (
<>
<div className={styles.checkboxContainer}>
<div className={styles.checkbox}>
{isSelected && <CheckIcon color="white" />}
</div>
{children}
</div>
{children && fieldState.error ? (
<Caption className={styles.error}>
<InfoCircleIcon color="red" />
{fieldState.error.message}
</Caption>
) : null}
</>
)}
</AriaCheckbox>
)
}

View File

@@ -16,7 +16,7 @@
.checkboxContainer {
display: flex;
align-items: flex-start;
align-items: center;
gap: var(--Spacing-x-one-and-half);
}

View File

@@ -70,3 +70,7 @@
.listItem:nth-of-type(n + 2) {
margin-top: var(--Spacing-x-quarter);
}
.highlight {
color: var(--Scandic-Brand-Scandic-Red);
}

View File

@@ -34,3 +34,12 @@ export type CheckboxProps =
export type RadioProps =
| Omit<ListCardProps, "type">
| Omit<TextCardProps, "type">
export interface ListProps extends Pick<ListCardProps, "declined"> {
list?: ListCardProps["list"]
}
export interface SubtitleProps
extends Pick<BaseCardProps, "highlightSubtitle" | "subtitle"> {}
export interface TextProps extends Pick<TextCardProps, "text"> {}

View File

@@ -2,16 +2,16 @@
import { useFormContext } from "react-hook-form"
import { CheckIcon, CloseIcon, HeartIcon } from "@/components/Icons"
import { CheckIcon, CloseIcon } 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"
import type { CardProps, ListProps, SubtitleProps, TextProps } from "./card"
export default function Card({
Icon = HeartIcon,
Icon,
iconHeight = 32,
iconWidth = 32,
declined = false,
@@ -26,56 +26,79 @@ export default function Card({
value,
}: CardProps) {
const { register } = useFormContext()
return (
<label className={styles.label} data-declined={declined} tabIndex={0}>
<Caption className={styles.title} type="label" uppercase>
<Caption className={styles.title} color="burgundy" type="label" uppercase>
{title}
</Caption>
{subtitle ? (
<Caption
className={styles.subtitle}
color={highlightSubtitle ? "baseTextAccent" : "uiTextHighContrast"}
type="regular"
>
{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>
<Subtitle highlightSubtitle={highlightSubtitle} subtitle={subtitle} />
{Icon ? (
<Icon
className={styles.icon}
color="uiTextHighContrast"
height={iconHeight}
width={iconWidth}
/>
) : null}
<List declined={declined} list={list} />
<Text text={text} />
<input
{...register(name)}
aria-hidden
id={id || name}
hidden
type={type}
value={value}
{...register(name)}
/>
</label>
)
}
function List({ declined, list }: ListProps) {
if (!list) {
return null
}
return 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>
))
}
function Subtitle({ highlightSubtitle, subtitle }: SubtitleProps) {
if (!subtitle) {
return null
}
return (
<Caption
className={styles.subtitle}
color={highlightSubtitle ? "baseTextAccent" : "uiTextMediumContrast"}
type="label"
uppercase
>
{subtitle}
</Caption>
)
}
function Text({ text }: TextProps) {
if (!text) {
return null
}
return (
<Footnote className={styles.text} color="uiTextMediumContrast">
{text}
</Footnote>
)
}
export function Highlight({ children }: React.PropsWithChildren) {
return <span className={styles.highlight}>{children}</span>
}

View File

@@ -0,0 +1,7 @@
import Chip from "./_Chip"
import type { FilterChipCheckboxProps } from "@/types/components/form/filterChip"
export default function CheckboxChip(props: FilterChipCheckboxProps) {
return <Chip {...props} type="checkbox" />
}

View File

@@ -0,0 +1,37 @@
.label {
display: flex;
align-items: center;
gap: var(--Spacing-x-half);
padding: var(--Spacing-x1) var(--Spacing-x-one-and-half);
border: 1px solid var(--Base-Border-Subtle);
border-radius: var(--Corner-radius-Small);
background-color: var(--Base-Surface-Secondary-light-Normal);
cursor: pointer;
}
.label[data-selected="true"],
.label[data-selected="true"]:hover {
background-color: var(--Primary-Light-Surface-Normal);
border-color: var(--Base-Border-Hover);
}
.label:hover {
background-color: var(--Base-Surface-Primary-light-Hover-alt);
border-color: var(--Base-Border-Subtle);
}
.label[data-disabled="true"] {
background-color: var(--Base-Button-Primary-Fill-Disabled);
border-color: var(--Base-Button-Primary-Fill-Disabled);
cursor: not-allowed;
}
.caption {
display: none;
}
@media (min-width: 768px) {
.caption {
display: block;
}
}

View File

@@ -0,0 +1,57 @@
import { useMemo } from "react"
import { useFormContext } from "react-hook-form"
import { HeartIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./chip.module.css"
import { FilterChipProps } from "@/types/components/form/filterChip"
export default function FilterChip({
Icon = HeartIcon,
iconHeight = 20,
iconWidth = 20,
id,
name,
label,
type,
value,
selected,
disabled,
}: FilterChipProps) {
const { register } = useFormContext()
const color = useMemo(() => {
if (selected) return "burgundy"
if (disabled) return "disabled"
return "uiTextPlaceholder"
}, [selected, disabled])
return (
<label
className={styles.label}
data-selected={selected}
data-disabled={disabled}
>
<Icon
className={styles.icon}
color={color}
height={iconHeight}
width={iconWidth}
/>
<Caption type="bold" color={color} className={styles.caption}>
{label}
</Caption>
<input
aria-hidden
id={id || name}
hidden
type={type}
value={value}
disabled={disabled}
{...register(name)}
/>
</label>
)
}

View File

@@ -91,7 +91,7 @@ export default function Phone({
<Label required={!!registerOptions.required} size="small">
{formatMessage({ id: "Country code" })}
</Label>
<div className={styles.selectContainer}>
<span className={styles.selectContainer}>
{props.children}
<Body asChild fontOnly>
<DialCodePreview
@@ -106,7 +106,7 @@ export default function Phone({
height={18}
width={18}
/>
</div>
</span>
</button>
)}
/>

View File

@@ -34,6 +34,7 @@ export default function Select({
required = false,
tabIndex,
value,
maxHeight,
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
@@ -60,12 +61,12 @@ export default function Select({
>
<Body asChild fontOnly>
<Button className={styles.input} data-testid={name}>
<div className={styles.inputContentWrapper} tabIndex={tabIndex}>
<span className={styles.inputContentWrapper} tabIndex={tabIndex}>
<Label required={required} size="small">
{label}
</Label>
<SelectValue />
</div>
</span>
<SelectChevron />
</Button>
</Body>
@@ -81,6 +82,7 @@ export default function Select({
* on the container as well as to not overflow it at any time.
*/
UNSTABLE_portalContainer={rootDiv}
maxHeight={maxHeight}
>
<ListBox className={styles.listBox}>
{items.map((item) => (

View File

@@ -9,6 +9,7 @@ export interface SelectProps
onSelect: (key: Key) => void
placeholder?: string
value?: string | number
maxHeight?: number
}
export type SelectPortalContainer = HTMLDivElement | undefined

View File

@@ -15,6 +15,7 @@
opacity: 0;
transition: opacity 0.3s;
max-width: 200px;
min-width: 150px;
}
.tooltipContainer:hover .tooltip {
@@ -31,11 +32,15 @@
}
.top {
bottom: 100%;
bottom: calc(100% + 8px);
}
.bottom {
top: 100%;
top: calc(100% + 8px);
}
.bottom.arrowRight {
right: 0;
}
.tooltip::before {