Merge branch 'develop' into feature/tracking

This commit is contained in:
Linus Flood
2024-10-08 15:13:16 +02:00
178 changed files with 3745 additions and 1291 deletions

View File

@@ -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

View File

@@ -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
}

View File

@@ -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}

View File

@@ -0,0 +1,7 @@
import Card from "."
import type { CheckboxProps } from "./card"
export default function CheckboxCard(props: CheckboxProps) {
return <Card {...props} type="checkbox" />
}

View File

@@ -0,0 +1,7 @@
import Card from "."
import type { RadioProps } from "./card"
export default function RadioCard(props: RadioProps) {
return <Card {...props} type="radio" />
}

View 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);
}

View 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">

View 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>
)
}

View File

@@ -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
}

View File

@@ -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}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -0,0 +1,4 @@
export interface AriaInputWithLabelProps
extends React.InputHTMLAttributes<HTMLInputElement> {
label: string
}

View File

@@ -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">

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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 {

View File

@@ -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">

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -1,5 +1,3 @@
import React from "react"
import { ChevronRightIcon } from "@/components/Icons"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"

View File

@@ -92,6 +92,10 @@
color: var(--Base-Text-Medium-contrast);
}
.uiTextHighContrast {
color: var(--UI-Text-High-contrast);
}
.uiTextPlaceholder {
color: var(--UI-Text-Placeholder);
}

View File

@@ -15,6 +15,7 @@ const config = {
white: styles.white,
peach50: styles.peach50,
peach80: styles.peach80,
uiTextHighContrast: styles.uiTextHighContrast,
uiTextPlaceholder: styles.uiTextPlaceholder,
},
textAlign: {

View File

@@ -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;
}

View File

@@ -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} />
}

View File

@@ -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",

View File

@@ -59,7 +59,7 @@
color: var(--Scandic-Peach-50);
}
.textMediumContrast {
.uiTextMediumContrast {
color: var(--UI-Text-Medium-contrast);
}

View File

@@ -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: {

View File

@@ -58,3 +58,7 @@
.pale {
color: var(--Scandic-Brand-Pale-Peach);
}
.uiTextHighContrast {
color: var(--UI-Text-High-contrast);
}

View File

@@ -8,6 +8,7 @@ const config = {
black: styles.black,
burgundy: styles.burgundy,
pale: styles.pale,
uiTextHighContrast: styles.uiTextHighContrast,
},
textAlign: {
center: styles.center,

View 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>
)
}

View 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;
}

View 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,
},
},
})