Merged in feat/LOY-400-create-spend-points-modal (pull request #3131)

Feat/LOY-400 create spend points modal

* feat(LOY-400): Added custom button to my pages overview and skeleton file to custom modal for my points.

* feat(LOY-400): Added custom button to my pages overview and components for custom modal for my points.

* feat(LOY-400): Changed some style and infogridcardover

* feat(LOY-400):Removed custom card components and changed in infoCard: Added imagePosition top, added optional height prop. In Card: Changed Text-wrap styling, added min-width styling to buttons, added optional Icon prop, added optional height prop

* feat(LOY-400):Added linkList, LinkListItem component and messageBanner component. Added granola illustration.

* feat(LOY-400): Removed background in several illustrations. Added component for illustration. Fixed LinkedList and styling for UsePointsButton.

* feat(LOY-400): Added modal to PointsToSpendCard and fixed UsePointsButton.

* fix(LOY-400):added some styling

* feat(LOY-400): Linked Modal to contentstack and fetch the data in cards with UsePointsModal for now

* feat(LOY-400): changed link to aria-component, cleaned up a bit

* feat(LOY-400): Changed height for larger modals in mobile, fixed zod schema for no illustration input, cleaned up

* fix(LOY-400): fixed graphql after rebase

* fix(LOY-400): mini fix

* fix(LOY-400): fixed pr-comments

* fix(LOY-400): fixed some PR-comments

* fix(LOY-400): fixed a PR-comment

* feat(LOY-400): added size prop to ilustration in LinkListItem to be able to use illustrations in IllustrationByIconName

* fix(LOY-400): fixed pr-comments

* Merged in feat/LOY-402-pre-ticked-book-reward-night-in-booking-flow (pull request #3210)

Feat/LOY-402 pre ticked book reward night in booking flow

* feat(LOY-402): Changed UsePointsModal structure to handle button actions in card.

* feat(LOY-402): added functionality for book now button

* feat(LOY-400): pr comment fix

* feat(LOY-402): transformed the contentstack data

* fix(LOY-402): fixed pr comments

Approved-by: Chuma Mcphoy (We Ahead)
Approved-by: Anton Gunnarsson
Approved-by: Matilda Landström

* Merged in feat/LOY-404-add-tracking-for-spend-points-modal (pull request #3229)

Feat/LOY-404 add tracking for spend points modal

* feat(LOY-402): Changed UsePointsModal structure to handle button actions in card.

* feat(LOY-402): added functionality for book now button

* feat(LOY-400): pr comment fix

* feat(LOY-402): transformed the contentstack data

* feat(LOY-404): added tracking

* fix(LOY-404): fix for session storage removal of bookNowFromPointsModal

* feat(LOY-404): added consts

* fix(LOY-404): moved foxusWidget const

* fix(LOY-404): moved BOOKING_WIDGET_STATE const

* fix(LOY-404):fix


Approved-by: Matilda Landström

* fix(LOY-400): some fixes

* feat(LOY-400): created linkList storybook


Approved-by: Chuma Mcphoy (We Ahead)
Approved-by: Matilda Landström
This commit is contained in:
Emma Zettervall
2025-11-28 15:08:06 +00:00
parent 69f194f7bf
commit f443bae46e
54 changed files with 3631 additions and 70 deletions

View File

@@ -0,0 +1,56 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { LinkList } from './index'
import { IconName } from '../Icons/iconName'
import type { LinkListItemProps } from './LinkListItem'
const meta: Meta<typeof LinkList> = {
title: 'Components/LinkList',
component: LinkList,
argTypes: {},
}
export default meta
type Story = StoryObj<typeof LinkList>
const illustrationItems = [
{
text: 'First Link Item',
isExternal: false,
illustration: {
illustration: 'Granola' as IconName,
size: 'medium',
},
} as LinkListItemProps,
{
text: 'Second Link Item',
isExternal: true,
illustration: {
illustration: 'Coin' as IconName,
size: 'small',
},
} as LinkListItemProps,
]
const textItems = [
{
text: 'First Link Item',
isExternal: false,
} as LinkListItemProps,
{
text: 'Second Link Item',
isExternal: true,
} as LinkListItemProps,
]
export const IllustrationLinkList: Story = {
args: {
linkListItems: illustrationItems,
},
}
export const TextOnlyLinkList: Story = {
args: {
linkListItems: textItems,
},
}

View File

@@ -0,0 +1,36 @@
import { IconProps, LogoAndIllustrationProps } from '../../../Icons'
import { IconName } from '../../../Icons/iconName'
import { IllustrationByIconName } from '../../../Icons/IllustrationByIconName'
import { FC } from 'react'
export interface IllustrationIconProps extends LogoAndIllustrationProps {
illustration: IconName
size?: 'small' | 'medium' | 'large'
}
function mapIllustrationToIcon(illustration: IconName): FC<IconProps> | null {
if (!IllustrationByIconName(illustration)) return null
return IllustrationByIconName(illustration)
}
const sizeMap = {
small: { width: 60, height: 60 },
medium: { width: 80, height: 80 },
large: { width: 120, height: 120 },
} as const
export function IllustrationIcon({
illustration,
size = 'large',
...props
}: IllustrationIconProps) {
const IllustrationComponent = mapIllustrationToIcon(illustration)
if (!IllustrationComponent) return null
return (
<IllustrationComponent
{...props}
height={sizeMap[size].height}
width={sizeMap[size].width}
/>
)
}

View File

@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from '@storybook/nextjs-vite'
import { LinkListItem } from './index'
import { IconName } from '../../Icons/iconName'
const meta: Meta<typeof LinkListItem> = {
title: 'Components/LinkListItem',
component: LinkListItem,
argTypes: {
isExternal: {
control: { type: 'boolean' },
},
},
}
export default meta
type Story = StoryObj<typeof LinkListItem>
export const Default: Story = {
args: {
text: 'Link Item',
},
}
export const IllustrationItem: Story = {
args: {
text: 'Link Item',
isExternal: false,
illustration: {
illustration: 'Granola' as IconName,
size: 'medium',
},
},
}
export const TextOnlyItem: Story = {
args: {
text: 'Link Item',
isExternal: true,
},
}

View File

@@ -0,0 +1,57 @@
import { MaterialIcon } from '../../Icons/MaterialIcon'
import { Typography } from '../../Typography'
import styles from './linkListItem.module.css'
import {
IllustrationIcon,
IllustrationIconProps,
} from '../LinkListItem/IllustrationIcon'
import { cx } from 'class-variance-authority'
import { Link } from 'react-aria-components'
export interface LinkListItemProps {
text: string
isExternal?: boolean
href: string
className?: string
illustration?: IllustrationIconProps
onClick?: () => void
}
export function LinkListItem({
text,
isExternal,
href,
illustration,
onClick,
}: LinkListItemProps) {
return (
<Link
href={href}
target={'_blank'}
onClick={onClick}
className={cx(styles.content, {
[styles.graphic]: illustration,
[styles.noGraphic]: !illustration,
})}
>
{illustration && (
<div className={styles.illustrationWrapper}>
<IllustrationIcon
illustration={illustration.illustration}
size={illustration.size}
className={styles.illustration}
/>
</div>
)}
<Typography variant="Body/Paragraph/mdBold">
<p>{text}</p>
</Typography>
<MaterialIcon
color="Icon/Interactive/Default"
icon={isExternal ? 'open_in_new' : 'arrow_forward'}
/>
</Link>
)
}

View File

@@ -0,0 +1,56 @@
.list {
list-style-type: none;
}
.linkListItem {
border: 1px solid var(--Base-Border-Subtle);
background-color: var(--Surface-Primary-Default);
}
.linkListItem + .linkListItem {
border-top: none;
}
.linkListItem:first-child {
border-radius: var(--Corner-radius-md) var(--Corner-radius-md) 0 0;
}
.linkListItem:last-child {
border-radius: 0 0 var(--Corner-radius-md) var(--Corner-radius-md);
}
.linkListItem:hover {
background-color: var(--Surface-Primary-Hover);
}
.content {
display: grid;
align-items: center;
padding: var(--Space-x2);
gap: var(--Space-x2);
text-decoration: none;
color: var(--Text-Interactive-Default);
}
.graphic {
grid-template-columns: 80px 1fr auto;
}
.noGraphic {
grid-template-columns: 1fr auto;
}
.illustrationWrapper {
position: relative;
border-radius: var(--Corner-radius-sm);
background-color: var(--Surface-Primary-Hover-Accent);
width: 80px;
height: 80px;
}
.illustration {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}

View File

@@ -0,0 +1,23 @@
import { LinkListItem, type LinkListItemProps } from './LinkListItem'
import styles from './LinkListItem/linkListItem.module.css'
export interface LinkListProps {
linkListItems: LinkListItemProps[]
}
export function LinkList({ linkListItems }: LinkListProps) {
return (
<ul className={styles.list}>
{linkListItems.map((item, index) => (
<li className={styles.linkListItem} key={index}>
<LinkListItem
text={item.text}
isExternal={item.isExternal}
href={item.href}
illustration={item.illustration}
onClick={item.onClick}
/>
</li>
))}
</ul>
)
}