Merge branch 'develop' into feature/tracking
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { RegisterOptions } from "react-hook-form"
|
||||
|
||||
export interface CheckboxProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
name: string
|
||||
registerOptions?: RegisterOptions
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
.checkboxContainer {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
|
||||
@@ -70,3 +70,7 @@
|
||||
.listItem:nth-of-type(n + 2) {
|
||||
margin-top: var(--Spacing-x-quarter);
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
@@ -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"> {}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
7
components/TempDesignSystem/Form/FilterChip/Checkbox.tsx
Normal file
7
components/TempDesignSystem/Form/FilterChip/Checkbox.tsx
Normal 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" />
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
57
components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx
Normal file
57
components/TempDesignSystem/Form/FilterChip/_Chip/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -9,6 +9,7 @@ export interface SelectProps
|
||||
onSelect: (key: Key) => void
|
||||
placeholder?: string
|
||||
value?: string | number
|
||||
maxHeight?: number
|
||||
}
|
||||
|
||||
export type SelectPortalContainer = HTMLDivElement | undefined
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user