From 9db212feb72cb85d59b733c13f055aa47520cf42 Mon Sep 17 00:00:00 2001 From: VictoriaBeilstenEdmands <45741274+VictoriaBeilsten-Edmands@users.noreply.github.com> Date: Mon, 1 Jun 2026 15:21:38 +0100 Subject: [PATCH 01/13] Update components to new DS theme --- .storybook/preview.tsx | 11 +- .../controls/AppTitlebar.stories.tsx | 21 +-- src/components/controls/AppTitlebar.tsx | 19 +- src/components/controls/Bar.stories.tsx | 162 ++++++++---------- src/components/controls/Bar.test.tsx | 2 +- src/components/controls/Bar.tsx | 96 +++++++---- .../controls/ColourSchemeButton.tsx | 51 ++---- .../ImageColourSchemeSwitch.stories.tsx | 4 +- .../controls/ImageColourSchemeSwitch.tsx | 67 +++----- src/components/controls/Logo.stories.tsx | 2 +- src/components/controls/Logo.tsx | 58 +++++-- src/components/controls/Progress.tsx | 11 +- .../controls/ScrollableImages.stories.tsx | 7 - src/components/controls/ScrollableImages.tsx | 26 +-- src/components/controls/User.tsx | 92 ++-------- .../navigation/Breadcrumbs.stories.tsx | 11 -- src/components/navigation/Breadcrumbs.tsx | 27 +-- src/components/navigation/Footer.test.tsx | 7 +- src/components/navigation/Footer.tsx | 94 +++------- src/components/navigation/NavMenu.tsx | 85 +++++---- src/components/navigation/Navbar.stories.tsx | 10 +- src/components/navigation/Navbar.test.tsx | 2 +- src/components/navigation/Navbar.tsx | 119 ++++++------- .../{logo-light.svg => logo-dark-surface.svg} | 0 .../{logo-dark.svg => logo-light-surface.svg} | 0 .../{logo-light.svg => logo-dark-surface.svg} | 0 .../{logo-dark.svg => logo-light-surface.svg} | 0 src/storybook/theme/Logos.mdx | 12 +- src/themes/DiamondDSTheme.ts | 8 +- src/themes/DiamondOldTheme.ts | 4 +- src/themes/DiamondTheme.ts | 2 +- src/themes/GenericTheme.ts | 4 +- 32 files changed, 431 insertions(+), 583 deletions(-) rename src/public/diamond/{logo-light.svg => logo-dark-surface.svg} (100%) rename src/public/diamond/{logo-dark.svg => logo-light-surface.svg} (100%) rename src/public/generic/{logo-light.svg => logo-dark-surface.svg} (100%) rename src/public/generic/{logo-dark.svg => logo-light-surface.svg} (100%) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 3ddb8296..a03750d5 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -4,20 +4,15 @@ import type { Preview } from "@storybook/react"; import "@fontsource-variable/inter"; import "./storybook.css"; /* Storybook CSS override */ import { ThemeProvider } from "../src"; -import { GenericTheme, DiamondTheme, DiamondDSTheme } from "../src"; +import { DiamondDSTheme } from "../src"; import { ThemeSwapper, TextLight, TextDark, TextSystem } from "./ThemeSwapper"; import "../src/styles/diamondDS/diamond-ds-roles.css"; -const TextThemeBase = "Theme: Generic"; -const TextThemeDiamond = "Theme: Diamond"; const TextThemeDiamondDS = "Theme: DiamondDS"; +// Left in for now even though only a single theme function resolveTheme(selectedTheme: string) { switch (selectedTheme) { - case TextThemeBase: - return GenericTheme; - case TextThemeDiamond: - return DiamondTheme; case TextThemeDiamondDS: default: return DiamondDSTheme; @@ -66,7 +61,7 @@ const preview: Preview = { toolbar: { title: "Theme", icon: "cog", - items: [TextThemeBase, TextThemeDiamond, TextThemeDiamondDS], + items: [TextThemeDiamondDS], dynamicTitle: true, }, }, diff --git a/src/components/controls/AppTitlebar.stories.tsx b/src/components/controls/AppTitlebar.stories.tsx index 1bb6f76d..7a696998 100644 --- a/src/components/controls/AppTitlebar.stories.tsx +++ b/src/components/controls/AppTitlebar.stories.tsx @@ -50,7 +50,9 @@ export const InCentreSlot: Story = { export const DifferentBackground: Story = { args: { title: "My Great App", - sx: { backgroundColor: "red" }, + sx: { + backgroundColor: "surface.subtle", + }, }, parameters: { docs: { @@ -61,21 +63,8 @@ export const DifferentBackground: Story = { }, }; -export const DifferentColourAndLarge: Story = { +export const CustomTypography: Story = { args: { - children: ( - - ), - }, - parameters: { - docs: { - description: { - story: - "You can add styles directly to the title when it's a child or in a slot.", - }, - }, + children: , }, }; diff --git a/src/components/controls/AppTitlebar.tsx b/src/components/controls/AppTitlebar.tsx index 32723246..a85105d8 100644 --- a/src/components/controls/AppTitlebar.tsx +++ b/src/components/controls/AppTitlebar.tsx @@ -1,26 +1,14 @@ -import React from "react"; -import { styled, Typography, TypographyProps } from "@mui/material"; - +import { Typography, TypographyProps } from "@mui/material"; import { Bar, BarSlotsProps } from "./Bar"; interface AppTitleProps extends TypographyProps { title: string; } -const TypographyStyled = styled(Typography)(({ theme }) => ({ - color: theme.vars.palette.primary.contrastText, - fontSize: "2em", -})); - -/** - * A simple wrapper for a H1 title - * @param title The title to display - * @param props Additional styles, etc. - */ const AppTitle = ({ title, ...props }: AppTitleProps) => ( - + {title} - + ); interface AppTitlebarProps extends BarSlotsProps { @@ -40,4 +28,3 @@ const AppTitlebar = ({ title, children, ...props }: AppTitlebarProps) => { }; export { AppTitlebar, AppTitle }; -export type { AppTitlebarProps, AppTitleProps }; diff --git a/src/components/controls/Bar.stories.tsx b/src/components/controls/Bar.stories.tsx index 15a25174..e58c5270 100644 --- a/src/components/controls/Bar.stories.tsx +++ b/src/components/controls/Bar.stories.tsx @@ -1,5 +1,6 @@ import { Meta, StoryObj } from "@storybook/react"; -import { Bar, BarProps } from "./Bar"; +import { Typography } from "@mui/material"; +import { Bar } from "./Bar"; const meta: Meta = { title: "Components/Controls/Bar", @@ -10,24 +11,58 @@ const meta: Meta = { export default meta; type Story = StoryObj; -const Slot = ({ children }: BarProps) => ( -
- {children} -
-); +export const Default: Story = { + args: { + leftSlot: Left, + centreSlot: Centre, + rightSlot: Right, + }, +}; + +export const Primary: Story = { + args: { + color: "primary", + leftSlot: Primary Bar, + rightSlot: Actions, + }, +}; + +export const Secondary: Story = { + args: { + color: "secondary", + leftSlot: Secondary Bar, + }, +}; + +export const Subtle: Story = { + args: { + color: "primary", + variant: "subtle", + leftSlot: Subtle Primary, + }, +}; + +export const WithTitle: Story = { + args: { + color: "primary", + leftSlot: My App, + rightSlot: Controls, + }, +}; + +export const FullWidth: Story = { + args: { + containerWidth: false, + leftSlot: Full width content, + rightSlot: Right, + }, +}; export const AllSlots: Story = { args: { - leftSlot: Left Slot, - centreSlot: Centre Slot, - rightSlot: Right Slot, + leftSlot: Left, + centreSlot: Centre, + rightSlot: Right, }, parameters: { docs: { @@ -38,10 +73,10 @@ export const AllSlots: Story = { }, }; -export const Children: Story = { +export const WithChildren: Story = { args: { - leftSlot: Left Slot, - children: Children, + leftSlot: Left, + children: Children, }, parameters: { docs: { @@ -53,11 +88,20 @@ export const Children: Story = { }, }; +export const WithContent: Story = { + args: { + leftSlot: Text content, + centreSlot: , + rightSlot: , + }, +}; + export const Width: Story = { args: { - leftSlot: |<, - centreSlot: Normal width, - rightSlot: >|, + leftSlot: |<, + centreSlot: "md" Width, + rightSlot: >|, + containerWidth: "md", }, parameters: { docs: { @@ -72,9 +116,9 @@ export const Width: Story = { export const WidthMax: Story = { args: { - leftSlot: |<, - centreSlot: Max width, - rightSlot: >|, + leftSlot: |<, + centreSlot: Max Width, + rightSlot: >|, containerWidth: false, }, parameters: { @@ -89,9 +133,9 @@ export const WidthMax: Story = { export const WidthThin: Story = { args: { - leftSlot: |<, - centreSlot: "sm" width, - rightSlot: >|, + leftSlot: |<, + centreSlot: "sm" width, + rightSlot: >|, containerWidth: "sm", }, parameters: { @@ -104,65 +148,3 @@ export const WidthThin: Story = { }, }, }; - -export const Styles: Story = { - args: { - leftSlot: ( -

- Colours... -

- ), - centreSlot: ( -

- and text-size... -

- ), - rightSlot: ( -

- adjusted. -

- ), - style: { background: "#600", color: "#0df", fontSize: "larger" }, - }, - parameters: { - docs: { - description: { - story: - 'Styles are passed through to the underlining Container with the "style" parameter.', - }, - }, - }, -}; - -export const Content: Story = { - args: { - leftSlot:

My text

, - centreSlot: ( - - ), - rightSlot: , - style: { color: "white" }, - }, - parameters: { - docs: { - description: { - story: "Bars can hold anything you want", - }, - }, - }, -}; - -export const Spacing: Story = { - args: { - style: { background: "green", height: "10px" }, - }, - parameters: { - docs: { - description: { - story: "They can become spacing devices to, to add a strip of colour.", - }, - }, - }, -}; diff --git a/src/components/controls/Bar.test.tsx b/src/components/controls/Bar.test.tsx index 66f15cb3..6877ffc8 100644 --- a/src/components/controls/Bar.test.tsx +++ b/src/components/controls/Bar.test.tsx @@ -38,7 +38,7 @@ describe("Bar", () => { // check new style is set expect(headerComputedStyle.border).toBe(borderStyle); // Check default values are still set - expect(headerComputedStyle.height).toBe("auto"); + expect(headerComputedStyle.height).not.toBe("0px"); }); }); diff --git a/src/components/controls/Bar.tsx b/src/components/controls/Bar.tsx index 8f909eca..db615fcb 100644 --- a/src/components/controls/Bar.tsx +++ b/src/components/controls/Bar.tsx @@ -8,35 +8,30 @@ import { Stack, styled, } from "@mui/material"; +import { Theme } from "@mui/material/styles"; + +type IntentColour = + | "primary" + | "secondary" + | "error" + | "warning" + | "info" + | "success"; interface SlotProps extends BoxProps, React.PropsWithChildren { className: string; } -const Slot = ({ className, style, children }: SlotProps) => ( - + +const Slot = ({ className, children }: SlotProps) => ( + {children} ); -const BoxStyled = styled(Box)(({ theme }) => ({ - width: "100%", - height: "auto", - minHeight: "50px", - display: "flex", - alignItems: "center", - justifyContent: "space-between", - borderRadius: 0, - backgroundColor: theme.vars.palette.primary.main, -})); - -interface BarProps extends BoxProps, React.PropsWithChildren { +interface BarProps extends BoxProps { containerWidth?: false | Breakpoint; + color?: IntentColour; + variant?: "default" | "subtle"; } interface BarSlotsProps extends BarProps { @@ -45,6 +40,49 @@ interface BarSlotsProps extends BarProps { leftSlot?: React.ReactElement; } +const BoxStyled = styled(Box)(({ + theme, + color, + variant = "default", +}: { + theme: Theme; + color?: IntentColour; + variant?: "default" | "subtle"; +}) => { + let styles; + + if (!color) { + styles = { + backgroundColor: + variant === "subtle" + ? (theme.palette.surface?.subtle ?? theme.palette.background.paper) + : theme.palette.background.paper, + color: theme.palette.text.primary, + }; + } else { + const p = theme.palette[color]; + + styles = + variant === "subtle" + ? { + backgroundColor: p.container, + color: p.onContainer, + } + : { + backgroundColor: p.solid, + color: p.onSolid, + }; + } + + return { + width: "100%", + minHeight: 50, + display: "flex", + alignItems: "center", + ...styles, + }; +}); + /** * Basic bar. Comes with three slots, and adjustable width. Children are placed in the left slot. */ @@ -57,20 +95,12 @@ const Bar = ({ ...props }: BarSlotsProps) => ( - + {leftSlot} @@ -78,7 +108,7 @@ const Bar = ({ { - const theme = useTheme(); - const { colorScheme: colourScheme, setColorScheme: setColourScheme } = - useColorScheme(); - - if (!colourScheme) return undefined; - - const isDark = (): boolean => colourScheme === ColourSchemes.Dark; +export const ColourSchemeButton = ({ sx, ...props }: IconButtonProps) => { + const { mode, setMode } = useColorScheme(); + const isDark = mode === "dark"; return ( { - setColourScheme(isDark() ? ColourSchemes.Light : ColourSchemes.Dark); - if (props.onClick) props.onClick(event); + setMode?.(isDark ? "light" : "dark"); + props.onClick?.(event); }} + sx={[ + { + ml: 1, + color: "inherit", + "&:hover": { + backgroundColor: (theme) => theme.palette.action.hover, + }, + }, + // Had to add to satisfy typing + ...(Array.isArray(sx) ? sx : [sx]), + ]} > - {isDark() ? : } + {isDark ? : } ); }; - -export type { IconButtonProps }; -export { ColourSchemeButton }; diff --git a/src/components/controls/ImageColourSchemeSwitch.stories.tsx b/src/components/controls/ImageColourSchemeSwitch.stories.tsx index 4d72a762..47bc8f05 100644 --- a/src/components/controls/ImageColourSchemeSwitch.stories.tsx +++ b/src/components/controls/ImageColourSchemeSwitch.stories.tsx @@ -1,8 +1,8 @@ import { Meta, StoryObj } from "@storybook/react"; import { ImageColourSchemeSwitch } from "./ImageColourSchemeSwitch"; -import imageDark from "../../public/generic/logo-dark.svg"; -import imageLight from "../../public/generic/logo-light.svg"; +import imageDark from "../../public/generic/logo-dark-surface.svg"; +import imageLight from "../../public/generic/logo-light-surface.svg"; const meta: Meta = { title: "Components/Controls/ImageColourSchemeSwitch", diff --git a/src/components/controls/ImageColourSchemeSwitch.tsx b/src/components/controls/ImageColourSchemeSwitch.tsx index 70569094..0b7d14dd 100644 --- a/src/components/controls/ImageColourSchemeSwitch.tsx +++ b/src/components/controls/ImageColourSchemeSwitch.tsx @@ -1,4 +1,4 @@ -import { styled } from "@mui/material"; +import { useColorScheme } from "@mui/material"; import React from "react"; type ImageColourSchemeSwitchType = { @@ -17,64 +17,41 @@ type ImageColourSchemeSwitchType = { interface ImageColourSchemeSwitchProps { /** The definition for the two images. */ image: ImageColourSchemeSwitchType; - /** When true, the light image will appear in dark mode and vice-versa. */ - interchange?: boolean; + /** When inverse, the light image will appear in dark mode and vice-versa. */ + tone?: "default" | "inverse"; /** Additional styles to pass to the underlying img tag. */ style?: React.CSSProperties; } -/** Styled component which is only displayed in dark mode. */ -const ImageDark = styled("img")(({ theme }) => [ - { display: "none" }, - theme.applyStyles("dark", { - display: "block", - }), -]); - -/** Styled component which is only displayed in light mode. */ -const ImageLight = styled("img")(({ theme }) => [ - { display: "block" }, - theme.applyStyles("dark", { - display: "none", - }), -]); - -/** - * Switch between two different images depending on the current color scheme selected (light or dark). - */ const ImageColourSchemeSwitch = ({ image, - interchange, + tone = "default", style, -}: ImageColourSchemeSwitchProps) => - image.srcDark ? ( - <> - - - - ) : ( +}: ImageColourSchemeSwitchProps) => { + const { mode } = useColorScheme(); + const isDark = (mode ?? "light") === "dark"; + + let src = image.src; + + if (image.srcDark) { + if (tone === "inverse") { + src = image.srcDark; + } else { + src = isDark ? image.srcDark : image.src; + } + } + + return ( {image.alt} ); +}; export { ImageColourSchemeSwitch }; export type { ImageColourSchemeSwitchProps, ImageColourSchemeSwitchType }; diff --git a/src/components/controls/Logo.stories.tsx b/src/components/controls/Logo.stories.tsx index b59ec863..78b37021 100644 --- a/src/components/controls/Logo.stories.tsx +++ b/src/components/controls/Logo.stories.tsx @@ -41,7 +41,7 @@ export const TheShortLogo: Story = { export const LightLogoForDarkTheme: Story = { args: { - interchange: true, + tone: "inverse", style: { padding: "10px", background: "grey" }, }, parameters: { diff --git a/src/components/controls/Logo.tsx b/src/components/controls/Logo.tsx index ed2db8f4..97e9975e 100644 --- a/src/components/controls/Logo.tsx +++ b/src/components/controls/Logo.tsx @@ -1,26 +1,60 @@ import { BoxProps, useTheme } from "@mui/material"; -import { ImageColourSchemeSwitch } from "./ImageColourSchemeSwitch"; +import { useColorScheme } from "@mui/material"; import React from "react"; -interface LogoProps extends BoxProps { +export interface LogoProps extends BoxProps { short?: boolean; + + /** + * Tone of the logo. + * - "default": follows light/dark mode + * - "inverse": forces the inverse version + */ + tone?: "default" | "inverse"; + + /** + * @deprecated Use `tone="inverse"` instead. + * When true, forces the inverse logo. + */ interchange?: boolean; + style?: React.CSSProperties; } -const Logo = ({ short = false, interchange = false, style }: LogoProps) => { +const Logo = ({ + short = false, + tone = "default", + interchange, + style, +}: LogoProps) => { const theme = useTheme(); - const logo = - short !== undefined && short ? theme.logos?.short : theme.logos?.normal; + const { mode } = useColorScheme(); + + const logo = short ? theme.logos?.short : theme.logos?.normal; + + if (!logo) return null; + + // Keep backwards compatibility for interchange + const effectiveTone = interchange ? "inverse" : tone; + + let src = logo.src; + + if (logo.srcDark) { + if (effectiveTone === "inverse") { + src = logo.srcDark; + } else { + src = mode === "dark" ? logo.srcDark : logo.src; + } + } return ( - logo && ( - - ) + {logo.alt} ); }; diff --git a/src/components/controls/Progress.tsx b/src/components/controls/Progress.tsx index 298da8a9..6ba51e07 100644 --- a/src/components/controls/Progress.tsx +++ b/src/components/controls/Progress.tsx @@ -28,20 +28,24 @@ const Progress = (props: ProgressProps) => ( zoom: size[props.size ?? "medium"], }} > + {/* Outer */} ({ - color: theme.vars.palette.primary.light, + color: theme.palette.primary.main, + opacity: 0.2, })} size={40} thickness={9} value={100} role={undefined} /> + {/* Inner */} ({ - color: theme.vars.palette.primary.dark, + color: theme.palette.primary.main, + opacity: 0.5, position: "absolute", left: 2, top: 2, @@ -51,11 +55,12 @@ const Progress = (props: ProgressProps) => ( value={100} role={undefined} /> + {/* Spinner */} ({ - color: theme.vars.palette.secondary.main, + color: theme.palette.primary.main, animationDuration: speed[props.speed ?? "medium"], position: "absolute", left: 2.4, diff --git a/src/components/controls/ScrollableImages.stories.tsx b/src/components/controls/ScrollableImages.stories.tsx index f37734e9..f41cf54e 100644 --- a/src/components/controls/ScrollableImages.stories.tsx +++ b/src/components/controls/ScrollableImages.stories.tsx @@ -73,13 +73,6 @@ export const NoNumbers: Story = { }, }; -export const DifferentBackgroundColour: Story = { - args: { - images: imagesList, - backgroundColor: "#166", - }, -}; - export const OneImage: Story = { args: { images: imagesList[0], diff --git a/src/components/controls/ScrollableImages.tsx b/src/components/controls/ScrollableImages.tsx index 780ccfe0..28da374a 100644 --- a/src/components/controls/ScrollableImages.tsx +++ b/src/components/controls/ScrollableImages.tsx @@ -1,5 +1,12 @@ import { useCallback, useEffect, useRef, useState } from "react"; -import { Box, Button, IconButton, Slider, Stack } from "@mui/material"; +import { + Box, + Button, + IconButton, + Slider, + Stack, + useTheme, +} from "@mui/material"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; @@ -40,7 +47,7 @@ const ScrollableImages = ({ scrollStep = 320, }: ScrollableImagesProps) => { const [imageList, setImageList] = useState([]); - + const theme = useTheme(); const handleArrowKeys = (event: React.KeyboardEvent) => { if (event.key === "ArrowLeft") handlePrev(); else if (event.key === "ArrowRight") handleNext(); @@ -122,8 +129,6 @@ const ScrollableImages = ({ top: "50%", transform: "translateY(-50%)", zIndex: 2, - backgroundColor: "#4C5266", - color: "white", }} > @@ -147,8 +152,9 @@ const ScrollableImages = ({ height, flexShrink: 0, scrollSnapAlign: "start", - backgroundColor, - border: "1px solid #ccc", + backgroundColor: + backgroundColor ?? theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, display: "flex", alignItems: "center", justifyContent: "center", @@ -176,8 +182,6 @@ const ScrollableImages = ({ top: "50%", transform: "translateY(-50%)", zIndex: 2, - backgroundColor: "#4C5266", - color: "white", }} > @@ -192,6 +196,7 @@ const ScrollableImages = ({ {buttons && imageCount > 1 && ( diff --git a/src/components/navigation/Breadcrumbs.stories.tsx b/src/components/navigation/Breadcrumbs.stories.tsx index aac11e01..32181179 100644 --- a/src/components/navigation/Breadcrumbs.stories.tsx +++ b/src/components/navigation/Breadcrumbs.stories.tsx @@ -74,14 +74,3 @@ export const NoLinkComponentWithCustomPath: Story = { ], }, }; - -export const ColourChange: Story = { - args: { - path: ["first", "second", "third", "last"], - linkComponent: MockLink, - sx: { backgroundColor: "blue" }, - muiBreadcrumbsProps: { - sx: { color: "yellow" }, - }, - }, -}; diff --git a/src/components/navigation/Breadcrumbs.tsx b/src/components/navigation/Breadcrumbs.tsx index 086f9333..9e1b078a 100644 --- a/src/components/navigation/Breadcrumbs.tsx +++ b/src/components/navigation/Breadcrumbs.tsx @@ -3,7 +3,6 @@ import { Breadcrumbs as MuiBreadcrumbs, BreadcrumbsProps as MuiBreadcrumbsProps, Link as MuiLink, - styled, Typography, } from "@mui/material"; import HomeIcon from "@mui/icons-material/Home"; @@ -53,17 +52,6 @@ export function getCrumbs( }); } -const BarStyled = styled(Bar)(({ theme }) => ({ - backgroundColor: theme.vars.palette.primary.light, -})); - -const MuiBreadcrumbsStyled = styled(MuiBreadcrumbs)( - ({ theme }) => ({ - color: theme.vars.palette.primary.contrastText, - padding: 0, - }), -); - const Breadcrumbs = ({ path, linkComponent, @@ -73,8 +61,8 @@ const Breadcrumbs = ({ const crumbs: CustomLink[] = getCrumbs(path); return ( - - + } {...muiBreadcrumbsProps} @@ -105,7 +93,7 @@ const Breadcrumbs = ({ return ( {crumb.name} ); } })} - - + + ); }; diff --git a/src/components/navigation/Footer.test.tsx b/src/components/navigation/Footer.test.tsx index 63c56a55..b482ff39 100644 --- a/src/components/navigation/Footer.test.tsx +++ b/src/components/navigation/Footer.test.tsx @@ -46,7 +46,12 @@ describe("Footer logo and copyright", () => { test("Should render logo via theme", async () => { renderWithProviders(