Merge master

This commit is contained in:
Linus Flood
2024-11-28 13:37:45 +01:00
225 changed files with 4488 additions and 3192 deletions

View File

@@ -30,7 +30,6 @@ export default function Alert({
if (!text && !heading) {
return null
}
return (
<section className={classNames}>
<div className={styles.content}>
@@ -65,7 +64,7 @@ export default function Alert({
) : null}
</div>
{link ? (
<Link color="burgundy" href={link.url}>
<Link color="burgundy" textDecoration="underline" href={link.url}>
{link.title}
</Link>
) : null}

View File

@@ -265,6 +265,10 @@ a.default {
color: var(--Base-Button-Text-On-Fill-Normal);
}
.baseTextInverted {
color: var(--Base-Button-Primary-On-Fill-Normal);
}
.baseText:active,
.baseText:focus,
.baseText:hover {

View File

@@ -10,6 +10,7 @@ export const buttonVariants = cva(styles.btn, {
secondary: styles.secondary,
tertiary: styles.tertiary,
text: styles.text,
textInverted: styles.text,
},
size: {
small: styles.small,
@@ -140,5 +141,10 @@ export const buttonVariants = cva(styles.btn, {
intent: "text",
theme: "base",
},
{
className: styles.baseTextInverted,
intent: "textInverted",
theme: "base",
},
],
})

View File

@@ -25,6 +25,7 @@
width: 24px;
height: 24px;
min-width: 24px;
background: var(--UI-Input-Controls-Surface-Normal);
border: 1px solid var(--UI-Input-Controls-Border-Normal);
border-radius: 4px;
transition: all 200ms;

View File

@@ -15,7 +15,6 @@ export default function Card({
iconHeight = 32,
iconWidth = 32,
declined = false,
defaultChecked,
highlightSubtitle = false,
id,
list,
@@ -58,7 +57,6 @@ export default function Card({
<input
{...register(name)}
aria-hidden
defaultChecked={defaultChecked}
id={id || name}
hidden
type={type}

View File

@@ -1,8 +1,3 @@
/* Leaving, will most likely get deleted */
.datePicker {
container-name: datePickerContainer;
container-type: inline-size;
}
.container {
display: grid;
gap: var(--Spacing-x2);
@@ -11,6 +6,13 @@
width: var(--width);
}
@media (max-width: 350px) {
.container {
display: flex;
flex-direction: column;
}
}
.day {
grid-area: day;
}
@@ -31,10 +33,3 @@
.year.invalid > div > div {
border-color: var(--Scandic-Red-60);
}
@container datePickerContainer (max-width: 350px) {
.container {
display: flex;
flex-direction: column;
}
}

View File

@@ -36,21 +36,6 @@ export default function NewPassword({
const intl = useIntl()
const [isPasswordVisible, setIsPasswordVisible] = useState(false)
function getErrorMessage(key: PasswordValidatorKey) {
switch (key) {
case "length":
return `10 ${intl.formatMessage({ id: "to" })} 40 ${intl.formatMessage({ id: "characters" })}`
case "hasUppercase":
return `1 ${intl.formatMessage({ id: "uppercase letter" })}`
case "hasLowercase":
return `1 ${intl.formatMessage({ id: "lowercase letter" })}`
case "hasNumber":
return `1 ${intl.formatMessage({ id: "number" })}`
case "hasSpecialChar":
return `1 ${intl.formatMessage({ id: "special character" })}`
}
}
return (
<Controller
disabled={disabled}
@@ -59,6 +44,7 @@ export default function NewPassword({
rules={registerOptions}
render={({ field, fieldState, formState }) => {
const errors = Object.values(formState.errors[name]?.types ?? []).flat()
return (
<TextField
aria-label={ariaLabel}
@@ -100,20 +86,9 @@ export default function NewPassword({
</Button>
) : null}
</div>
{field.value ? (
<div className={styles.errors}>
{Object.entries(passwordValidators).map(
([key, { message }]) => (
<Caption asChild color="black" key={key}>
<Text className={styles.helpText} slot="description">
<Icon errorMessage={message} errors={errors} />
{getErrorMessage(key as PasswordValidatorKey)}
</Text>
</Caption>
)
)}
</div>
) : null}
<PasswordValidation value={field.value} errors={errors} />
{!field.value && fieldState.error ? (
<Caption className={styles.error} fontOnly>
<InfoCircleIcon color="red" />
@@ -134,3 +109,43 @@ function Icon({ errorMessage, errors }: IconProps) {
<CheckIcon color="green" height={20} width={20} />
)
}
function PasswordValidation({
value,
errors,
}: {
value: string
errors: string[]
}) {
const intl = useIntl()
if (!value) return null
function getErrorMessage(key: PasswordValidatorKey) {
switch (key) {
case "length":
return `10 ${intl.formatMessage({ id: "to" })} 40 ${intl.formatMessage({ id: "characters" })}`
case "hasUppercase":
return `1 ${intl.formatMessage({ id: "uppercase letter" })}`
case "hasLowercase":
return `1 ${intl.formatMessage({ id: "lowercase letter" })}`
case "hasNumber":
return `1 ${intl.formatMessage({ id: "number" })}`
case "hasSpecialChar":
return `1 ${intl.formatMessage({ id: "special character" })}`
}
}
return (
<div className={styles.errors}>
{Object.entries(passwordValidators).map(([key, { message }]) => (
<Caption asChild color="black" key={key}>
<Text className={styles.helpText} slot="description">
<Icon errorMessage={message} errors={errors} />
{getErrorMessage(key as PasswordValidatorKey)}
</Text>
</Caption>
))}
</div>
)
}

View File

@@ -78,69 +78,67 @@ export default function Phone({
}
return (
<div className={`${styles.wrapper} ${className}`}>
<div className={styles.phone}>
<CountrySelector
disabled={readOnly}
dropdownArrowClassName={styles.arrow}
flagClassName={styles.flag}
onSelect={handleSelectCountry}
preferredCountries={["de", "dk", "fi", "no", "se", "gb"]}
selectedCountry={country.iso2}
renderButtonWrapper={(props) => (
<button
{...props.rootProps}
className={styles.select}
tabIndex={0}
type="button"
data-testid="country-selector"
>
<Label required={!!registerOptions.required} size="small">
{intl.formatMessage({ id: "Country code" })}
</Label>
<span 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 className={`${styles.phone} ${className}`}>
<CountrySelector
disabled={readOnly}
dropdownArrowClassName={styles.arrow}
flagClassName={styles.flag}
onSelect={handleSelectCountry}
preferredCountries={["de", "dk", "fi", "no", "se", "gb"]}
selectedCountry={country.iso2}
renderButtonWrapper={(props) => (
<button
{...props.rootProps}
className={styles.select}
tabIndex={0}
type="button"
data-testid="country-selector"
>
<Label required={!!registerOptions.required} size="small">
{intl.formatMessage({ id: "Country code" })}
</Label>
<span className={styles.selectContainer}>
{props.children}
<Body asChild fontOnly>
<DialCodePreview
className={styles.dialCode}
dialCode={country.dialCode}
prefix="+"
/>
</span>
</button>
)}
/>
<TextField
aria-label={ariaLabel}
defaultValue={field.value}
isDisabled={disabled ?? field.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
isReadOnly={readOnly}
name={field.name}
</Body>
<ChevronDownIcon
className={styles.chevron}
color="grey80"
height={18}
width={18}
/>
</span>
</button>
)}
/>
<TextField
aria-label={ariaLabel}
defaultValue={field.value}
isDisabled={disabled ?? field.disabled}
isInvalid={fieldState.invalid}
isRequired={!!registerOptions?.required}
isReadOnly={readOnly}
name={field.name}
type="tel"
>
<AriaInputWithLabel
{...field}
id={field.name}
label={label}
onChange={handleChange}
placeholder={placeholder}
readOnly={readOnly}
required={!!registerOptions.required}
type="tel"
>
<AriaInputWithLabel
{...field}
id={field.name}
label={label}
onChange={handleChange}
placeholder={placeholder}
readOnly={readOnly}
required={!!registerOptions.required}
type="tel"
value={inputValue}
/>
<ErrorMessage errors={formState.errors} name={field.name} />
</TextField>
</div>
value={inputValue}
/>
<ErrorMessage errors={formState.errors} name={field.name} />
</TextField>
</div>
)
}

View File

@@ -1,11 +1,7 @@
.wrapper {
container-name: phoneContainer;
container-type: inline-size;
}
.phone {
display: grid;
grid-template-columns: 1fr;
gap: var(--Spacing-x2);
grid-template-columns: minmax(124px, 164px) 1fr;
--react-international-phone-background-color: var(--Main-Grey-White);
--react-international-phone-border-color: var(--Scandic-Beige-40);
@@ -28,6 +24,12 @@
);
}
@media (min-width: 385px) {
.phone {
grid-template-columns: minmax(124px, 164px) 1fr;
}
}
.phone:has(.input:active, .input:focus) {
--react-international-phone-border-color: var(--Scandic-Blue-90);
}
@@ -104,10 +106,3 @@
justify-self: flex-start;
padding: 0;
}
@container phoneContainer (max-width: 350px) {
.phone {
display: flex;
flex-direction: column;
}
}

View File

@@ -4,6 +4,7 @@
box-shadow: 0px 0px 14px 6px rgba(0, 0, 0, 0.1);
padding: var(--Spacing-x2);
max-width: calc(360px + var(--Spacing-x2) * 2);
overflow-y: auto;
}
.root section:focus-visible {

View File

@@ -38,9 +38,10 @@ export default function Select({
maxHeight,
showRadioButton = false,
discreet = false,
isNestedInModal = false,
}: SelectProps) {
const [rootDiv, setRootDiv] = useState<SelectPortalContainer>(undefined)
const setOverflowVisible = useSetOverflowVisibleOnRA()
const setOverflowVisible = useSetOverflowVisibleOnRA(isNestedInModal)
function setRef(node: SelectPortalContainerArgs) {
if (node) {

View File

@@ -12,6 +12,7 @@ export interface SelectProps
maxHeight?: number
showRadioButton?: boolean
discreet?: boolean
isNestedInModal?: boolean
}
export type SelectPortalContainer = HTMLDivElement | undefined

View File

@@ -59,6 +59,7 @@
.dialog {
height: 100%;
outline: none;
}
.sidePeek {

View File

@@ -16,7 +16,7 @@ import { toastVariants } from "./variants"
import styles from "./toasts.module.css"
export function ToastHandler() {
return <Toaster position="bottom-right" />
return <Toaster position="bottom-right" duration={5000} />
}
function getIcon(variant: ToastsProps["variant"]) {

View File

@@ -6,7 +6,12 @@
background: var(--Base-Surface-Primary-light-Normal);
box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.08);
align-items: center;
width: var(--width);
}
@media screen and (min-width: 768px) {
.toast {
width: var(--width);
}
}
.toast .message {