Merged in feat/sw-3238-move-modal-to-design-system (pull request #2628)

feat(SW-3238): Move modal to design system

* Move Modal to design-system

* Remove temp modal from booking-flow


Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2025-08-14 07:14:51 +00:00
parent dc483fe599
commit 04aebb372c
29 changed files with 72 additions and 573 deletions

View File

@@ -18,12 +18,11 @@ import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Image from "@scandic-hotels/design-system/Image"
import { Input } from "@scandic-hotels/design-system/Input"
import Modal from "@scandic-hotels/design-system/Modal"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { SAS_TRANSFER_POINT_KEY } from "@scandic-hotels/trpc/constants/partnerSAS"
import Modal from "@/components/Modal"
import styles from "./transferPoints.module.css"
import type { Lang } from "@scandic-hotels/common/constants/language"

View File

@@ -7,6 +7,7 @@ import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { ChildBedTypeEnum } from "@scandic-hotels/trpc/enums/childBedTypeEnum"
@@ -14,7 +15,6 @@ import { CancellationRuleEnum } from "@/constants/booking"
import { useBookingConfirmationStore } from "@/stores/booking-confirmation"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import Modal from "@/components/Modal"
import Breakfast from "./Breakfast"
import RoomSkeletonLoader from "./RoomSkeletonLoader"

View File

@@ -8,11 +8,11 @@ import { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import Body from "@scandic-hotels/design-system/Body"
import MagicWandIcon from "@scandic-hotels/design-system/Icons/MagicWandIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import Title from "@scandic-hotels/design-system/Title"
import Modal from "@/components/Modal"
import { useRoomContext } from "@/contexts/Details/Room"
import styles from "./modal.module.css"

View File

@@ -5,11 +5,11 @@ import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
import { getFeatureDescription } from "@/components/HotelReservation/utils/getRoomFeatureDescription"
import Modal from "@/components/Modal"
import { getMemberPrice, getPublicPrice } from "../utils"
import Breakfast from "./Breakfast"

View File

@@ -11,6 +11,7 @@ import { guaranteeCallback } from "@scandic-hotels/common/constants/routes/hotel
import { dt } from "@scandic-hotels/common/dt"
import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Divider } from "@scandic-hotels/design-system/Divider"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trpc } from "@scandic-hotels/trpc/client"
import { BreakfastPackageEnum } from "@scandic-hotels/trpc/enums/breakfast"
@@ -30,7 +31,6 @@ import {
setAncillarySessionData,
} from "@/components/HotelReservation/MyStay/utils/ancillaries"
import LoadingSpinner from "@/components/LoadingSpinner"
import Modal from "@/components/Modal"
import { toast } from "@/components/TempDesignSystem/Toasts"
import { useGuaranteeBooking } from "@/hooks/booking/useGuaranteeBooking"
import useLang from "@/hooks/useLang"

View File

@@ -8,6 +8,8 @@ import { useIntl } from "react-intl"
import { profileEdit } from "@scandic-hotels/common/constants/routes/myPages"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { ModalContentWithActions } from "@scandic-hotels/design-system/Modal/ModalContentWithActions"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { trpc } from "@scandic-hotels/trpc/client"
@@ -15,8 +17,6 @@ import { trpc } from "@scandic-hotels/trpc/client"
import { isWebview } from "@/constants/routes/webviews"
import MembershipLevelIcon from "@/components/Levels/Icon"
import Modal from "@/components/Modal"
import { ModalContentWithActions } from "@/components/Modal/ModalContentWithActions"
import { toast } from "@/components/TempDesignSystem/Toasts"
import useLang from "@/hooks/useLang"

View File

