feat(WEB-162): final design edit profile page

This commit is contained in:
Simon Emanuelsson
2024-06-18 08:15:57 +02:00
committed by Christel Westerberg
parent 5f3e417593
commit d84efcbb0f
81 changed files with 1538 additions and 711 deletions

View File

@@ -0,0 +1,12 @@
.password,
.user {
align-self: flex-start;
display: grid;
gap: var(--Spacing-x2);
}
.container {
display: grid;
gap: var(--Spacing-x2);
grid-template-columns: max(164px) 1fr;
}

View File

@@ -0,0 +1,104 @@
"use client"
// import { useFormStatus } from "react-dom"
import { useIntl } from "react-intl"
import CountrySelect from "@/components/TempDesignSystem/Form/Country"
import DateSelect from "@/components/TempDesignSystem/Form/Date"
import Input from "@/components/TempDesignSystem/Form/Input"
import Phone from "@/components/TempDesignSystem/Form/Phone"
import Select from "@/components/TempDesignSystem/Form/Select"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./formContent.module.css"
const languages = [
{ label: "Danish", value: "Da" },
{ label: "German", value: "De" },
{ label: "English", value: "En" },
{ label: "Finnish", value: "Fi" },
{ label: "Norwegian", value: "No" },
{ label: "Swedish", value: "Sv" },
]
export default function FormContent() {
const { formatMessage } = useIntl()
// const { pending } = useFormStatus()
const city = formatMessage({ id: "City" })
const country = formatMessage({ id: "Country" })
const email = `${formatMessage({ id: "Email" })} ${formatMessage({ id: "Address" }).toLowerCase()}`
const street = formatMessage({ id: "Address" })
const password = formatMessage({ id: "Current password" })
const newPassword = formatMessage({ id: "New password" })
const retypeNewPassword = formatMessage({ id: "Retype new password" })
const zipCode = formatMessage({ id: "Zip code" })
return (
<>
<section className={styles.user}>
<header>
<Body textTransform="bold">
{formatMessage({ id: "User information" })}
</Body>
</header>
<DateSelect name="dateOfBirth" registerOptions={{ required: true }} />
<Input
label={`${street} 1`}
name="address.streetAddress"
placeholder={street}
/>
<Input label={city} name="address.city" placeholder={city} />
<div className={styles.container}>
<Input
label={zipCode}
name="address.zipCode"
placeholder={zipCode}
required
/>
<CountrySelect
label={country}
name="address.countryCode"
placeholder={country}
registerOptions={{ required: true }}
/>
</div>
<Input label={email} name="email" placeholder={email} required />
<Phone
label={formatMessage({ id: "Phone number" })}
name="phoneNumber"
placeholder={formatMessage({ id: "Phone number" })}
registerOptions={{ required: true }}
/>
<Select
items={languages}
label={formatMessage({ id: "Language" })}
name="language"
placeholder={formatMessage({ id: "Select language" })}
/>
</section>
<section className={styles.password}>
<header>
<Body textTransform="bold">{formatMessage({ id: "Password" })}</Body>
</header>
<Input
label={password}
name="currentPassword"
placeholder={password}
type="password"
/>
<Input
label={newPassword}
name="newPassword"
placeholder={newPassword}
type="password"
/>
<Input
label={retypeNewPassword}
name="retypeNewPassword"
placeholder={retypeNewPassword}
type="password"
/>
</section>
</>
)
}

View File

@@ -0,0 +1,10 @@
.form {
display: grid;
gap: var(--Spacing-x5);
grid-template-columns: 1fr 1fr;
}
.btns {
display: flex;
gap: var(--Spacing-x-half);
}

View File

@@ -0,0 +1,94 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useFormState as useReactFormState } from "react-dom"
import { FormProvider, useForm } from "react-hook-form"
import { useIntl } from "react-intl"
import { editProfile } from "@/actions/editProfile"
import Header from "@/components/Profile/Header"
import Button from "@/components/TempDesignSystem/Button"
import Divider from "@/components/TempDesignSystem/Divider"
import Title from "@/components/TempDesignSystem/Text/Title"
import FormContent from "./FormContent"
import { type EditProfileSchema, editProfileSchema } from "./schema"
import styles from "./form.module.css"
import type {
EditFormProps,
State,
} from "@/types/components/myPages/myProfile/edit"
const formId = "edit-profile"
export default function Form({ user }: EditFormProps) {
const { formatMessage } = useIntl()
/**
* like react, react-hook-form also exports a useFormState hook,
* we want to clearly keep them separate by naming.
*/
const [state, formAction] = useReactFormState<State, FormData>(
editProfile,
null
)
const form = useForm<EditProfileSchema>({
defaultValues: {
"address.city": user.address.city ?? "",
"address.countryCode": user.address.countryCode ?? "",
"address.streetAddress": user.address.streetAddress ?? "",
"address.zipCode": user.address.zipCode ?? "",
dateOfBirth: user.dateOfBirth,
email: user.email,
language: user.language,
phoneNumber: user.phoneNumber,
currentPassword: "",
newPassword: "",
retypeNewPassword: "",
},
mode: "all",
resolver: zodResolver(editProfileSchema),
reValidateMode: "onChange",
})
return (
<FormProvider {...form}>
<Header>
<hgroup>
<Title as="h4" color="peach80" level="h1">
{formatMessage({ id: "Edit" })}
</Title>
<Title as="h4" color="burgundy" level="h2">
{user.name}
</Title>
</hgroup>
<div className={styles.btns}>
<Button
form={formId}
intent="primary"
size="small"
theme="primaryDark"
type="reset"
>
{formatMessage({ id: "Discard changes" })}
</Button>
<Button
disabled={!form.formState.isValid || form.formState.isSubmitting}
form={formId}
intent="secondary"
size="small"
theme="base"
type="submit"
>
{formatMessage({ id: "Save" })}
</Button>
</div>
</Header>
<Divider color="burgundy" opacity={8} />
<form action={formAction} className={styles.form} id={formId}>
<FormContent />
</form>
</FormProvider>
)
}

View File

@@ -0,0 +1,24 @@
import { z } from "zod"
// import { phoneValidator } from "@/utils/phoneValidator"
export const editProfileSchema = z.object({
"address.city": z.string().optional(),
"address.countryCode": z.string().min(1),
"address.streetAddress": z.string().optional(),
"address.zipCode": z.string().min(1),
dateOfBirth: z.string().min(1),
email: z.string().email(),
language: z.string(),
phoneNumber: z.string(),
// phoneValidator(
// "Phone is required",
// "Please enter a valid phone number"
// ),
currentPassword: z.string().optional(),
newPassword: z.string().optional(),
retypeNewPassword: z.string().optional(),
})
export type EditProfileSchema = z.infer<typeof editProfileSchema>

View File

