feat(WEB-162): final design edit profile page
This commit is contained in:
committed by
Christel Westerberg
parent
5f3e417593
commit
d84efcbb0f
@@ -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;
|
||||
}
|
||||
104
components/Forms/Edit/Profile/FormContent/index.tsx
Normal file
104
components/Forms/Edit/Profile/FormContent/index.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
10
components/Forms/Edit/Profile/form.module.css
Normal file
10
components/Forms/Edit/Profile/form.module.css
Normal 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);
|
||||
}
|
||||
94
components/Forms/Edit/Profile/index.tsx
Normal file
94
components/Forms/Edit/Profile/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
24
components/Forms/Edit/Profile/schema.ts
Normal file
24
components/Forms/Edit/Profile/schema.ts
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
40
components/Icons/InfoCircle.tsx
Normal file
40
components/Icons/InfoCircle.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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 />
|
||||
)
|
||||
|
||||
@@ -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 <></>
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.form {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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} />
|
||||
}
|
||||
6
components/Profile/Header/header.module.css
Normal file
6
components/Profile/Header/header.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: var(--Spacing-x2);
|
||||
justify-content: space-between;
|
||||
}
|
||||
5
components/Profile/Header/index.tsx
Normal file
5
components/Profile/Header/index.tsx
Normal 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>
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
20
components/TempDesignSystem/Form/Label/index.tsx
Normal file
20
components/TempDesignSystem/Form/Label/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
30
components/TempDesignSystem/Form/Label/label.module.css
Normal file
30
components/TempDesignSystem/Form/Label/label.module.css
Normal 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;
|
||||
}
|
||||
9
components/TempDesignSystem/Form/Label/label.ts
Normal file
9
components/TempDesignSystem/Form/Label/label.ts
Normal 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
|
||||
}
|
||||
15
components/TempDesignSystem/Form/Label/variants.ts
Normal file
15
components/TempDesignSystem/Form/Label/variants.ts
Normal 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",
|
||||
},
|
||||
})
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
|
||||
.activeSidebar {
|
||||
background-color: var(--Scandic-Brand-Warm-White);
|
||||
background-color: var(--Scandic-Brand-Pale-Peach);
|
||||
}
|
||||
|
||||
.black {
|
||||
|
||||
@@ -10,6 +10,7 @@ export const linkVariants = cva(styles.link, {
|
||||
color: {
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
none: "",
|
||||
pale: styles.pale,
|
||||
peach80: styles.peach80,
|
||||
},
|
||||
|
||||
98
components/TempDesignSystem/Select/index.tsx
Normal file
98
components/TempDesignSystem/Select/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
15
components/TempDesignSystem/Select/select.ts
Normal file
15
components/TempDesignSystem/Select/select.ts
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
}
|
||||
|
||||
.black {
|
||||
/* No black variable exist yet */
|
||||
color: #000;
|
||||
color: var(--Main-Grey-100);
|
||||
}
|
||||
|
||||
.burgundy {
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
}
|
||||
|
||||
.black {
|
||||
/* No black variable exist yet */
|
||||
color: #000;
|
||||
color: var(--Main-Grey-100);
|
||||
}
|
||||
|
||||
.burgundy {
|
||||
|
||||
@@ -34,8 +34,7 @@
|
||||
}
|
||||
|
||||
.black {
|
||||
/* No black variable exist yet */
|
||||
color: #000;
|
||||
color: var(--Main-Grey-100);
|
||||
}
|
||||
|
||||
.burgundy {
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.black {
|
||||
color: var(--Main-Grey-100);
|
||||
}
|
||||
|
||||
.burgundy {
|
||||
color: var(--Scandic-Brand-Burgundy);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import styles from "./subtitle.module.css"
|
||||
const config = {
|
||||
variants: {
|
||||
color: {
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const config = {
|
||||
black: styles.black,
|
||||
burgundy: styles.burgundy,
|
||||
pale: styles.pale,
|
||||
peach80: styles.peach80,
|
||||
red: styles.red,
|
||||
},
|
||||
textAlign: {
|
||||
|
||||
Reference in New Issue
Block a user