@@ -9,6 +9,7 @@ import { IconButton } from "@scandic-hotels/design-system/IconButton"
import IconChip from "@scandic-hotels/design-system/IconChip"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Image from "@scandic-hotels/design-system/Image"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
@@ -16,7 +17,6 @@ import { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
import { CancellationRuleEnum } from "@/constants/booking"
import { IconForFeatureCode } from "@/components/HotelReservation/utils"
import Modal from "@/components/Modal"
import useRateTitles from "@/hooks/booking/useRateTitles"
import useLang from "@/hooks/useLang"

View File

@@ -2,13 +2,13 @@ import { useIntl } from "react-intl"
import { IconButton } from "@scandic-hotels/design-system/IconButton"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { RateEnum } from "@scandic-hotels/trpc/enums/rate"
import { CancellationRuleEnum } from "@/constants/booking"
import { useMyStayStore } from "@/stores/my-stay"
import Modal from "@/components/Modal"
import useRateTitles from "@/hooks/booking/useRateTitles"
import Row from "../Row"

View File

@@ -3,8 +3,7 @@ import { useIntl } from "react-intl"
import { Button } from "@scandic-hotels/design-system/Button"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@/components/Modal"
import Modal from "@scandic-hotels/design-system/Modal"
import PriceDetailsTable, {
type PriceDetailsTableProps,

View File

@@ -5,11 +5,10 @@ import { formatPrice } from "@scandic-hotels/common/utils/numberFormatting"
import { Button } from "@scandic-hotels/design-system/Button"
import { Divider } from "@scandic-hotels/design-system/Divider"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { ChildBedMapEnum } from "@scandic-hotels/trpc/enums/childBedMapEnum"
import Modal from "@/components/Modal"
import { isBookingCodeRate } from "../../utils"
import { getMemberPrice } from "../utils"

View File

@@ -1,36 +0,0 @@
import type { Dispatch, JSX, SetStateAction } from "react"
export enum AnimationStateEnum {
unmounted = "unmounted",
hidden = "hidden",
visible = "visible",
}
export type AnimationState = keyof typeof AnimationStateEnum
export type ModalProps = {
onAnimationComplete?: () => void
title?: string
subtitle?: string
withActions?: boolean
hideHeader?: boolean
className?: string
} & (
| {
trigger: JSX.Element
isOpen?: never
onToggle?: never
onOpenChange?: (open: boolean) => void
}
| {
trigger?: never
isOpen: boolean
onToggle: (open: boolean) => void
onOpenChange?: never
}
)
export type InnerModalProps = Omit<ModalProps, "trigger"> & {
animation: AnimationState
setAnimation: Dispatch<SetStateAction<AnimationState>>
}

View File

@@ -1,23 +0,0 @@
export const fade = {
hidden: {
opacity: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
transition: { duration: 0.4, ease: "easeInOut" },
},
}
export const slideInOut = {
hidden: {
opacity: 0,
y: 32,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
}

View File

@@ -4,9 +4,9 @@ import { useIntl } from "react-intl"
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { Typography } from "@scandic-hotels/design-system/Typography"
import Modal from "@/components/Modal"
import useWakeLock from "@/hooks/useWakeLock"
import DigitalTeamMemberCardContent from "./Content"

View File

@@ -8,6 +8,7 @@ import Body from "@scandic-hotels/design-system/Body"
import Caption from "@scandic-hotels/design-system/Caption"
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import {
type ButtonProps,
OldDSButton as Button,
@@ -18,7 +19,6 @@ import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
import BookingFlowInput from "../../../../BookingFlowInput"
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
import Modal from "../../../../TEMP/Modal"
import { Input as BookingWidgetInput } from "../Input"
import { isMultiRoomError } from "../utils"

View File

@@ -8,12 +8,12 @@ import { useMediaQuery } from "usehooks-ts"
import Caption from "@scandic-hotels/design-system/Caption"
import Checkbox from "@scandic-hotels/design-system/Form/Checkbox"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Modal from "@scandic-hotels/design-system/Modal"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { SEARCH_TYPE_REDEMPTION } from "@scandic-hotels/trpc/constants/booking"
import { getErrorMessage } from "../../../../BookingFlowInput/errors"
import Modal from "../../../../TEMP/Modal"
import { RemoveExtraRooms } from "../BookingCode"
import { isMultiRoomError } from "../utils"

View File

@@ -1,69 +0,0 @@
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import styles from "./modalContent.module.css"
import type { ReactNode } from "react"
interface ModalContentProps {
title?: string
content: ReactNode
primaryAction: {
label: string
onClick: () => void
intent?: "primary" | "secondary" | "text"
isLoading?: boolean
disabled?: boolean
} | null
secondaryAction: {
label: string
onClick: () => void
intent?: "primary" | "secondary" | "text"
} | null
onClose?: () => void
}
export function ModalContentWithActions({
title,
content,
primaryAction,
secondaryAction,
onClose,
}: ModalContentProps) {
return (
<>
{title && (
<header className={styles.header}>
<Subtitle>{title}</Subtitle>
<button onClick={onClose} type="button" className={styles.close}>
<MaterialIcon icon="close" color="Icon/Interactive/Placeholder" />
</button>
</header>
)}
<div className={styles.content}>{content}</div>
<footer className={styles.footer}>
{secondaryAction && (
<Button
theme="base"
intent={secondaryAction.intent ?? "text"}
color="burgundy"
onClick={secondaryAction.onClick}
>
{secondaryAction.label}
</Button>
)}
{primaryAction && (
<Button
theme="base"
intent={primaryAction.intent ?? "secondary"}
onClick={primaryAction.onClick}
disabled={primaryAction.isLoading || primaryAction.disabled}
>
{primaryAction.label}
</Button>
)}
</footer>
</>
)
}

View File

@@ -1,45 +0,0 @@
.content {
display: flex;
flex-direction: column;
gap: var(--Spacing-x3);
padding: var(--Spacing-x1) var(--Spacing-x3) var(--Spacing-x4);
max-height: 70vh;
overflow-y: auto;
width: 100%;
}
.header {
display: flex;
justify-content: space-between;
width: 100%;
padding: var(--Spacing-x3) var(--Spacing-x3) var(--Spacing-x1)
var(--Spacing-x3);
}
.footer {
display: flex;
justify-content: space-between;
width: 100%;
border-top: 1px solid var(--Base-Border-Subtle);
padding: var(--Spacing-x3);
}
.close {
background: none;
border: none;
cursor: pointer;
position: absolute;
display: flex;
align-items: center;
padding: 0;
justify-content: center;
top: 20px;
right: 20px;
}
@media screen and (min-width: 768px) {
.content {
width: 640px;
max-width: 100%;
}
}

View File

@@ -1,215 +0,0 @@
"use client"
// TODO this is duplicated from scandic-web
import { cx } from "class-variance-authority"
import { AnimatePresence, motion } from "motion/react"
import { type PropsWithChildren, useEffect, useState } from "react"
import {
Dialog,
DialogTrigger,
Modal as AriaModal,
ModalOverlay,
} from "react-aria-components"
import { useIntl } from "react-intl"
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Preamble from "@scandic-hotels/design-system/Preamble"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import {
type AnimationState,
AnimationStateEnum,
type InnerModalProps,
type ModalProps,
} from "./modal"
import { fade, slideInOut } from "./motionVariants"
import { modalContentVariants } from "./variants"
import styles from "./modal.module.css"
const MotionOverlay = motion.create(ModalOverlay)
const MotionModal = motion.create(AriaModal)
function InnerModal({
animation,
onAnimationComplete = () => undefined,
setAnimation,
onToggle,
isOpen,
children,
title,
subtitle,
withActions,
hideHeader,
className,
}: PropsWithChildren<InnerModalProps>) {
const intl = useIntl()
const contentClassNames = modalContentVariants({
withActions: withActions,
})
function modalStateHandler(newAnimationState: AnimationState) {
setAnimation((currentAnimationState) =>
newAnimationState === AnimationStateEnum.hidden &&
currentAnimationState === AnimationStateEnum.hidden
? AnimationStateEnum.unmounted
: currentAnimationState
)
if (newAnimationState === AnimationStateEnum.visible) {
onAnimationComplete()
}
}
function onOpenChange(state: boolean) {
onToggle!(state)
}
return (
<MotionOverlay
animate={animation}
className={styles.overlay}
initial="hidden"
isDismissable
// TODO: Enabling this causes the modal to never unmount.
// Seems to be an issue with react-aria-components, see https://github.com/adobe/react-spectrum/issues/7563.
// Exit animations can probably be fixed by rewriting to a solution similar to
// https://react-spectrum.adobe.com/react-aria/examples/framer-modal-sheet.html.
// isExiting={animation === AnimationStateEnum.hidden}
onAnimationComplete={modalStateHandler}
variants={fade}
isOpen={isOpen}
onOpenChange={onOpenChange}
>
<MotionModal
className={cx(styles.modal, className)}
variants={slideInOut}
animate={animation}
initial="hidden"
>
<Dialog
className={styles.dialog}
aria-label={intl.formatMessage({
defaultMessage: "Dialog",
})}
>
{({ close }) => (
<>
{!hideHeader && (
<header
className={`${styles.header} ${!subtitle ? styles.verticalCenter : ""}`}
>
<div>
{title && (
<Subtitle type="one" color="uiTextHighContrast">
{title}
</Subtitle>
)}
{subtitle && (
<Preamble asChild>
<span>{subtitle}</span>
</Preamble>
)}
</div>
<button
onClick={close}
type="button"
className={styles.close}
>
<MaterialIcon icon="close" color="Icon/Feedback/Neutral" />
</button>
</header>
)}
<div className={contentClassNames}>{children}</div>
</>
)}
</Dialog>
</MotionModal>
</MotionOverlay>
)
}
export default function Modal({
onAnimationComplete = () => undefined,
trigger,
isOpen,
onToggle,
onOpenChange,
title,
subtitle,
children,
withActions = false,
hideHeader = false,
className = "",
}: PropsWithChildren<ModalProps>) {
const [animation, setAnimation] = useState<AnimationState>(
AnimationStateEnum.visible
)
useEffect(() => {
if (typeof isOpen === "boolean") {
setAnimation(
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
)
}
if (isOpen === undefined) {
setAnimation(AnimationStateEnum.unmounted)
}
}, [isOpen])
const shouldRender = isOpen || animation !== AnimationStateEnum.unmounted
if (!trigger) {
return (
<AnimatePresence>
{shouldRender && (
<InnerModal
onAnimationComplete={onAnimationComplete}
animation={animation}
setAnimation={setAnimation}
onToggle={onToggle}
isOpen={isOpen}
title={title}
subtitle={subtitle}
withActions={withActions}
hideHeader={hideHeader}
className={className}
>
{children}
</InnerModal>
)}
</AnimatePresence>
)
}
return (
<DialogTrigger
onOpenChange={(isOpen) => {
setAnimation(
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
)
onOpenChange?.(isOpen)
}}
>
{trigger}
<AnimatePresence>
{shouldRender && (
<InnerModal
onAnimationComplete={onAnimationComplete}
animation={animation}
setAnimation={setAnimation}
title={title}
subtitle={subtitle}
withActions={withActions}
className={className}
>
{children}
</InnerModal>
)}
</AnimatePresence>
</DialogTrigger>
)
}

View File

@@ -1,95 +0,0 @@
.overlay {
background: rgba(0, 0, 0, 0.5);
height: var(--visual-viewport-height);
position: fixed;
top: 0;
left: 0;
width: 100vw;
z-index: var(--default-modal-overlay-z-index);
}
.modal {
background-color: var(--Base-Surface-Primary-light-Normal);
border-radius: var(--Corner-radius-md) var(--Corner-radius-md) 0 0;
box-shadow: var(--modal-box-shadow);
width: 100%;
position: absolute;
left: 0;
bottom: 0;
z-index: var(--default-modal-z-index);
}
.dialog {
display: flex;
flex-direction: column;
/* For removing focus outline when modal opens first time */
outline: 0 none;
max-height: 100dvh;
}
.header {
--button-dimension: 32px;
box-sizing: content-box;
display: flex;
align-items: flex-start;
min-height: var(--button-dimension);
position: relative;
padding: var(--Spacing-x3) var(--Spacing-x3) 0;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--Spacing-x2);
overflow: auto;
}
.contentWithActions {
padding: 0;
}
.contentWithoutActions {
padding: 0 var(--Spacing-x3) var(--Spacing-x4);
}
.close {
background: none;
border: none;
cursor: pointer;
position: absolute;
right: var(--Spacing-x2);
width: var(--button-dimension);
height: var(--button-dimension);
display: flex;
align-items: center;
padding: 0;
justify-content: center;
}
.verticalCenter {
align-items: center;
}
@media screen and (min-width: 768px) {
.overlay {
display: flex;
justify-content: center;
align-items: center;
}
.modal {
left: auto;
bottom: auto;
width: auto;
border-radius: var(--Corner-radius-md);
max-width: var(--max-width-page);
}
.dialog {
max-height: 90dvh;
}
}

View File

@@ -1,23 +0,0 @@
export const fade = {
hidden: {
opacity: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
transition: { duration: 0.4, ease: "easeInOut" },
},
}
export const slideInOut = {
hidden: {
opacity: 0,
y: 32,
transition: { duration: 0.4, ease: "easeInOut" },
},
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: "easeInOut" },
},
}

View File

@@ -1,17 +0,0 @@
import { cva } from "class-variance-authority"
import styles from "./modal.module.css"
const config = {
variants: {
withActions: {
true: styles.contentWithActions,
false: styles.contentWithoutActions,
},
},
defaultVariants: {
withActions: false,
},
} as const
export const modalContentVariants = cva(styles.content, config)

View File

@@ -1,10 +1,10 @@
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { OldDSButton as Button } from '../../OldDSButton'
import Subtitle from '../../Subtitle'
import styles from "./modalContent.module.css"
import styles from './modalContent.module.css'
import type { ReactNode } from "react"
import type { ReactNode } from 'react'
interface ModalContentProps {
title?: string
@@ -12,14 +12,14 @@ interface ModalContentProps {
primaryAction: {
label: string
onClick: () => void
intent?: "primary" | "secondary" | "text"
intent?: 'primary' | 'secondary' | 'text'
isLoading?: boolean
disabled?: boolean
} | null
secondaryAction: {
label: string
onClick: () => void
intent?: "primary" | "secondary" | "text"
intent?: 'primary' | 'secondary' | 'text'
} | null
onClose?: () => void
}
@@ -46,7 +46,7 @@ export function ModalContentWithActions({
{secondaryAction && (
<Button
theme="base"
intent={secondaryAction.intent ?? "text"}
intent={secondaryAction.intent ?? 'text'}
color="burgundy"
onClick={secondaryAction.onClick}
>
@@ -56,7 +56,7 @@ export function ModalContentWithActions({
{primaryAction && (
<Button
theme="base"
intent={primaryAction.intent ?? "secondary"}
intent={primaryAction.intent ?? 'secondary'}
onClick={primaryAction.onClick}
disabled={primaryAction.isLoading || primaryAction.disabled}
>

View File

@@ -1,30 +1,30 @@
"use client"
'use client'
import { cx } from "class-variance-authority"
import { AnimatePresence, motion } from "motion/react"
import { type PropsWithChildren, useEffect, useState } from "react"
import { cx } from 'class-variance-authority'
import { AnimatePresence, motion } from 'motion/react'
import { type PropsWithChildren, useEffect, useState } from 'react'
import {
Dialog,
DialogTrigger,
Modal as AriaModal,
ModalOverlay,
} from "react-aria-components"
import { useIntl } from "react-intl"
} from 'react-aria-components'
import { useIntl } from 'react-intl'
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
import Preamble from "@scandic-hotels/design-system/Preamble"
import Subtitle from "@scandic-hotels/design-system/Subtitle"
import { MaterialIcon } from '../Icons/MaterialIcon'
import Preamble from '../Preamble'
import Subtitle from '../Subtitle'
import {
type AnimationState,
AnimationStateEnum,
type InnerModalProps,
type ModalProps,
} from "./modal"
import { fade, slideInOut } from "./motionVariants"
import { modalContentVariants } from "./variants"
} from './modal'
import { fade, slideInOut } from './motionVariants'
import { modalContentVariants } from './variants'
import styles from "./modal.module.css"
import styles from './modal.module.css'
const MotionOverlay = motion.create(ModalOverlay)
const MotionModal = motion.create(AriaModal)
@@ -89,14 +89,14 @@ function InnerModal({
<Dialog
className={styles.dialog}
aria-label={intl.formatMessage({
defaultMessage: "Dialog",
defaultMessage: 'Dialog',
})}
>
{({ close }) => (
<>
{!hideHeader && (
<header
className={`${styles.header} ${!subtitle ? styles.verticalCenter : ""}`}
className={`${styles.header} ${!subtitle ? styles.verticalCenter : ''}`}
>
<div>
{title && (
@@ -141,14 +141,14 @@ export default function Modal({
children,
withActions = false,
hideHeader = false,
className = "",
className = '',
}: PropsWithChildren<ModalProps>) {
const [animation, setAnimation] = useState<AnimationState>(
AnimationStateEnum.visible
)
useEffect(() => {
if (typeof isOpen === "boolean") {
if (typeof isOpen === 'boolean') {
setAnimation(
isOpen ? AnimationStateEnum.visible : AnimationStateEnum.hidden
)

View File

@@ -1,9 +1,9 @@
import type { Dispatch, JSX, SetStateAction } from "react"
import type { Dispatch, JSX, SetStateAction } from 'react'
export enum AnimationStateEnum {
unmounted = "unmounted",
hidden = "hidden",
visible = "visible",
unmounted = 'unmounted',
hidden = 'hidden',
visible = 'visible',
}
export type AnimationState = keyof typeof AnimationStateEnum
@@ -30,7 +30,7 @@ export type ModalProps = {
}
)
export type InnerModalProps = Omit<ModalProps, "trigger"> & {
export type InnerModalProps = Omit<ModalProps, 'trigger'> & {
animation: AnimationState
setAnimation: Dispatch<SetStateAction<AnimationState>>
}

View File

@@ -0,0 +1,23 @@
export const fade = {
hidden: {
opacity: 0,
transition: { duration: 0.4, ease: 'easeInOut' },
},
visible: {
opacity: 1,
transition: { duration: 0.4, ease: 'easeInOut' },
},
}
export const slideInOut = {
hidden: {
opacity: 0,
y: 32,
transition: { duration: 0.4, ease: 'easeInOut' },
},
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4, ease: 'easeInOut' },
},
}

View File

@@ -1,6 +1,6 @@
import { cva } from "class-variance-authority"
import { cva } from 'class-variance-authority'
import styles from "./modal.module.css"
import styles from './modal.module.css'
const config = {
variants: {

View File

@@ -33,6 +33,8 @@
"./Label": "./lib/components/Label/index.tsx",
"./Lightbox": "./lib/components/Lightbox/index.tsx",
"./Link": "./lib/components/Link/index.tsx",
"./Modal": "./lib/components/Modal/index.tsx",
"./Modal/ModalContentWithActions": "./lib/components/Modal/ModalContentWithActions/index.tsx",
"./OldDSButton": "./lib/components/OldDSButton/index.tsx",
"./OpeningHours": "./lib/components/OpeningHours/index.tsx",
"./Select": "./lib/components/Select/index.tsx",