@@ -12,27 +12,27 @@ export default function ChevronDownIcon({
<svg
className={classNames}
fill="none"
height="24"
viewBox="0 0 24 24"
width="24"
height="20"
viewBox="0 0 20 20"
width="20"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
height="24"
id="mask0_553_6963"
height="20"
id="mask0_4971_13121"
maskUnits="userSpaceOnUse"
style={{ maskType: "alpha" }}
width="24"
width="20"
x="0"
y="0"
>
<rect width="24" height="24" fill="#D9D9D9" />
<rect width="20" height="20" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_553_6963)">
<g mask="url(#mask0_4971_13121)">
<path
d="M12 15.3746L6 9.37461L7.4 7.97461L12 12.5746L16.6 7.97461L18 9.37461L12 15.3746Z"
fill="#757575"
d="M10.422 11.9374L16.2449 6.1145C16.4254 5.93395 16.6459 5.84193 16.9063 5.83846C17.1668 5.83499 17.3873 5.92353 17.5678 6.10409C17.7484 6.28464 17.8386 6.50513 17.8386 6.76554C17.8386 7.02596 17.7484 7.24645 17.5678 7.427L11.4011 13.6041C11.2553 13.7499 11.1025 13.8523 10.9428 13.9114C10.7831 13.9704 10.6095 13.9999 10.422 13.9999C10.2345 13.9999 10.0609 13.9704 9.90114 13.9114C9.74142 13.8523 9.58864 13.7499 9.44281 13.6041L3.27614 7.43742C3.09558 7.25686 3.00357 7.03464 3.0001 6.77075C2.99663 6.50686 3.08517 6.28464 3.26572 6.10409C3.44628 5.92353 3.66676 5.83325 3.92718 5.83325C4.1876 5.83325 4.40808 5.92353 4.58864 6.10409L10.422 11.9374Z"
fill="#4D001B"
/>
</g>
</svg>

View File

@@ -0,0 +1,40 @@
import { iconVariants } from "./variants"
import type { IconProps } from "@/types/components/icon"
export default function InfoCircleIcon({
className,
color,
...props
}: IconProps) {
const classNames = iconVariants({ className, color })
return (
<svg
className={classNames}
fill="none"
height="17"
viewBox="0 0 16 17"
width="16"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<mask
height="17"
id="mask0_954_4761"
maskUnits="userSpaceOnUse"
style={{ maskType: "alpha" }}
width="16"
x="0"
y="0"
>
<rect y="0.5" width="16" height="16" fill="#D9D9D9" />
</mask>
<g mask="url(#mask0_954_4761)">
<path
d="M8.00833 11.75C8.18056 11.75 8.32778 11.6889 8.45 11.5667C8.57222 11.4444 8.63333 11.2972 8.63333 11.125V8.475C8.63333 8.30278 8.57222 8.15556 8.45 8.03333C8.32778 7.91111 8.18056 7.85 8.00833 7.85C7.83611 7.85 7.68889 7.91111 7.56667 8.03333C7.44444 8.15556 7.38333 8.30278 7.38333 8.475V11.125C7.38333 11.2972 7.44444 11.4444 7.56667 11.5667C7.68889 11.6889 7.83611 11.75 8.00833 11.75ZM7.99825 6.56667C8.17719 6.56667 8.32778 6.50614 8.45 6.38508C8.57222 6.26404 8.63333 6.11404 8.63333 5.93508C8.63333 5.75614 8.57281 5.60556 8.45175 5.48333C8.33071 5.36111 8.18071 5.3 8.00175 5.3C7.82281 5.3 7.67222 5.36053 7.55 5.48158C7.42778 5.60263 7.36667 5.75263 7.36667 5.93158C7.36667 6.11053 7.42719 6.26111 7.54825 6.38333C7.66929 6.50556 7.81929 6.56667 7.99825 6.56667ZM8 15C7.10103 15 6.25623 14.8291 5.46558 14.4873C4.67493 14.1455 3.98717 13.6816 3.4023 13.0956C2.81743 12.5097 2.35417 11.8217 2.0125 11.0319C1.67083 10.242 1.5 9.39806 1.5 8.5C1.5 7.60103 1.67091 6.75623 2.01272 5.96558C2.35453 5.17493 2.81842 4.48717 3.40438 3.9023C3.99035 3.31743 4.67826 2.85417 5.46812 2.5125C6.25798 2.17083 7.10194 2 8 2C8.89897 2 9.74377 2.17091 10.5344 2.51272C11.3251 2.85453 12.0128 3.31842 12.5977 3.90438C13.1826 4.49035 13.6458 5.17826 13.9875 5.96812C14.3292 6.75798 14.5 7.60194 14.5 8.5C14.5 9.39897 14.3291 10.2438 13.9873 11.0344C13.6455 11.8251 13.1816 12.5128 12.5956 13.0977C12.0097 13.6826 11.3217 14.1458 10.5319 14.4875C9.74202 14.8292 8.89806 15 8 15Z"
fill="#CD0921"
/>
</g>
</svg>
)
}

View File

@@ -1,6 +1,5 @@
.icon {
height: 20px;
width: 20px;
margin: 0;
}
.black,
@@ -13,6 +12,11 @@
fill: var(--Scandic-Brand-Burgundy);
}
.grey80,
.grey80 * {
fill: var(--UI-Grey-80);
}
.pale,
.pale * {
fill: var(--Scandic-Brand-Pale-Peach);

View File

@@ -9,6 +9,7 @@ export { default as ChevronRightIcon } from "./ChevronRight"
export { default as EmailIcon } from "./Email"
export { default as GlobeIcon } from "./Globe"
export { default as HouseIcon } from "./House"
export { default as InfoCircleIcon } from "./InfoCircle"
export { default as LocationIcon } from "./Location"
export { default as LockIcon } from "./Lock"
export { default as PhoneIcon } from "./Phone"

View File

@@ -7,6 +7,7 @@ const config = {
color: {
black: styles.black,
burgundy: styles.burgundy,
grey80: styles.grey80,
pale: styles.pale,
peach80: styles.peach80,
primaryLightOnSurfaceAccent: styles.plosa,

View File

@@ -8,7 +8,7 @@ import { Lang } from "@/constants/languages"
import { membershipLevels } from "@/constants/membershipLevels"
import Image from "@/components/Image"
import Select from "@/components/TempDesignSystem/Form/Select"
import Select from "@/components/TempDesignSystem/Select"
import { getMembership } from "@/utils/user"
import levelsData from "./data/EN.json"

View File

@@ -7,7 +7,7 @@ import MobileTable from "./Mobile"
import type {
ClientEarnAndBurnProps,
TransactionsObject,
TransactionsNonNullResponseObject,
} from "@/types/components/myPages/myPage/earnAndBurn"
export default function ClientEarnAndBurn({
@@ -34,7 +34,7 @@ export default function ClientEarnAndBurn({
// later on when we handle errors appropriately.
const filteredTransactions = (data?.pages.filter(
(page) => page && page.data
) ?? []) as unknown as TransactionsObject[]
) ?? []) as unknown as TransactionsNonNullResponseObject[]
const transactions = filteredTransactions.flatMap((page) => page.data)
return (
<>

View File

@@ -56,13 +56,11 @@ export default function ClientPreviousStays({
stay={stay}
/>
))}
</Grids.Stackable >
{
hasNextPage ? (
<ShowMoreButton disabled={isFetching} loadMoreData={loadMoreData} />
) : null
}
</ListContainer >
</Grids.Stackable>
{hasNextPage ? (
<ShowMoreButton disabled={isFetching} loadMoreData={loadMoreData} />
) : null}
</ListContainer>
) : (
<EmptyPreviousStaysBlock />
)

View File

@@ -1,27 +0,0 @@
"use client"
import { useEffect } from "react"
import { useFormStatus } from "react-dom"
// import { useWatch } from "react-hook-form"
// import { useIntl } from "react-intl"
import { useProfileStore } from "@/stores/edit-profile"
import type { EditFormContentProps } from "@/types/components/myPages/myProfile/edit"
export default function FormContent({ control }: EditFormContentProps) {
// const { formatMessage } = useIntl()
const { pending } = useFormStatus()
const setIsPending = useProfileStore((store) => store.setIsPending)
// const country = useWatch({ name: "address.country" })
useEffect(() => {
setIsPending(pending)
}, [pending, setIsPending])
// const city = formatMessage({ id: "City" })
// const email = formatMessage({ id: "Email" })
// const street = formatMessage({ id: "Street" })
// const zipCode = formatMessage({ id: "Zip code" })
return <></>
}

View File

@@ -1,5 +0,0 @@
.form {
display: grid;
gap: var(--Spacing-x2);
grid-template-columns: 1fr 1fr;
}

View File

@@ -1,54 +0,0 @@
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useEffect } from "react"
import { useFormState as useReactFormState } from "react-dom"
import { FormProvider, useForm } from "react-hook-form"
import { useProfileStore } from "@/stores/edit-profile"
import { editProfile } from "@/actions/editProfile"
import FormContent from "./Content"
import { type EditProfileSchema, editProfileSchema } from "./schema"
import styles from "./form.module.css"
import {
type EditFormProps,
type State,
} from "@/types/components/myPages/myProfile/edit"
export default function Form({ user }: EditFormProps) {
const isValid = useProfileStore((store) => store.valid)
const setValid = useProfileStore((store) => store.setValid)
/**
* like react, react-hook-form also exports a useFormState hook,
* we want to clearly keep them separate by naming.
*/
const [state, formAction] = useReactFormState<State, FormData>(
editProfile,
null
)
const form = useForm<EditProfileSchema>({
defaultValues: user,
criteriaMode: "all",
mode: "onTouched",
resolver: zodResolver(editProfileSchema),
reValidateMode: "onChange",
})
useEffect(() => {
if (isValid !== form.formState.isValid) {
setValid(form.formState.isValid)
}
}, [form.formState.isValid, isValid, setValid])
return (
<FormProvider {...form}>
<form action={formAction} className={styles.form} id="edit-profile">
<FormContent control={form.control} />
</form>
</FormProvider>
)
}

View File

@@ -1,18 +0,0 @@
import { z } from "zod"
import { phoneValidator } from "@/utils/phoneValidator"
export const editProfileSchema = z.object({
"address.country": z.string().min(1),
"address.city": z.string().optional(),
"address.streetAddress": z.string().optional(),
"address.zipCode": z.string().min(1),
dateOfBirth: z.string().min(1),
email: z.string().email(),
phoneNumber: phoneValidator(
"Phone is required",
"Please enter a valid phone number"
),
})
export type EditProfileSchema = z.infer<typeof editProfileSchema>

View File

@@ -1,11 +0,0 @@
import { serverClient } from "@/lib/trpc/server"
import Form from "./Form"
export default async function EditProfile() {
const user = await serverClient().user.get()
if (!user) {
return null
}
return <Form user={user} />
}

View File

@@ -0,0 +1,6 @@
.header {
align-items: center;
display: flex;
gap: var(--Spacing-x2);
justify-content: space-between;
}

View File

@@ -0,0 +1,5 @@
import styles from "./header.module.css"
export default function Header({ children }: React.PropsWithChildren) {
return <header className={styles.header}>{children}</header>
}

View File

@@ -1,167 +1,411 @@
.btn {
background: none;
border: none;
/* No variable yet for radius 50px */
border-radius: 50px;
cursor: pointer;
/* TODO: Waiting for variables for buttons from Design team */
font-family: var(--typography-Body-Regular-fontFamily);
font-weight: 600;
line-height: 150%;
letter-spacing: 1%;
margin: 0;
padding: 0;
text-align: center;
transition:
background-color 300ms ease,
color 300ms ease;
/* TODO: Waiting for variables for buttons from Design team */
font-family: var(--typography-Body-Bold-fontFamily);
font-size: var(--typography-Body-Bold-fontSize);
font-weight: var(--typography-Body-Bold-fontWeight);
line-height: 24px;
letter-spacing: 0.6%;
}
/* INTENT */
.primary,
a.primary {
background-color: var(--background-color);
color: var(--font-color);
}
.primary:hover,
a.primary:hover,
.primary:active,
a.primary:active,
.primary:focus,
a.primary:focus {
background-color: var(--hover-background);
color: var(--hover-color);
border: none;
}
.secondary,
a.secondary {
background-color: transparent;
border: 1px solid var(--background-color);
color: var(--background-color);
background: none;
border-style: solid;
border-width: 2px;
}
.secondary:hover,
a.secondary:hover,
.secondary:active,
a.secondary:active,
.secondary:focus,
a.secondary:focus {
border: 1px solid var(--hover-color);
color: var(--hover-color);
.tertiary,
a.tertiary {
border: none;
}
.inverted,
a.inverted {
border: none;
}
/* VARIANTS */
.default,
a.default {
align-items: center;
border-radius: 50px;
/* No variable yet */
display: flex;
gap: var(--Spacing-x1);
}
.icon {
align-items: baseline;
font-size: 18px;
}
/* Disabled styles */
/* SIZES */
.small {
gap: var(--Spacing-x-quarter);
height: 40px;
padding: var(--Spacing-x1) var(--Spacing-x2);
}
.medium {
gap: var(--Spacing-x-quarter);
height: 48px;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
}
.large {
gap: var(--Spacing-x-half);
height: 56px;
padding: var(--Spacing-x2) var(--Spacing-x3);
}
/* DISABLED */
.btn:disabled {
background-color: var(--disabled-background-color);
color: var(--disabled-color);
cursor: not-allowed;
}
/* Sizes */
.small {
font-size: 14px;
gap: var(--Spacing-x2);
height: 40px;
padding: var(--Spacing-x1) var(--Spacing-x2);
/* THEMES */
.basePrimary {
background-color: var(--Base-Button-Primary-Fill-Normal);
color: var(--Base-Button-Primary-On-Fill-Normal);
}
.medium {
font-size: 16px;
height: 30px;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
.basePrimary:active,
.basePrimary:focus,
.basePrimary:hover {
background-color: var(--Base-Button-Primary-Fill-Hover);
color: var(--Base-Button-Primary-On-Fill-Hover);
}
.large {
font-size: 16px;
height: 50px;
gap: var(--Spacing-x-half);
padding: var(--Spacing-x2) var(--Spacing-x3);
.basePrimary:disabled {
background-color: var(--Base-Button-Primary-Fill-Disabled);
color: var(--Base-Button-Primary-On-Fill-Disabled);
}
.primaryLight {
--font-color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Normal);
--background-color: var(--Theme-Primary-Light-Button-Primary-Fill-Normal);
--hover-background: var(--Theme-Primary-Light-Button-Primary-Fill-Hover);
--hover-color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Hover);
--disabled-background-color: var(
--Theme-Primary-Light-Button-Primary-Fill-Disabled
);
--disabled-color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Disabled);
.baseSecondary {
background-color: var(--Base-Button-Secondary-Fill-Normal);
border-color: var(--Base-Button-Secondary-Border-Normal);
color: var(--Base-Button-Secondary-On-Fill-Normal);
}
.primaryDark {
--font-color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Normal);
--background-color: var(--Theme-Primary-Dark-Button-Primary-Fill-Normal);
--hover-color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Hover);
--hover-background: var(--Theme-Primary-Dark-Button-Primary-Fill-Hover);
--disabled-background-color: var(
--Theme-Primary-Dark-Button-Primary-Fill-Disabled
);
--disabled-color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Disabled);
.baseSecondary:active,
.baseSecondary:focus,
.baseSecondary:hover {
background-color: var(--Base-Button-Secondary-Fill-Hover);
border-color: var(--Base-Button-Secondary-Border-Hover);
color: var(--Base-Button-Secondary-On-Fill-Hover);
}
.primaryStrong {
--background-color: var(--Theme-Primary-Strong-Button-Primary-Fill-Normal);
--disabled-background-color: var(
--Theme-Primary-Strong-Button-Primary-Fill-Disabled
);
--disabled-color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Disabled);
--font-color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Normal);
--hover-background: var(--Theme-Primary-Strong-Button-Primary-Fill-Hover);
--hover-color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Hover);
.baseSecondary:disabled {
background-color: var(--Base-Button-Secondary-Fill-Disabled);
border-color: var(--Base-Button-Secondary-Border-Disabled);
color: var(--Base-Button-Secondary-On-Fill-Disabled);
}
.secondaryLight {
--font-color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Normal);
--background-color: var(--Theme-Secondary-Light-Button-Primary-Fill-Normal);
--hover-color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Hover);
--hover-background: var(--Theme-Secondary-Light-Button-Primary-Fill-Hover);
--disabled-background-color: var(
--Theme-Secondary-Light-Button-Primary-Fill-Disabled
);
--disabled-color: var(
--Theme-Secondary-Light-Button-Primary-On-Fill-Disabled
);
.baseTertiary {
background-color: var(--Base-Button-Tertiary-Fill-Normal);
color: var(--Base-Button-Tertiary-On-Fill-Normal);
}
.secondaryDark {
--font-color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Normal);
--background-color: var(--Theme-Secondary-Dark-Button-Primary-Fill-Normal);
--hover-color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Hover);
--hover-background: var(--Theme-Secondary-Dark-Button-Primary-Fill-Hover);
--disabled-background-color: var(
--Theme-Secondary-Dark-Button-Primary-Fill-Disabled
);
--disabled-color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Disabled);
.baseTertiary:active,
.baseTertiary:focus,
.baseTertiary:hover {
background-color: var(--Base-Button-Tertiary-Fill-Hover);
color: var(--Base-Button-Tertiary-On-Fill-Hover);
}
.tertiaryLight {
--font-color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Normal);
--background-color: var(--Theme-Tertiary-Light-Button-Primary-Fill-Normal);
--hover-color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Hover);
--hover-background: var(--Theme-Tertiary-Light-Button-Primary-Fill-Hover);
--disabled-background-color: var(
--Theme-Tertiary-Light-Button-Primary-Fill-Disabled
);
--disabled-color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Disabled);
.baseTertiary:disabled {
background-color: var(--Base-Button-Tertiary-Fill-Disabled);
color: var(--Base-Button-Tertiary-On-Fill-Disabled);
}
.tertiaryDark {
--font-color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Normal);
--background-color: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Normal);
--hover-color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Hover);
--hover-background: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Hover);
--disabled-background-color: var(
--Theme-Tertiary-Dark-Button-Primary-Fill-Disabled
);
--disabled-color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Disabled);
.baseInverted {
background-color: var(--Base-Button-Inverted-Fill-Normal);
color: var(--Base-Button-Inverted-On-Fill-Normal);
}
.baseInverted:active,
.baseInverted:focus,
.baseInverted:hover {
background-color: var(--Base-Button-Inverted-Fill-Hover);
color: var(--Base-Button-Inverted-On-Fill-Hover);
}
.baseInverted:disabled {
background-color: var(--Base-Button-Inverted-Fill-Disabled);
color: var(--Base-Button-Inverted-On-Fill-Disabled);
}
.primaryStrongPrimary {
background-color: var(--Theme-Primary-Strong-Button-Primary-Fill-Normal);
color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Normal);
}
.primaryStrongPrimary:active,
.primaryStrongPrimary:focus,
.primaryStrongPrimary:hover {
background-color: var(--Theme-Primary-Strong-Button-Primary-Fill-Hover);
color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Hover);
}
.primaryStrongPrimary:disabled {
background-color: var(--Theme-Primary-Strong-Button-Primary-Fill-Disabled);
color: var(--Theme-Primary-Strong-Button-Primary-On-Fill-Disabled);
}
.primaryStrongSecondary {
background-color: var(--Theme-Primary-Strong-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Primary-Strong-Button-Secondary-Border-Normal);
color: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Normal);
}
.primaryStrongSecondary:active,
.primaryStrongSecondary:focus,
.primaryStrongSecondary:hover {
background-color: var(--Theme-Primary-Strong-Button-Secondary-Fill-Hover);
border-color: var(--Theme-Primary-Strong-Button-Secondary-Border-Hover);
color: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Hover);
}
.primaryStrongSecondary:disabled {
background-color: var(--Theme-Primary-Strong-Button-Secondary-Fill-Disabled);
border-color: var(--Theme-Primary-Strong-Button-Secondary-Border-Disabled);
color: var(--Theme-Primary-Strong-Button-Secondary-On-Fill-Disabled);
}
.primaryDarkPrimary {
background-color: var(--Theme-Primary-Dark-Button-Primary-Fill-Normal);
color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Normal);
}
.primaryDarkPrimary:active,
.primaryDarkPrimary:focus,
.primaryDarkPrimary:hover {
background-color: var(--Theme-Primary-Dark-Button-Primary-Fill-Hover);
color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Hover);
}
.primaryDarkPrimary:disabled {
background-color: var(--Theme-Primary-Dark-Button-Primary-Fill-Disabled);
color: var(--Theme-Primary-Dark-Button-Primary-On-Fill-Disabled);
}
.primaryDarkSecondary {
background-color: var(--Theme-Primary-Dark-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Primary-Dark-Button-Secondary-Border-Normal);
color: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Normal);
}
.primaryDarkSecondary:active,
.primaryDarkSecondary:focus,
.primaryDarkSecondary:hover {
background-color: var(--Theme-Primary-Dark-Button-Secondary-Fill-Hover);
border-color: var(--Theme-Primary-Dark-Button-Secondary-Border-Hover);
color: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Hover);
}
.primaryDarkSecondary:disabled {
background-color: var(--Theme-Primary-Dark-Button-Secondary-Fill-Disabled);
border-color: var(--Theme-Primary-Dark-Button-Secondary-Border-Disabled);
color: var(--Theme-Primary-Dark-Button-Secondary-On-Fill-Disabled);
}
.primaryLightPrimary {
background-color: var(--Theme-Primary-Light-Button-Primary-Fill-Normal);
color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Normal);
}
.primaryLightPrimary:active,
.primaryLightPrimary:focus,
.primaryLightPrimary:hover {
background-color: var(--Theme-Primary-Light-Button-Primary-Fill-Hover);
color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Hover);
}
.primaryLightPrimary:disabled {
background-color: var(--Theme-Primary-Light-Button-Primary-Fill-Disabled);
color: var(--Theme-Primary-Light-Button-Primary-On-Fill-Disabled);
}
.primaryLightSecondary {
background-color: var(--Theme-Primary-Light-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Primary-Light-Button-Secondary-Border-Normal);
color: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Normal);
}
.primaryLightSecondary:active,
.primaryLightSecondary:focus,
.primaryLightSecondary:hover {
background-color: var(--Theme-Primary-Light-Button-Secondary-Fill-Hover);
border-color: var(--Theme-Primary-Light-Button-Secondary-Border-Hover);
color: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Hover);
}
.primaryLightSecondary:disabled {
background-color: var(--Theme-Primary-Light-Button-Secondary-Fill-Disabled);
border-color: var(--Theme-Primary-Light-Button-Secondary-Border-Disabled);
color: var(--Theme-Primary-Light-Button-Secondary-On-Fill-Disabled);
}
.secondaryDarkPrimary {
background-color: var(--Theme-Secondary-Dark-Button-Primary-Fill-Normal);
color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Normal);
}
.secondaryDarkPrimary:active,
.secondaryDarkPrimary:focus,
.secondaryDarkPrimary:hover {
background-color: var(--Theme-Secondary-Dark-Button-Primary-Fill-Hover);
color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Hover);
}
.secondaryDarkPrimary:disabled {
background-color: var(--Theme-Secondary-Dark-Button-Primary-Fill-Disabled);
color: var(--Theme-Secondary-Dark-Button-Primary-On-Fill-Disabled);
}
.secondaryDarkSecondary {
background-color: var(--Theme-Secondary-Dark-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Secondary-Dark-Button-Secondary-Border-Normal);
color: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Normal);
}
.secondaryDarkSecondary:active,
.secondaryDarkSecondary:focus,
.secondaryDarkSecondary:hover {
background-color: var(--Theme-Secondary-Dark-Button-Secondary-Fill-Hover);
border-color: var(--Theme-Secondary-Dark-Button-Secondary-Border-Hover);
color: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Hover);
}
.secondaryDarkSecondary:disabled {
background-color: var(--Theme-Secondary-Dark-Button-Secondary-Fill-Disabled);
border-color: var(--Theme-Secondary-Dark-Button-Secondary-Border-Disabled);
color: var(--Theme-Secondary-Dark-Button-Secondary-On-Fill-Disabled);
}
.secondaryLightPrimary {
background-color: var(--Theme-Secondary-Light-Button-Primary-Fill-Normal);
color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Normal);
}
.secondaryLightPrimary:active,
.secondaryLightPrimary:focus,
.secondaryLightPrimary:hover {
background-color: var(--Theme-Secondary-Light-Button-Primary-Fill-Hover);
color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Hover);
}
.secondaryLightPrimary:disabled {
background-color: var(--Theme-Secondary-Light-Button-Primary-Fill-Disabled);
color: var(--Theme-Secondary-Light-Button-Primary-On-Fill-Disabled);
}
.secondaryLightSecondary {
background-color: var(--Theme-Secondary-Light-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Secondary-Light-Button-Secondary-Border-Normal);
color: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Normal);
}
.secondaryLightSecondary:active,
.secondaryLightSecondary:focus,
.secondaryLightSecondary:hover {
background-color: var(--Theme-Secondary-Light-Button-Secondary-Fill-Hover);
border-color: var(--Theme-Secondary-Light-Button-Secondary-Border-Hover);
color: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Hover);
}
.secondaryLightSecondary:disabled {
background-color: var(--Theme-Secondary-Light-Button-Secondary-Fill-Disabled);
border-color: var(--Theme-Secondary-Light-Button-Secondary-Border-Disabled);
color: var(--Theme-Secondary-Light-Button-Secondary-On-Fill-Disabled);
}
.tertiaryDarkPrimary {
background-color: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Normal);
color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Normal);
}
.tertiaryDarkPrimary:active,
.tertiaryDarkPrimary:focus,
.tertiaryDarkPrimary:hover {
background-color: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Hover);
color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Hover);
}
.tertiaryDarkPrimary:disabled {
background-color: var(--Theme-Tertiary-Dark-Button-Primary-Fill-Disabled);
color: var(--Theme-Tertiary-Dark-Button-Primary-On-Fill-Disabled);
}
.tertiaryDarkSecondary {
background-color: var(--Theme-Tertiary-Dark-Button-Secondary-Fill-Normal);
border-color: var(--Theme-Tertiary-Dark-Button-Secondary-Border-Normal);
color: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Normal);
}
.tertiaryDarkSecondary:active,
.tertiaryDarkSecondary:focus,
.tertiaryDarkSecondary:hover {
background-color: var(--Theme-Tertiary-Dark-Button-Secondary-Fill-Hover);
border-color: var(--Theme-Tertiary-Dark-Button-Secondary-Border-Hover);
color: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Hover);
}
.tertiaryDarkSecondary:disabled {
background-color: var(--Theme-Tertiary-Dark-Button-Secondary-Fill-Disabled);
border-color: var(--Theme-Tertiary-Dark-Button-Secondary-Border-Disabled);
color: var(--Theme-Tertiary-Dark-Button-Secondary-On-Fill-Disabled);
}
.tertiaryLightPrimary {
background-color: var(--Theme-Tertiary-Light-Button-Primary-Fill-Normal);
color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Normal);
}
.tertiaryLightPrimary:active,
.tertiaryLightPrimary:focus,
.tertiaryLightPrimary:hover {
background-color: var(--Theme-Tertiary-Light-Button-Primary-Fill-Hover);
color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Hover);
}
.tertiaryLightPrimary:disabled {
background-color: var(--Theme-Tertiary-Light-Button-Primary-Fill-Disabled);
color: var(--Theme-Tertiary-Light-Button-Primary-On-Fill-Disabled);
}
.tertiaryLightSecondary {
background-color: var(--Tertiary-Light-Button-Secondary-Fill-Normal);
border-color: var(--Tertiary-Light-Button-Secondary-Border-Normal);
color: var(--Tertiary-Light-Button-Secondary-On-Fill-Normal);
}
.tertiaryLightSecondary:active,
.tertiaryLightSecondary:focus,
.tertiaryLightSecondary:hover {
background-color: var(--Tertiary-Light-Button-Secondary-Fill-Hover);
border-color: var(--Tertiary-Light-Button-Secondary-Border-Hover);
color: var(--Tertiary-Light-Button-Secondary-On-Fill-Hover);
}
.tertiaryLightSecondary:disabled {
background-color: var(--Tertiary-Light-Button-Secondary-Fill-Disabled);
border-color: var(--Tertiary-Light-Button-Secondary-Border-Disabled);
color: var(--Tertiary-Light-Button-Secondary-On-Fill-Disabled);
}

