Merged in feat/SW-3644-storybook-v10 (pull request #3240)

feat(SW-3644): Storybook v10

* Auto update to Storybook v10

* Add scandic theme and logo

* Update yarn.lock

* Update formatting of package.json

* Update vitest config and playwright plugin

* Remove vitest 4 update

* Re-added comment

* Update the Typography component to explicitly return React.ReactNode

* Add an explicit type assertion to the export

* Add an explicit type assertion to the export for Checkbox

* Explicit return type assertion

* Add an explicit type assertion to the export

* Update @types/react and fix ts warnings

* Updated typings


Approved-by: Linus Flood
Approved-by: Matilda Landström
This commit is contained in:
Rasmus Langvad
2025-11-28 08:05:40 +00:00
parent 27b3f41bff
commit c65091b36a
29 changed files with 6354 additions and 4970 deletions

View File

@@ -84,7 +84,7 @@
"@eslint/js": "^9.26.0",
"@scandic-hotels/typescript-config": "workspace:*",
"@t3-oss/env-nextjs": "^0.13.4",
"@types/react": "19.1.0",
"@types/react": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"@vitest/coverage-v8": "^3.2.4",

View File

@@ -1,20 +1,19 @@
import type { StorybookConfig } from '@storybook/nextjs-vite'
import { dirname, join } from 'path'
import { mergeConfig } from 'vite'
const config: StorybookConfig = {
framework: '@storybook/nextjs-vite',
stories: ['../lib/**/*.mdx', '../lib/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
addons: [
getAbsolutePath('@storybook/addon-links'),
getAbsolutePath('@storybook/addon-themes'),
getAbsolutePath('@storybook/addon-vitest'),
getAbsolutePath('@storybook/addon-docs'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('storybook-react-intl'),
'@storybook/addon-links',
'@storybook/addon-themes',
'@storybook/addon-vitest',
'@storybook/addon-docs',
'@storybook/addon-a11y',
'storybook-react-intl',
],
framework: {
name: getAbsolutePath('@storybook/nextjs-vite'),
options: {},
core: {
disableTelemetry: true,
},
async viteFinal(config) {
return mergeConfig(config, {
@@ -48,7 +47,3 @@ const config: StorybookConfig = {
},
}
export default config
function getAbsolutePath(value: string) {
return dirname(require.resolve(join(value, 'package.json')))
}

View File

@@ -0,0 +1,6 @@
import { addons } from 'storybook/manager-api'
import scandicTheme from './scandic-theme'
addons.setConfig({
theme: scandicTheme,
})

View File

@@ -47,18 +47,23 @@ const preview: Preview = {
},
parameters: {
reactIntl,
nextjs: {
appDirectory: true,
},
docs: {
toc: true,
},
controls: { matchers: { color: /(background|color)$/i, date: /Date$/i } },
options: {
storySort: {
order: ['Introduction', 'Global', 'Components', 'Compositions', '*'],
},
},
backgrounds: {
options: {
// 👇 Scandic
@@ -70,6 +75,13 @@ const preview: Preview = {
storybookLight: { name: 'Storybook Light', value: '#F7F9F2' },
},
},
a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo',
},
},
tags: ['autodocs'],

View File

@@ -0,0 +1,8 @@
import { create } from 'storybook/theming'
export default create({
base: 'dark',
brandTitle: 'Scandic Design System',
brandUrl: 'https://www.scandichotels.com/',
brandImage: 'http://scandichotels.com/_static/img/scandic-logotype.png',
})

View File

@@ -1,4 +1,5 @@
import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview'
import { setProjectAnnotations } from '@storybook/nextjs-vite'
import * as previewAnnotations from './preview'
setProjectAnnotations([previewAnnotations])
setProjectAnnotations([a11yAddonAnnotations, previewAnnotations])

View File

@@ -16,8 +16,10 @@ import styles from './select.module.css'
import Body from '../Body'
import { Label } from '../Label'
interface SelectProps
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'onSelect'> {
interface SelectProps extends Omit<
React.SelectHTMLAttributes<HTMLSelectElement>,
'onSelect'
> {
defaultSelectedKey?: Key
items: { label: string; value: Key }[]
label: string
@@ -67,8 +69,10 @@ export default function Select({
}
}
function handleOnSelect(key: Key) {
onSelect(key)
function handleOnSelect(key: Key | null) {
if (key !== null) {
onSelect(key)
}
}
let chevronProps = {}
@@ -141,7 +145,7 @@ export default function Select({
key={`${item.value}_${item.label}`}
data-testid={item.label}
>
{optionsIcon ? optionsIcon : null}
{optionsIcon}
{item.label}
</ListBoxItem>
))}

View File

@@ -20,7 +20,7 @@ interface CheckboxProps extends React.InputHTMLAttributes<HTMLInputElement> {
errorCodeMessages?: Record<string, string>
}
const Checkbox = forwardRef<
const CheckboxComponent = forwardRef<
HTMLInputElement,
React.PropsWithChildren<CheckboxProps>
>(function Checkbox(
@@ -85,4 +85,8 @@ const Checkbox = forwardRef<
)
})
const Checkbox = CheckboxComponent as React.ForwardRefExoticComponent<
React.PropsWithChildren<CheckboxProps> & React.RefAttributes<HTMLInputElement>
>
export default Checkbox

View File

@@ -122,7 +122,7 @@ export type HotelCardProps = {
onAddressClick: () => void
}
export const HotelCard = memo(
export const HotelCardComponent = memo(
({
prices,
hotel,
@@ -385,6 +385,10 @@ export const HotelCard = memo(
}
)
export const HotelCard = HotelCardComponent as React.MemoExoticComponent<
(props: HotelCardProps) => React.ReactElement
>
interface PricesWrapperProps {
children: React.ReactNode
isClickable?: boolean

View File

@@ -97,4 +97,8 @@ function ImageGallery({
)
}
export default memo(ImageGallery)
const ImageGalleryComponent = memo(ImageGallery)
export default ImageGalleryComponent as React.MemoExoticComponent<
(props: ImageGalleryProps) => React.ReactElement
>

View File

@@ -15,7 +15,7 @@ import styles from './input.module.css'
import type { InputProps } from './types'
import { Typography } from '../Typography'
export const Input = forwardRef(function AriaInputWithLabelComponent(
const InputComponent = forwardRef(function AriaInputWithLabelComponent(
{ label, ...props }: InputProps,
forwardedRef: ForwardedRef<HTMLInputElement>
) {
@@ -44,3 +44,7 @@ export const Input = forwardRef(function AriaInputWithLabelComponent(
</AriaLabel>
)
})
export const Input = InputComponent as React.ForwardRefExoticComponent<
InputProps & React.RefAttributes<HTMLInputElement>
>

View File

@@ -82,7 +82,7 @@ export default function FullView({
opacity: 0,
x: animateLeft ? -300 : 300,
}),
}
} as const
return (
<div className={styles.fullView}>

View File

@@ -82,7 +82,7 @@ export default function Gallery({
opacity: 0,
x: animateLeft ? -300 : 300,
}),
}
} as const
return (
<div className={styles.gallery}>

View File

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

View File

@@ -7,7 +7,7 @@ export const fade = {
opacity: 1,
transition: { duration: 0.4, ease: 'easeInOut' },
},
}
} as const
export const slideInOut = {
hidden: {
@@ -20,7 +20,7 @@ export const slideInOut = {
y: 0,
transition: { duration: 0.4, ease: 'easeInOut' },
},
}
} as const
export const slideFromTop = {
hidden: {
@@ -33,4 +33,4 @@ export const slideFromTop = {
y: 0,
transition: { duration: 0.4, ease: 'easeInOut' },
},
}
} as const

View File

@@ -4,7 +4,11 @@ import { variants } from './variants'
import type { TypographyProps } from './types'
export function Typography({ variant, className, children }: TypographyProps) {
export function Typography({
variant,
className,
children,
}: TypographyProps): React.ReactNode {
if (!isValidElement(children)) return null
const classNames = variants({

View File

@@ -246,19 +246,19 @@
"devDependencies": {
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.26.0",
"@storybook/addon-a11y": "^9.1.2",
"@storybook/addon-docs": "^9.1.2",
"@storybook/addon-links": "^9.1.2",
"@storybook/addon-themes": "^9.1.2",
"@storybook/addon-vitest": "^9.1.2",
"@storybook/nextjs-vite": "^9.1.2",
"@storybook/addon-a11y": "^10.0.8",
"@storybook/addon-docs": "^10.0.8",
"@storybook/addon-links": "^10.0.8",
"@storybook/addon-themes": "^10.0.8",
"@storybook/addon-vitest": "^10.0.8",
"@storybook/nextjs-vite": "^10.0.8",
"@types/css-modules": "^1.0.5",
"@types/node": "^20.17.17",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"@vitejs/plugin-react": "^5.0.0",
"@vitejs/plugin-react": "^5.1.1",
"@vitest/browser": "^3.2.4",
"babel-plugin-formatjs": "^10.5.10",
"class-variance-authority": "^0.7.1",
@@ -270,7 +270,7 @@
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-storybook": "^9.1.2",
"eslint-plugin-storybook": "^10.0.8",
"glob": "^11.0.2",
"globals": "^16.1.0",
"husky": "^9.1.7",
@@ -282,11 +282,11 @@
"react-dom": "^19.1.0",
"rollup": "^4.40.2",
"rollup-preserve-directives": "^1.1.3",
"storybook": "^9.1.2",
"storybook-react-intl": "^4.0.7",
"storybook": "^10.0.8",
"storybook-react-intl": "^10.0.1",
"typescript": "^5.8.3",
"vite": "^6.3.5",
"vite-plugin-dts": "^4.5.3",
"vite": "^7.2.4",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-lib-inject-css": "^2.2.2",
"vitest": "^3.2.4",
"vitest-browser-react": "^1.0.1"

View File

@@ -5,7 +5,7 @@ import {
type Control,
type FieldValues,
useFormState,
type UseFromSubscribe,
type UseFormSubscribe,
} from "react-hook-form"
import {
@@ -17,7 +17,7 @@ import {
export function useFormTracking<T extends FieldValues>(
formType: FormType,
subscribe: UseFromSubscribe<T>,
subscribe: UseFormSubscribe<T>,
control: Control<T>,
nameSuffix: string = ""
) {

View File

@@ -3,6 +3,8 @@ import "server-only"
import deepmerge from "deepmerge"
import merge from "deepmerge"
import type { DocumentNode } from "graphql"
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
import { request } from "./request"
@@ -24,7 +26,11 @@ export async function batchRequest<T>(
try {
const response = await Promise.allSettled(
queries.map((query) =>
request<T>(query.document, query.variables, query.cacheOptions)
request<T>(
query.document as string | DocumentNode,
query.variables,
query.cacheOptions
)
)
)

View File

@@ -80,7 +80,7 @@ function internalRequest<T>(
...params,
signal: AbortSignal.timeout(15_000),
})
}),
}) as unknown as typeof fetch,
})
const mergedParams =

View File

@@ -77,7 +77,7 @@
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.26.0",
"@scandic-hotels/typescript-config": "workspace:*",
"@types/react": "19.1.0",
"@types/react": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.32.0",
"@typescript-eslint/parser": "^8.32.0",
"dotenv": "^16.5.0",

View File

@@ -4,13 +4,13 @@
"esModuleInterop": true,
"incremental": false,
"isolatedModules": true,
"lib": ["es2022", "DOM", "DOM.Iterable"],
"lib": ["es2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleDetection": "force",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "ES2022"
"target": "ES2023"
}
}