View File

@@ -5,8 +5,10 @@ import styles from "./button.module.css"
export const buttonVariants = cva(styles.btn, {
variants: {
intent: {
inverted: styles.inverted,
primary: styles.primary,
secondary: styles.secondary,
tertiary: styles.tertiary,
},
size: {
small: styles.small,
@@ -14,13 +16,14 @@ export const buttonVariants = cva(styles.btn, {
large: styles.large,
},
theme: {
primaryDark: styles.primaryDark,
primaryLight: styles.primaryLight,
primaryStrong: styles.primaryStrong,
secondaryLight: styles.secondaryLight,
secondaryDark: styles.secondaryDark,
tertiaryLight: styles.tertiaryLight,
tertiaryDark: styles.tertiaryDark,
base: "",
primaryDark: "",
primaryStrong: "",
primaryLight: "",
secondaryLight: "",
secondaryDark: "",
tertiaryLight: "",
tertiaryDark: "",
},
variant: {
default: styles.default,
@@ -33,4 +36,97 @@ export const buttonVariants = cva(styles.btn, {
theme: "primaryLight",
variant: "default",
},
compoundVariants: [
{
className: styles.basePrimary,
intent: "primary",
theme: "base",
},
{
className: styles.baseSecondary,
intent: "secondary",
theme: "base",
},
{
className: styles.baseTertiary,
intent: "tertiary",
theme: "base",
},
{
className: styles.baseInverted,
intent: "inverted",
theme: "base",
},
{
className: styles.primaryDarkPrimary,
intent: "primary",
theme: "primaryDark",
},
{
className: styles.primaryDarkSecondary,
intent: "secondary",
theme: "primaryDark",
},
{
className: styles.primaryLightPrimary,
intent: "primary",
theme: "primaryLight",
},
{
className: styles.primaryLightSecondary,
intent: "secondary",
theme: "primaryLight",
},
{
className: styles.primaryStrongPrimary,
intent: "primary",
theme: "primaryStrong",
},
{
className: styles.primaryStrongSecondary,
intent: "secondary",
theme: "primaryStrong",
},
{
className: styles.secondaryDarkPrimary,
intent: "primary",
theme: "secondaryDark",
},
{
className: styles.secondaryDarkSecondary,
intent: "secondary",
theme: "secondaryDark",
},
{
className: styles.secondaryLightPrimary,
intent: "primary",
theme: "secondaryLight",
},
{
className: styles.secondaryLightSecondary,
intent: "secondary",
theme: "secondaryLight",
},
{
className: styles.tertiaryDarkPrimary,
intent: "primary",
theme: "tertiaryDark",
},
{
className: styles.tertiaryDarkSecondary,
intent: "secondary",
theme: "tertiaryDark",
},
{
className: styles.tertiaryLightPrimary,
intent: "primary",
theme: "tertiaryLight",
},
{
className: styles.tertiaryLightSecondary,
intent: "secondary",
theme: "tertiaryLight",
},
],
})

View File

@@ -1,55 +0,0 @@
.gridContainer {
display: grid;
gap: var(--Spacing-x2);
}
.carousel {
display: grid;
grid-auto-columns: 80dvw;
grid-auto-flow: column;
gap: var(--Spacing-x2);
margin-left: calc(0 - var(--Spacing-x2));
margin-right: calc(0 - var(--Spacing-x2));
padding-left: var(--Spacing-x2);
overflow-x: scroll;
scroll-padding-left: var(--Spacing-x2);
scroll-snap-type: x mandatory;
scrollbar-width: none;
/* Hide scrollbar IE and Edge */
-ms-overflow-style: none;
/* Hide Scrollbar Firefox */
}
.carousel:last-child {
margin-right: var(--Spacing-x2);
}
.carousel > * {
scroll-snap-align: start;
}
/* Hide Scrollbar Chrome, Safari and Opera */
.gridContainer::-webkit-scrollbar {
display: none;
}
@media screen and (min-width: 1367px) {
.twoColumnGrid,
.twoPlusOne {
grid-template-columns: repeat(2, 1fr);
}
.threeColumnGrid {
grid-template-columns: repeat(3, 1fr);
}
.twoPlusOne > *:last-child {
grid-column: span 2;
}
.carousel {
grid-auto-flow: unset;
margin: 0;
padding: 0;
}
}

View File

@@ -1,35 +1,46 @@
.container {
--select-border: 2px solid var(--UI-Grey-60);
--select-width: min(28rem, 100%);
position: relative;
}
.comboBoxContainer {
background-color: var(--Main-Grey-White);
border-color: var(--Scandic-Beige-40);
border-radius: var(--Corner-radius-Medium);
border-style: solid;
border-width: 1px;
display: grid;
grid-template-areas: "content";
width: var(--select-width);
gap: var(--Spacing-x-half);
grid-template-areas:
"label chevron"
"input chevron";
grid-template-columns: 1fr auto;
grid-template-rows: auto auto;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
}
.label {
grid-area: label;
}
.input {
background-color: var(--Main-Grey-White);
border: var(--select-border);
border-radius: var(--Corner-radius-Small);
grid-area: content;
height: 40px;
padding: var(--Spacing-x1) var(--Spacing-x2);
width: var(--select-width);
border: none;
grid-area: input;
height: 18px;
padding: 0;
}
.input,
.listBoxItem {
color: var(--UI-Grey-60);
color: var(--Main-Grey-100);
}
.button {
background: none;
border: none;
cursor: pointer;
grid-area: content;
grid-area: chevron;
height: 100%;
justify-self: flex-end;
padding-left: 0;
@@ -38,13 +49,27 @@
.popover {
background-color: var(--Main-Grey-White);
border: var(--select-border);
border-radius: var(--Corner-radius-Small);
border-color: var(--Scandic-Beige-40);
border-style: solid;
border-width: 1px;
border-radius: var(--Corner-radius-Medium);
left: 0px;
max-height: 400px;
overflow: auto;
padding: var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x2) var(--Spacing-x1);
width: var(--select-width);
padding: var(--Spacing-x2);
top: calc(60px + var(--Spacing-x1));
width: 100%;
}
.listBoxItem {
padding: 0 var(--Spacing-x1);
}
padding: var(--Spacing-x1) var(--Spacing-x1) var(--Spacing-x1)
var(--Spacing-x2);
}
.listBoxItem[data-selected="true"],
.listBoxItem:hover {
background-color: var(--Scandic-Blue-00);
border-radius: var(--Corner-radius-Medium);
cursor: pointer;
outline: none;
}

View File

@@ -1,6 +1,11 @@
import type { RegisterOptions } from "react-hook-form"
export type CountryProps = {
label: string
name?: string
placeholder?: string
registerOptions?: RegisterOptions
}
export type CountryPortalContainer = HTMLDivElement | undefined
export type CountryPortalContainerArgs = HTMLDivElement | null

View File

@@ -1,6 +1,6 @@
"use client"
import { ErrorMessage } from "@hookform/error-message"
import { useRef } from "react"
import { useState } from "react"
import {
Button,
ComboBox,
@@ -14,21 +14,33 @@ import {
import { useController, useFormContext } from "react-hook-form"
import { useIntl } from "react-intl"
import Label from "@/components/TempDesignSystem/Form/Label"
import SelectChevron from "@/components/TempDesignSystem/Form/SelectChevron"
import Body from "@/components/TempDesignSystem/Text/Body"
import SelectChevron from "../SelectChevron"
import { countries } from "./countries"
import styles from "./country.module.css"
import type { CountryProps } from "./country"
import type {
CountryPortalContainer,
CountryPortalContainerArgs,
CountryProps,
} from "./country"
export default function CountrySelect({
label,
name = "country",
registerOptions,
registerOptions = {},
}: CountryProps) {
const { formatMessage } = useIntl()
const divRef = useRef<HTMLDivElement>(null)
const [rootDiv, setRootDiv] = useState<CountryPortalContainer>(undefined)
function setRef(node: CountryPortalContainerArgs) {
if (node) {
setRootDiv(node)
}
}
const { control, setValue } = useFormContext()
const { field } = useController({
control,
@@ -43,7 +55,7 @@ export default function CountrySelect({
const selectCountryLabel = formatMessage({ id: "Select a country" })
return (
<div className={styles.container} ref={divRef}>
<div className={styles.container} ref={setRef}>
<ComboBox
aria-label={formatMessage({ id: "Select country of residence" })}
className={styles.select}
@@ -55,6 +67,13 @@ export default function CountrySelect({
selectedKey={field.value}
>
<div className={styles.comboBoxContainer}>
<Label
className={styles.label}
size="small"
required={!!registerOptions.required}
>
{label}
</Label>
<Body asChild fontOnly>
<Input
aria-label={selectCountryLabel}
@@ -73,13 +92,14 @@ export default function CountrySelect({
className={styles.popover}
placement="bottom"
shouldFlip={false}
shouldUpdatePosition={false}
/**
* react-aria uses portals to render Popover in body
* unless otherwise specified. We need it to be contained
* by this component to both access css variables assigned
* on the container as well as to not overflow it at any time.
*/
UNSTABLE_portalContainer={divRef.current ?? undefined}
UNSTABLE_portalContainer={rootDiv}
>
<ListBox>
{countries.map((country, idx) => (

View File

@@ -1,20 +1,9 @@
/* Leaving, will most likely get deleted */
.container {
--border: 2px solid var(--some-black-color, #757575);
--radius: 0.4rem;
--width: min(28rem, 100%);
--width-day: 6rem;
--width-month: 6rem;
--width-year: 8rem;
display: grid;
gap: 0.8rem;
grid-template-areas: "day month year";
grid-template-columns: min(--width-day, 1fr) min(--width-month, 1fr) min(
--width-year,
2fr
);
gap: var(--Spacing-x2);
grid-template-areas: "year month day";
grid-template-columns: 1fr 1fr 1fr;
width: var(--width);
}

View File

@@ -1,6 +1,4 @@
import type { Control, RegisterOptions } from "react-hook-form"
import type { EditProfileSchema } from "@/components/MyProfile/Profile/Edit/Form/schema"
import type { RegisterOptions } from "react-hook-form"
export const enum DateName {
date = "date",
@@ -9,7 +7,6 @@ export const enum DateName {
}
export interface DateProps
extends React.SelectHTMLAttributes<HTMLSelectElement> {
control: Control<EditProfileSchema>
name: keyof EditProfileSchema
registerOptions: RegisterOptions<EditProfileSchema>
name: string
registerOptions: RegisterOptions
}

View File

@@ -11,9 +11,9 @@ import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import Select from "@/components/TempDesignSystem/Select"
import { rangeArray } from "@/utils/rangeArray"
import Select from "../Select"
import { DateName } from "./date"
import styles from "./date.module.css"
@@ -23,14 +23,10 @@ import type { Key } from "react-aria-components"
import type { DateProps } from "./date"
/** TODO: Get selecting with Enter-key to work */
export default function DateSelect({
control,
name,
registerOptions,
}: DateProps) {
export default function DateSelect({ name, registerOptions }: DateProps) {
const { formatMessage } = useIntl()
const d = useWatch({ name })
const { setValue } = useFormContext()
const { control, setValue } = useFormContext()
const { field } = useController({
control,
name,

View File

@@ -1,4 +1,7 @@
.message {
align-items: center;
color: var(--Scandic-Red-60);
margin: var(--Spacing-x-half) 0 0;
}
display: flex;
gap: var(--Spacing-x-half);
margin: var(--Spacing-x1) 0 0;
}

View File

@@ -1,6 +1,7 @@
import { ErrorMessage as RHFErrorMessage } from "@hookform/error-message"
import Body from "@/components/TempDesignSystem/Text/Body"
import { InfoCircleIcon } from "@/components/Icons"
import Caption from "@/components/TempDesignSystem/Text/Caption"
import styles from "./error.module.css"
@@ -15,9 +16,10 @@ export default function ErrorMessage<T>({
errors={errors}
name={name}
render={({ message }) => (
<Body className={styles.message} fontOnly>
<Caption className={styles.message} fontOnly>
<InfoCircleIcon color="red" />
{message}
</Body>
</Caption>
)}
/>
)

View File

@@ -1,8 +1,13 @@
"use client"
import { Input as AriaInput, TextField } from "react-aria-components"
import { useController } from "react-hook-form"
import {
Input as AriaInput,
Label as AriaLabel,
TextField,
} from "react-aria-components"
import { useController, useFormContext } from "react-hook-form"
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./input.module.css"
@@ -11,17 +16,24 @@ import type { InputProps } from "./input"
export default function Input({
"aria-label": ariaLabel,
control,
disabled,
label,
name,
placeholder,
registerOptions,
placeholder = "",
registerOptions = {},
required = false,
type = "text",
}: InputProps) {
const { control } = useFormContext()
const rules = {
...registerOptions,
required:
"required" in registerOptions ? !!registerOptions.required : required,
}
const { field, fieldState, formState } = useController({
control,
name,
rules: registerOptions,
rules,
})
return (
@@ -32,18 +44,24 @@ export default function Input({
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
name={field.name}
onBlur={field.onBlur}
onChange={field.onChange}
type={type}
>
<Body asChild fontOnly>
<AriaInput
className={styles.input}
placeholder={placeholder}
ref={field.ref}
/>
</Body>
<ErrorMessage errors={formState.errors} name={name} />
<AriaLabel className={styles.container} htmlFor={field.name}>
<Body asChild fontOnly>
<AriaInput
className={styles.input}
id={field.name}
name={field.name}
onBlur={field.onBlur}
onChange={field.onChange}
placeholder={placeholder}
ref={field.ref}
required={rules.required}
/>
</Body>
<Label required={rules.required}>{label}</Label>
</AriaLabel>
<ErrorMessage errors={formState.errors} name={field.name} />
</TextField>
)
}

View File

@@ -1,8 +1,44 @@
.input {
border: 2px solid var(--UI-Grey-60);
border-radius: var(--Corner-radius-Small);
color: var(--UI-Grey-60);
height: 40px;
.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;
gap: var(--Spacing-x-half);
grid-template-rows: auto auto;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
width: min(280px, 100%);
}
transition: border-color 200ms ease;
}
.container:has(.input:not(:focus):placeholder-shown) {
gap: 0;
grid-template-rows: 1fr;
}
.container:has(.input:active, .input:focus) {
border-color: var(--Scandic-Blue-90);
}
.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

@@ -1,10 +1,8 @@
import { Control, RegisterOptions } from "react-hook-form"
import { EditProfileSchema } from "@/components/MyProfile/Profile/Edit/Form/schema"
import type { RegisterOptions } from "react-hook-form"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {
control: Control<EditProfileSchema>
name: keyof EditProfileSchema
registerOptions?: RegisterOptions<EditProfileSchema>
label: string
name: string
registerOptions?: RegisterOptions
}

View File

@@ -0,0 +1,20 @@
import { labelVariants } from "./variants"
import type { LabelProps } from "./label"
export default function Label({
children,
className,
required,
size,
}: LabelProps) {
const classNames = labelVariants({
className,
size,
})
return (
<span className={classNames}>
{children} {required ? "*" : ""}
</span>
)
}

View File

@@ -0,0 +1,30 @@
.label {
color: var(--UI-Grey-60);
font-family: "fira sans";
font-weight: 400;
letter-spacing: 0.03px;
line-height: 120%;
text-align: left;
}
span.small {
display: block;
font-size: 12px;
}
span.regular {
font-size: 16px;
order: 1;
transition: font-size 200ms ease 100ms;
}
input:focus ~ .label,
input:not(:placeholder-shown) ~ .label {
display: block;
font-size: 12px;
}
input:placeholder-shown ~ .label {
align-self: center;
grid-row: 1/-1;
}

View File

@@ -0,0 +1,9 @@
import { labelVariants } from "./variants"
import type { VariantProps } from "class-variance-authority"
export interface LabelProps
extends React.PropsWithChildren<React.HTMLAttributes<HTMLSpanElement>>,
VariantProps<typeof labelVariants> {
required?: boolean
}

View File

@@ -0,0 +1,15 @@
import { cva } from "class-variance-authority"
import styles from "./label.module.css"
export const labelVariants = cva(styles.label, {
variants: {
size: {
small: styles.small,
regular: styles.regular,
},
},
defaultVariants: {
size: "regular",
},
})

View File

@@ -1,99 +1,137 @@
"use client"
import "react-international-phone/style.css"
import { useCallback, useEffect, useRef } from "react"
import { parsePhoneNumber } from "libphonenumber-js"
import {
Input as AriaInput,
Label as AriaLabel,
TextField,
} from "react-aria-components"
import { useController, useFormContext, useWatch } from "react-hook-form"
import {
defaultCountries,
getCountry,
PhoneInput,
type PhoneInputRefType,
CountrySelector,
DialCodePreview,
ParsedCountry,
usePhoneInput,
} from "react-international-phone"
import { useIntl } from "react-intl"
import { ChevronDownIcon } from "@/components/Icons"
import ErrorMessage from "@/components/TempDesignSystem/Form/ErrorMessage"
import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body"
import styles from "./phone.module.css"
import type { ChangeEvent } from "react"
import type { PhoneProps } from "./phone"
export default function Phone({
countrySelectName = "country",
ariaLabel = "Phone number input",
disabled = false,
label,
name = "phoneNumber",
placeholder = "",
registerOptions = {
required: true,
},
}: PhoneProps) {
const phoneRef = useRef<PhoneInputRefType>(null)
const { control, formState } = useFormContext()
const countryValue = useWatch({ name: countrySelectName })
const defaultCountry = getCountry({
countries: defaultCountries,
field: "iso2",
value: String(countryValue).toLowerCase(),
})
/**
* Holds the previous selected country to be able to update
* countrycode based on country select field.
* Since PhoneInput inputs the countrys dialcode (country code) upon
* selection, we need to check if the current value is just
* the previously selected countrys dialcode number.
*/
const prevSelectedCountry = useRef<string | undefined>(countryValue)
const { field } = useController({
const { formatMessage } = useIntl()
const { control, setValue } = useFormContext()
const phone = useWatch({ name })
const { field, fieldState, formState } = useController({
control,
disabled,
name,
rules: registerOptions,
})
const handleCountrySelectForPhone = useCallback((country: string) => {
const selectedCountry = getCountry({
countries: defaultCountries,
field: "iso2",
value: country.toLowerCase(),
const { country, handlePhoneValueChange, inputValue, setCountry } =
usePhoneInput({
defaultCountry:
parsePhoneNumber(
formState.defaultValues?.phoneNumber
).country?.toLowerCase() ?? "sv",
disableCountryGuess: true,
forceDialCode: true,
value: phone,
})
if (selectedCountry) {
phoneRef.current?.setCountry(selectedCountry.iso2)
prevSelectedCountry.current = country.toLowerCase()
}
}, [])
function handleSelectCountry(value: ParsedCountry) {
setCountry(value.iso2)
}
useEffect(() => {
if (countryValue) {
if (field.value) {
if (prevSelectedCountry.current) {
if (prevSelectedCountry.current !== countryValue) {
const selectedCountryPrev = getCountry({
countries: defaultCountries,
field: "iso2",
value: prevSelectedCountry.current.toLowerCase(),
})
if (
field.value.replace("+", "") === selectedCountryPrev?.dialCode
) {
handleCountrySelectForPhone(countryValue)
}
}
} else {
handleCountrySelectForPhone(countryValue)
}
} else {
handleCountrySelectForPhone(countryValue)
}
}
}, [countryValue, field.value, handleCountrySelectForPhone])
function handleChange(evt: ChangeEvent<HTMLInputElement>) {
handlePhoneValueChange(evt)
setValue(name, evt.target.value)
}
return (
<div className={styles.phone}>
<PhoneInput
{...field}
className={styles.input}
defaultCountry={defaultCountry?.iso2 ?? "se"}
placeholder={placeholder}
<CountrySelector
dropdownArrowClassName={styles.arrow}
flagClassName={styles.flag}
onSelect={handleSelectCountry}
preferredCountries={["de", "dk", "fi", "no", "se", "gb"]}
ref={phoneRef}
selectedCountry={country.iso2}
renderButtonWrapper={(props) => (
<button
{...props.rootProps}
className={styles.select}
tabIndex={0}
type="button"
>
<Label required={!!registerOptions.required} size="small">
{formatMessage({ id: "Country code" })}
</Label>
<div className={styles.selectContainer}>
{props.children}
<Body asChild fontOnly>
<DialCodePreview
className={styles.dialCode}
dialCode={country.dialCode}
prefix="+"
/>
</Body>
<ChevronDownIcon
className={styles.chevron}
color="grey80"
height={18}
width={18}
/>
</div>
</button>
)}
/>
<TextField
aria-label={ariaLabel}
defaultValue={field.value}
isDisabled={disabled ?? field.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
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>
<ErrorMessage errors={formState.errors} name={field.name} />
</TextField>
<ErrorMessage errors={formState.errors} name={name} />
</div>
)

View File

@@ -1,26 +1,130 @@
.phone {
--react-international-phone-border-color: var(--UI-Grey-60);
--react-international-phone-border-radius: var(--Corner-radius-Small);
--react-international-phone-font-size: var(
--typography-Body-Regular-fontSize
);
--react-international-phone-height: 40px;
--react-international-phone-text-color: var(--UI-Grey-60);
}
.phone :global(.react-international-phone-input-container) {
display: grid;
/* r-i-p sets their width dynamically and doesn't respect the width property of its parent */
grid-template-columns: 470px minmax(203px, 1fr);
width: min(280px, 100%);
gap: var(--Spacing-x2);
grid-template-columns: max(164px) 1fr;
--react-international-phone-background-color: var(--Main-Grey-White);
--react-international-phone-border-color: var(--Scandic-Beige-40);
--react-international-phone-dropdown-preferred-list-divider-color: var(
--Scandic-Brand-Pale-Peach
);
--react-international-phone-selected-dropdown-item-background-color: var(
--Scandic-Blue-00
);
--react-international-phone-text-color: var(--Main-Grey-100);
--react-international-phone-dropdown-preferred-list-divider-margin: 8px;
--react-international-phone-height: 60px;
--react-international-phone-dropdown-top: calc(
var(--react-international-phone-height) + var(--Spacing-x1)
);
}
/* react-international-phone only exposes variables to change border-color */
.phone :global(.react-international-phone-country-selector-button),
.phone :global(.react-international-phone-input) {
border-width: 2px;
.phone:has(.input:active, .input:focus) {
--react-international-phone-border-color: var(--Scandic-Blue-90);
}
.phone :global(.react-international-phone-input) {
.phone :global(.react-international-phone-country-selector-dropdown) {
background: var(--Main-Grey-White);
border-radius: var(--Corner-radius-Medium);
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.08);
gap: var(--Spacing-x1);
outline: none;
padding: var(--Spacing-x2);
}
.phone
:global(.react-international-phone-country-selector-dropdown__list-item) {
border-radius: var(--Corner-radius-Medium);
padding: var(--Spacing-x1) var(--Spacing-x1) var(--Spacing-x1)
var(--Spacing-x-one-and-half);
}
.phone
:global(.react-international-phone-country-selector-button__button-content) {
align-self: center;
}
.inputContainer,
.select {
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;
gap: var(--Spacing-x-half);
grid-template-rows: auto auto;
height: 60px;
padding: var(--Spacing-x1) var(--Spacing-x2);
transition: border-color 200ms ease;
}
.select {
width: 100%;
}
.select[aria-expanded="true"] .chevron {
transform: rotate(180deg);
}
.selectContainer {
background-color: var(--Main-Grey-White);
border: none;
display: grid;
gap: var(--Spacing-x1);
grid-template-columns: auto 1fr auto;
height: 18px;
justify-content: flex-start;
order: 2;
}
.arrow {
display: none;
}
.flag {
height: 18px;
margin: 0;
width: 18px;
}
.select .dialCode {
border: none;
color: var(--Main-Grey-100);
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);
}
.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

@@ -1,7 +1,9 @@
import type { RegisterOptions } from "react-hook-form"
export type PhoneProps = {
countrySelectName?: string
ariaLabel?: string
disabled?: boolean
label: string
name?: string
placeholder?: string
registerOptions?: RegisterOptions

View File

@@ -1,94 +1,35 @@
"use client"
import { useState } from "react"
import {
Button,
type Key,
Label,
ListBox,
ListBoxItem,
Popover,
Select as ReactAriaSelect,
SelectValue,
} from "react-aria-components"
import { useController, useFormContext } from "react-hook-form"
import Body from "../../Text/Body"
import Footnote from "../../Text/Footnote"
import SelectChevron from "../SelectChevron"
import ReactAriaSelect from "@/components/TempDesignSystem/Select"
import styles from "./select.module.css"
import type { SelectPortalContainer, SelectProps } from "./select"
import type { SelectProps } from "./select"
export default function Select({
"aria-label": ariaLabel,
items,
label,
onSelect,
name,
placeholder,
value,
defaultSelectedKey,
registerOptions = {},
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(null)
function setRef(node: SelectPortalContainer) {
if (node) {
setRootDiv(node)
}
}
function handleOnSelect(key: Key) {
onSelect(key)
}
const { control } = useFormContext()
const { field } = useController({
control,
name,
rules: registerOptions,
})
return (
<div className={styles.container} ref={setRef}>
<ReactAriaSelect
defaultSelectedKey={defaultSelectedKey}
aria-label={ariaLabel}
className={styles.select}
onSelectionChange={handleOnSelect}
placeholder={placeholder}
selectedKey={value as Key}
>
<Body asChild fontOnly>
<Button className={styles.input}>
<div className={styles.inputContentWrapper}>
<Footnote asChild fontOnly>
<Label className={styles.label}>{label}</Label>
</Footnote>
<SelectValue />
</div>
<SelectChevron />
</Button>
</Body>
<Body asChild fontOnly>
<Popover
className={styles.popover}
placement="bottom"
shouldFlip={false}
/**
* react-aria uses portals to render Popover in body
* unless otherwise specified. We need it to be contained
* by this component to both access css variables assigned
* on the container as well as to not overflow it at any time.
*/
UNSTABLE_portalContainer={rootDiv ?? undefined}
>
<ListBox className={styles.listBox}>
{items.map((item) => (
<ListBoxItem
aria-label={String(item)}
className={styles.listBoxItem}
id={item.value}
key={item.label}
>
{item.label}
</ListBoxItem>
))}
</ListBox>
</Popover>
</Body>
</ReactAriaSelect>
</div>
<ReactAriaSelect
defaultSelectedKey={field.value}
disabled={field.disabled}
items={items}
label={label}
name={field.name}
onBlur={field.onBlur}
onSelect={field.onChange}
placeholder={placeholder}
value={field.value}
/>
)
}

View File

@@ -1,13 +1,12 @@
import type { Key } from "react-aria-components"
import type { RegisterOptions } from "react-hook-form"
import type { SelectProps as ReactAriaSelectProps } from "@/components/TempDesignSystem/Select/select"
export interface SelectProps
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onSelect"> {
items: { label: string; value: Key }[]
label: string
name: string
onSelect: (key: Key) => void
placeholder?: string
defaultSelectedKey?: Key
extends Omit<
React.SelectHTMLAttributes<HTMLSelectElement>,
"name" | "onSelect"
>,
Omit<ReactAriaSelectProps, "onSelect" | "ref" | "value"> {
registerOptions?: RegisterOptions
}
export type SelectPortalContainer = HTMLDivElement | null

View File

@@ -5,7 +5,7 @@ import styles from "./chevron.module.css"
export default function SelectChevron() {
return (
<span aria-hidden="true" className={styles.chevron}>
<ChevronDownIcon height={24} width={24} />
<ChevronDownIcon color="grey80" />
</span>
)
}

View File

@@ -56,7 +56,7 @@
}
.activeSidebar {
background-color: var(--Scandic-Brand-Warm-White);
background-color: var(--Scandic-Brand-Pale-Peach);
}
.black {

View File

@@ -10,6 +10,7 @@ export const linkVariants = cva(styles.link, {
color: {
black: styles.black,
burgundy: styles.burgundy,
none: "",
pale: styles.pale,
peach80: styles.peach80,
},

View File

@@ -0,0 +1,98 @@
"use client"
import { useState } from "react"
import {
Button,
type Key,
ListBox,
ListBoxItem,
Popover,
Select as ReactAriaSelect,
SelectValue,
} from "react-aria-components"
import Label from "@/components/TempDesignSystem/Form/Label"
import Body from "@/components/TempDesignSystem/Text/Body"
import SelectChevron from "../Form/SelectChevron"
import styles from "./select.module.css"
import type {
SelectPortalContainer,
SelectPortalContainerArgs,
SelectProps,
} from "./select"
export default function Select({
"aria-label": ariaLabel,
defaultSelectedKey,
items,
label,
name,
onSelect,
placeholder,
value,
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
function setRef(node: SelectPortalContainerArgs) {
if (node) {
setRootDiv(node)
}
}
function handleOnSelect(key: Key) {
onSelect(key)
}
return (
<div className={styles.container} ref={setRef}>
<ReactAriaSelect
aria-label={ariaLabel}
className={styles.select}
defaultSelectedKey={defaultSelectedKey}
name={name}
onSelectionChange={handleOnSelect}
placeholder={placeholder}
selectedKey={value as Key}
>
<Body asChild fontOnly>
<Button className={styles.input}>
<div className={styles.inputContentWrapper}>
<Label size="small">{label}</Label>
<SelectValue />
</div>
<SelectChevron />
</Button>
</Body>
<Body asChild fontOnly>
<Popover
className={styles.popover}
placement="bottom"
shouldFlip={false}
/**
* react-aria uses portals to render Popover in body
* unless otherwise specified. We need it to be contained
* by this component to both access css variables assigned
* on the container as well as to not overflow it at any time.
*/
UNSTABLE_portalContainer={rootDiv}
>
<ListBox className={styles.listBox}>
{items.map((item) => (
<ListBoxItem
aria-label={String(item)}
className={styles.listBoxItem}
id={item.value}
key={item.label}
>
{item.label}
</ListBoxItem>
))}
</ListBox>
</Popover>
</Body>
</ReactAriaSelect>
</div>
)
}

View File

@@ -2,10 +2,6 @@
position: relative;
}
.label {
color: var(--Base-Text-UI-Placeholder);
}
.select {
border: 1px solid var(--Base-Border-Normal);
border-radius: var(--Corner-radius-Medium);
@@ -27,7 +23,7 @@
color: var(--Base-Text-UI-High-contrast);
display: flex;
gap: var(--Spacing-x-half);
height: 56px;
height: 60px;
outline: none;
padding: var(--Spacing-x-one-and-half) var(--Spacing-x2);
text-align: left;
@@ -69,4 +65,4 @@
.listBoxItem[data-selected="true"] {
font-weight: 500;
}
}

View File

@@ -0,0 +1,15 @@
import type { Key } from "react-aria-components"
export interface SelectProps
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onSelect"> {
defaultSelectedKey?: Key
items: { label: string; value: Key }[]
label: string
name: string
onSelect: (key: Key) => void
placeholder?: string
value?: string | number
}
export type SelectPortalContainer = HTMLDivElement | undefined
export type SelectPortalContainerArgs = HTMLDivElement | null

View File

@@ -5,18 +5,22 @@
}
.one {
font-size: clamp(var(--typography-Script-1-Mobile-fontSize),
1.3vw + 14px,
var(--typography-Script-1-Desktop-fontSize));
font-size: clamp(
var(--typography-Script-1-Mobile-fontSize),
1.3vw + 14px,
var(--typography-Script-1-Desktop-fontSize)
);
font-weight: var(--typography-Script-1-fontWeight);
letter-spacing: var(--typography-Script-1-letterSpacing);
line-height: var(--typography-Script-1-lineHeight);
}
.two {
font-size: clamp(var(--typography-Script-2-Mobile-fontSize),
0.6vw + 15px,
var(--typography-Script-2-Desktop-fontSize));
font-size: clamp(
var(--typography-Script-2-Mobile-fontSize),
0.6vw + 15px,
var(--typography-Script-2-Desktop-fontSize)
);
font-weight: var(--typography-Script-2-fontWeight);
letter-spacing: var(--typography-Script-2-letterSpacing);
line-height: var(--typography-Script-2-lineHeight);
@@ -31,7 +35,7 @@
}
.black {
color: #000;
color: var(--Main-Grey-100);
}
.burgundy {
@@ -44,4 +48,4 @@
.plosa {
color: var(--Theme-Primary-Light-On-Surface-Accent);
}
}

View File

@@ -43,8 +43,7 @@
}
.black {
/* No black variable exist yet */
color: #000;
color: var(--Main-Grey-100);
}
.burgundy {

View File

@@ -26,8 +26,7 @@
}
.black {
/* No black variable exist yet */
color: #000;
color: var(--Main-Grey-100);
}
.burgundy {

View File

@@ -34,8 +34,7 @@
}
.black {
/* No black variable exist yet */
color: #000;
color: var(--Main-Grey-100);
}
.burgundy {

View File

@@ -28,6 +28,10 @@
text-align: left;
}
.black {
color: var(--Main-Grey-100);
}
.burgundy {
color: var(--Scandic-Brand-Burgundy);
}

View File

@@ -5,6 +5,7 @@ import styles from "./subtitle.module.css"
const config = {
variants: {
color: {
black: styles.black,
burgundy: styles.burgundy,
pale: styles.pale,
},

View File

@@ -85,7 +85,7 @@
}
.black {
color: #000;
color: var(--Main-Grey-100);
}
.burgundy {
@@ -96,6 +96,10 @@
color: var(--Scandic-Brand-Pale-Peach);
}
.peach80 {
color: var(--Scandic-Peach-80);
}
.red {
color: var(--Scandic-Brand-Scandic-Red);
}

View File

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