From 97f9a4bb3d04988eb25a0656525ea2406d3d3b76 Mon Sep 17 00:00:00 2001 From: VictoriaBeilsten-Edmands Date: Fri, 2 May 2025 08:32:30 +0100 Subject: [PATCH 1/4] Add react router links to Breadcrumbs --- .storybook/main.ts | 1 + .storybook/preview.tsx | 2 + eslint.config.js | 1 + jest.config.js | 19 +++-- jest.setup.js | 4 + package.json | 26 ++++--- pnpm-lock.yaml | 112 ++++++++++++++++++++++++++++ src/components/Breadcrumbs.test.tsx | 88 ++++++++++++++++++++-- src/components/Breadcrumbs.tsx | 24 +++--- 9 files changed, 241 insertions(+), 36 deletions(-) create mode 100644 jest.setup.js diff --git a/.storybook/main.ts b/.storybook/main.ts index af2f7f2..a0af066 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -9,6 +9,7 @@ const config: StorybookConfig = { "@storybook/addon-interactions", "@storybook/addon-links", "storybook-dark-mode", + "storybook-addon-remix-react-router", ], framework: { name: "@storybook/react-webpack5", diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 056f01e..bfa1e20 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,6 +1,7 @@ import React from "react"; import { CssBaseline } from "@mui/material"; import type { Preview } from "@storybook/react"; +import { withRouter } from "storybook-addon-remix-react-router"; import { ThemeProvider } from "../src"; import { GenericTheme, DiamondTheme } from "../src"; @@ -11,6 +12,7 @@ const TextThemeBase = "Theme: Generic"; const TextThemeDiamond = "Theme: Diamond"; export const decorators = [ + withRouter, (StoriesWithPadding: React.FC) => { return (
diff --git a/eslint.config.js b/eslint.config.js index 5ce1737..4c3dc65 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -20,6 +20,7 @@ export default [ "babel.config.js", "eslint.config.js", "jest.config.js", + "jest.setup.js", "rollup.config.mjs", ]}, js.configs.recommended, diff --git a/jest.config.js b/jest.config.js index 3faca5f..88f3a2a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,10 +1,13 @@ /** @type {import('jest').Config} */ -const config = { testEnvironment: "jsdom", -moduleNameMapper: { - "^.+.(svg)$": "jest-transform-stub", -}, -transform: { -"^.+\\.tsx?$": "babel-jest" -}}; +const config = { + testEnvironment: "jsdom", + moduleNameMapper: { + "^.+.(svg)$": "jest-transform-stub", + }, + transform: { + "^.+\\.tsx?$": "babel-jest", + }, + setupFilesAfterEnv: ['./jest.setup.js'], +}; -export default config; \ No newline at end of file +export default config; diff --git a/jest.setup.js b/jest.setup.js new file mode 100644 index 0000000..e1d9e10 --- /dev/null +++ b/jest.setup.js @@ -0,0 +1,4 @@ +const { TextEncoder, TextDecoder } = require('util'); + +global.TextEncoder = TextEncoder; +global.TextDecoder = TextDecoder; diff --git a/package.json b/package.json index 1f412b4..8ca4943 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,15 @@ "storybook:publish": "gh-pages -b storybook/publish -d storybook-static" }, "dependencies": { - "react-icons": "^5.3.0" + "react-icons": "^5.3.0", + "react-router-dom": "^7.5.3", + "storybook-addon-remix-react-router": "3" }, "peerDependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@mui/material": "^6.1.7", "@mui/icons-material": "^6.1.7", + "@mui/material": "^6.1.7", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -40,6 +42,7 @@ "@babel/preset-react": "^7.25.9", "@babel/preset-typescript": "^7.26.0", "@chromatic-com/storybook": "^3.2.2", + "@eslint/eslintrc": "^3.2.0", "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-image": "^3.0.3", "@rollup/plugin-json": "^6.1.0", @@ -63,11 +66,19 @@ "@types/node": "^20.17.6", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "@typescript-eslint/eslint-plugin": "^8.18.1", + "@typescript-eslint/parser": "^8.18.1", "babel-jest": "^29.7.0", + "eslint": "^9.17.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.1.0", "file-loader": "^6.2.0", "gh-pages": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "jest-transform-stub": "^2.0.0", "rollup": "^4.27.3", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-peer-deps-external": "^2.2.4", @@ -76,16 +87,7 @@ "storybook-dark-mode": "^4.0.2", "tslib": "^2.8.1", "typescript": "^5.6.3", - "typescript-eslint": "^8.15.0", - "eslint": "^9.17.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.1.0", - "jest-transform-stub": "^2.0.0", - "@typescript-eslint/eslint-plugin": "^8.18.1", - "@typescript-eslint/parser": "^8.18.1", - "@eslint/eslintrc": "^3.2.0" + "typescript-eslint": "^8.15.0" }, "packageManager": "pnpm@9.12.3+sha256.24235772cc4ac82a62627cd47f834c72667a2ce87799a846ec4e8e555e2d4b8b" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 352d342..29fc513 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,12 @@ importers: react-icons: specifier: ^5.3.0 version: 5.4.0(react@18.3.1) + react-router-dom: + specifier: ^7.5.3 + version: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + storybook-addon-remix-react-router: + specifier: '3' + version: 3.1.0(@storybook/blocks@8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)))(@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2)))(@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/core-events@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/manager-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/preview-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/theming@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) devDependencies: '@babel/core': specifier: ^7.26.0 @@ -1543,6 +1549,11 @@ packages: typescript: optional: true + '@storybook/channels@8.6.12': + resolution: {integrity: sha512-31hhRuAcEJjb8whU9nlbStD/5SK2lPW3dDfJLg8+LqHh8MadXKPXbZYG08oETrP53jZadVPhhZU6th1duF2Gcg==} + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + '@storybook/components@8.4.7': resolution: {integrity: sha512-uyJIcoyeMWKAvjrG9tJBUCKxr2WZk+PomgrgrUwejkIfXMO76i6jw9BwLa0NZjYdlthDv30r9FfbYZyeNPmF0g==} peerDependencies: @@ -2360,6 +2371,9 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + compare-versions@6.1.1: + resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2375,6 +2389,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + core-js-compat@3.39.0: resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} @@ -4178,6 +4196,11 @@ packages: peerDependencies: react: '*' + react-inspector@6.0.2: + resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} + peerDependencies: + react: ^16.8.4 || ^17.0.0 || ^18.0.0 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4190,6 +4213,23 @@ packages: react-is@19.0.0: resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-router-dom@7.5.3: + resolution: {integrity: sha512-cK0jSaTyW4jV9SRKAItMIQfWZ/D6WEZafgHuuCb9g+SjhLolY78qc+De4w/Cz9ybjvLzShAmaIMEXt8iF1Cm+A==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.5.3: + resolution: {integrity: sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react-transition-group@4.4.5: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -4373,6 +4413,9 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4451,6 +4494,25 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + storybook-addon-remix-react-router@3.1.0: + resolution: {integrity: sha512-h6cOD+afyAddNrDz5ezoJGV6GBSeH7uh92VAPDz+HLuay74Cr9Ozz+aFmlzMEyVJ1hhNIMOIWDsmK56CueZjsw==} + peerDependencies: + '@storybook/blocks': ^8.0.0 + '@storybook/channels': ^8.0.0 + '@storybook/components': ^8.0.0 + '@storybook/core-events': ^8.0.0 + '@storybook/manager-api': ^8.0.0 + '@storybook/preview-api': ^8.0.0 + '@storybook/theming': ^8.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-router-dom: ^6.4.0 || ^7.0.0 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + storybook-dark-mode@4.0.2: resolution: {integrity: sha512-zjcwwQ01R5t1VsakA6alc2JDIRVtavryW8J3E3eKLDIlAMcvsgtpxlelWkZs2cuNspk6Z10XzhQVrUWtYc3F0w==} @@ -4653,6 +4715,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + turbo-stream@2.4.0: + resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==} + tween-functions@1.2.0: resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==} @@ -6502,6 +6567,10 @@ snapshots: - uglify-js - webpack-cli + '@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2))': + dependencies: + storybook: 8.4.7(prettier@3.4.2) + '@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2))': dependencies: storybook: 8.4.7(prettier@3.4.2) @@ -7465,6 +7534,8 @@ snapshots: commondir@1.0.1: {} + compare-versions@6.1.1: {} + concat-map@0.0.1: {} concat-with-sourcemaps@1.1.0: @@ -7477,6 +7548,8 @@ snapshots: convert-source-map@2.0.0: {} + cookie@1.0.2: {} + core-js-compat@3.39.0: dependencies: browserslist: 4.24.3 @@ -9627,6 +9700,10 @@ snapshots: dependencies: react: 18.3.1 + react-inspector@6.0.2(react@18.3.1): + dependencies: + react: 18.3.1 + react-is@16.13.1: {} react-is@17.0.2: {} @@ -9635,6 +9712,21 @@ snapshots: react-is@19.0.0: {} + react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-router@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + cookie: 1.0.2 + react: 18.3.1 + set-cookie-parser: 2.7.1 + turbo-stream: 2.4.0 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@babel/runtime': 7.26.0 @@ -9874,6 +9966,8 @@ snapshots: dependencies: randombytes: 2.1.0 + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -9962,6 +10056,22 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + storybook-addon-remix-react-router@3.1.0(@storybook/blocks@8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)))(@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2)))(@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/core-events@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/manager-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/preview-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/theming@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + dependencies: + '@storybook/blocks': 8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)) + '@storybook/channels': 8.6.12(storybook@8.4.7(prettier@3.4.2)) + '@storybook/components': 8.4.7(storybook@8.4.7(prettier@3.4.2)) + '@storybook/core-events': 8.4.7(storybook@8.4.7(prettier@3.4.2)) + '@storybook/manager-api': 8.4.7(storybook@8.4.7(prettier@3.4.2)) + '@storybook/preview-api': 8.4.7(storybook@8.4.7(prettier@3.4.2)) + '@storybook/theming': 8.4.7(storybook@8.4.7(prettier@3.4.2)) + compare-versions: 6.1.1 + react-inspector: 6.0.2(react@18.3.1) + react-router-dom: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + storybook-dark-mode@4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)): dependencies: '@storybook/components': 8.4.7(storybook@8.4.7(prettier@3.4.2)) @@ -10187,6 +10297,8 @@ snapshots: tslib@2.8.1: {} + turbo-stream@2.4.0: {} + tween-functions@1.2.0: {} type-check@0.4.0: diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/Breadcrumbs.test.tsx index 7510eba..41efb09 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/Breadcrumbs.test.tsx @@ -1,4 +1,10 @@ -import { render, RenderResult } from "@testing-library/react"; +import { + fireEvent, + render, + RenderResult, + screen, +} from "@testing-library/react"; +import { MemoryRouter, Route, Routes } from "react-router-dom"; import { Breadcrumbs, getCrumbs } from "./Breadcrumbs"; import "@testing-library/jest-dom"; import { CustomLink } from "types/links"; @@ -16,6 +22,7 @@ const crumbFirst = "first", { name: `${crumbSecondTitle}`, href: `/${crumbSecond}` }, { name: `${crumbLastTitle}`, href: "/" }, ]; + describe("Breadcrumbs", () => { function testHomeExists(renderResult: RenderResult) { const { getByTestId } = renderResult; @@ -47,32 +54,58 @@ describe("Breadcrumbs", () => { } it("should render without errors", () => { - render(); + render( + + + , + ); }); it("should show just home when an empty string", () => { - const renderResult = render(); + const renderResult = render( + + + , + ); testHomeExists(renderResult); expect(renderResult.getAllByRole("link")).toHaveLength(1); }); it("should show just home when an empty array", () => { - const renderResult = render(); + const renderResult = render( + + + , + ); testHomeExists(renderResult); expect(renderResult.getAllByRole("link")).toHaveLength(1); }); it("should use path as string", () => { - testCrumbsExist(render()); + testCrumbsExist( + render( + + + , + ), + ); }); it("should use path as string array", () => { - testCrumbsExist(render()); + testCrumbsExist( + render( + + + , + ), + ); }); it("should use path as object array", () => { const { getByRole, queryByRole, getByText } = render( - , + + + , ); let crumb = getByRole("link", { name: crumbFirstTitle }); expect(crumb).toBeInTheDocument(); @@ -87,6 +120,47 @@ describe("Breadcrumbs", () => { ).not.toBeInTheDocument(); expect(getByText(crumbLastTitle)).toBeInTheDocument(); }); + + it("should navigate to the correct route when a breadcrumb link is clicked", () => { + render( + + + Home Page
} /> + First Page} /> + Second Page} + /> + } + /> + + , + ); + fireEvent.click(screen.getByRole("link", { name: "Second" })); + expect(screen.getByText("Second Page")).toBeInTheDocument(); + }); + + it("should render correctly with different routes", () => { + const { rerender } = render( + + + , + ); + + expect(screen.getByRole("link", { name: "First" })).toBeInTheDocument(); + expect(screen.getByText("Second")).toBeInTheDocument(); + rerender( + + + , + ); + + expect(screen.getByRole("link", { name: "First" })).toBeInTheDocument(); + expect(screen.getByRole("link", { name: "Second" })).toBeInTheDocument(); + expect(screen.getByText("Last one")).toBeInTheDocument(); + }); }); describe("getCrumbs", () => { diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index b34671e..384765f 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -3,16 +3,15 @@ import { Breadcrumbs as Mui_Breadcrumbs, BreadcrumbsProps as Mui_BreadcrumbsProps, Container, - Link, + Link as MuiLink, Paper, PaperProps, Typography, useTheme, } from "@mui/material"; - +import { Link } from "react-router-dom"; import HomeIcon from "@mui/icons-material/Home"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; - import { CustomLink } from "types/links"; interface BreadcrumbsProps { @@ -23,7 +22,7 @@ interface BreadcrumbsProps { /** * Create CrumbData from crumb parts with links - * @param path A single string path, an array of string parts or and array of CustomLink parts + * @param path A single string path, an array of string parts or an array of CustomLink parts */ export function getCrumbs( path: string | string[] | CustomLink[], @@ -79,22 +78,29 @@ const Breadcrumbs = ({ sx={{ color: theme.palette.primary.contrastText }} {...muiBreadcrumbsProps} > - + - + {crumbs.map((crumb, i, all) => { if (i < all.length - 1) return ( - {crumb.name} - + ); else { return ( From efb296e22f3342e73c7a66bf8a4f8fe98760fce5 Mon Sep 17 00:00:00 2001 From: VictoriaBeilsten-Edmands Date: Tue, 6 May 2025 15:08:22 +0100 Subject: [PATCH 2/4] Add SciReactUIProvider to use ThemeProvider and BrowserRouter --- changelog.md | 4 ++-- package.json | 8 ++++---- pnpm-lock.yaml | 6 +++--- readme.md | 8 ++++---- src/components/Breadcrumbs.tsx | 10 +++++----- src/themes/SciReactUIProvider.tsx | 15 +++++++++++++++ 6 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 src/themes/SciReactUIProvider.tsx diff --git a/changelog.md b/changelog.md index e386614..dae51c2 100644 --- a/changelog.md +++ b/changelog.md @@ -6,13 +6,13 @@ SciReactUI Changelog -------------------- ### Added -- +- SciReactUIProvider to use ThemeProvider and BrowserRouter. ### Fixed - Styles added to Navbar and Footer incorrectly remove built in styles. ### Changed -- +- Breadcrumbs component uses react-router-dom for page routing. [v0.1.0] - 2025-04-10 --------------------- diff --git a/package.json b/package.json index 8ca4943..3fea780 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,7 @@ "storybook:publish": "gh-pages -b storybook/publish -d storybook-static" }, "dependencies": { - "react-icons": "^5.3.0", - "react-router-dom": "^7.5.3", - "storybook-addon-remix-react-router": "3" + "react-icons": "^5.3.0" }, "peerDependencies": { "@emotion/react": "^11.13.3", @@ -34,7 +32,8 @@ "@mui/icons-material": "^6.1.7", "@mui/material": "^6.1.7", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^7.5.3" }, "devDependencies": { "@babel/core": "^7.26.0", @@ -84,6 +83,7 @@ "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-postcss": "^4.0.2", "storybook": "^8.4.4", + "storybook-addon-remix-react-router": "3", "storybook-dark-mode": "^4.0.2", "tslib": "^2.8.1", "typescript": "^5.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29fc513..383e553 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,9 +32,6 @@ importers: react-router-dom: specifier: ^7.5.3 version: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - storybook-addon-remix-react-router: - specifier: '3' - version: 3.1.0(@storybook/blocks@8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)))(@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2)))(@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/core-events@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/manager-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/preview-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/theming@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) devDependencies: '@babel/core': specifier: ^7.26.0 @@ -177,6 +174,9 @@ importers: storybook: specifier: ^8.4.4 version: 8.4.7(prettier@3.4.2) + storybook-addon-remix-react-router: + specifier: '3' + version: 3.1.0(@storybook/blocks@8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)))(@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2)))(@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/core-events@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/manager-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/preview-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/theming@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) storybook-dark-mode: specifier: ^4.0.2 version: 4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)) diff --git a/readme.md b/readme.md index 8545231..ca2fc9c 100644 --- a/readme.md +++ b/readme.md @@ -19,18 +19,18 @@ npm i @diamondlightsource/sci-react-ui ### Usage -First use the ThemeProvider and supply a theme. +First use SciReactUIProvider and supply a theme to use the ThemeProvider and BrowserRouter. ```js import { - ThemeProvider, + SciReactUIProvider, DiamondTheme } from "@diamondlightsource/sci-react-ui"; root.render( - + - + ) ``` diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index 384765f..3231c82 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -3,7 +3,7 @@ import { Breadcrumbs as Mui_Breadcrumbs, BreadcrumbsProps as Mui_BreadcrumbsProps, Container, - Link as MuiLink, + Link as Mui_Link, Paper, PaperProps, Typography, @@ -78,7 +78,7 @@ const Breadcrumbs = ({ sx={{ color: theme.palette.primary.contrastText }} {...muiBreadcrumbsProps} > - - + {crumbs.map((crumb, i, all) => { if (i < all.length - 1) return ( - {crumb.name} - + ); else { return ( diff --git a/src/themes/SciReactUIProvider.tsx b/src/themes/SciReactUIProvider.tsx new file mode 100644 index 0000000..cfec935 --- /dev/null +++ b/src/themes/SciReactUIProvider.tsx @@ -0,0 +1,15 @@ +import * as React from "react"; +import { BrowserRouter } from "react-router-dom"; +import { ThemeProvider, ThemeProviderProps } from "./ThemeProvider"; + +type SciReactUIProviderProps = React.PropsWithChildren; + +export const SciReactUIProvider: React.FC = ( + props, +) => { + return ( + + {props.children} + + ); +}; From 1a36fe7113e71c9fdece804c351f3997db39cc02 Mon Sep 17 00:00:00 2001 From: VictoriaBeilstenEdmands Date: Thu, 15 May 2025 14:06:53 +0100 Subject: [PATCH 3/4] Use LinkComponent prop in Breadcrumbs & reorganise components --- .storybook/main.ts | 1 - .storybook/preview.tsx | 7 +- changelog.md | 6 +- eslint.config.js | 1 + package.json | 9 +- pnpm-lock.yaml | 73 +--------- readme.md | 81 +++++++---- src/components/Footer.test.tsx | 127 ------------------ .../ColourSchemeButton.stories.tsx | 0 .../ColourSchemeButton.test.tsx | 4 +- .../{ => controls}/ColourSchemeButton.tsx | 2 +- .../ImageColorSchemeSwitch.stories.tsx | 4 +- .../ImageColorSchemeSwitch.test.tsx | 2 +- .../{ => controls}/ImageColorSchemeSwitch.tsx | 0 .../{ => controls}/User.stories.tsx | 0 src/components/{ => controls}/User.test.tsx | 2 +- src/components/{ => controls}/User.tsx | 0 .../{ => controls}/VisitInput.stories.tsx | 2 +- .../{ => controls}/VisitInput.test.tsx | 0 src/components/{ => controls}/VisitInput.tsx | 7 +- .../{ => navigation}/Breadcrumbs.stories.tsx | 32 +++++ .../{ => navigation}/Breadcrumbs.test.tsx | 22 +-- .../{ => navigation}/Breadcrumbs.tsx | 7 +- .../{ => navigation}/Footer.stories.tsx | 0 src/components/navigation/Footer.test.tsx | 101 ++++++++++++++ src/components/{ => navigation}/Footer.tsx | 15 ++- .../{ => navigation}/Navbar.stories.tsx | 8 +- .../{ => navigation}/Navbar.test.tsx | 2 +- src/components/{ => navigation}/Navbar.tsx | 12 +- src/index.ts | 17 ++- src/storybook/theme/Logos.mdx | 2 +- src/themes/BaseTheme.ts | 2 +- src/themes/SciReactUIProvider.tsx | 15 --- 33 files changed, 273 insertions(+), 290 deletions(-) delete mode 100644 src/components/Footer.test.tsx rename src/components/{ => controls}/ColourSchemeButton.stories.tsx (100%) rename src/components/{ => controls}/ColourSchemeButton.test.tsx (91%) rename src/components/{ => controls}/ColourSchemeButton.tsx (96%) rename src/components/{ => controls}/ImageColorSchemeSwitch.stories.tsx (92%) rename src/components/{ => controls}/ImageColorSchemeSwitch.test.tsx (96%) rename src/components/{ => controls}/ImageColorSchemeSwitch.tsx (100%) rename src/components/{ => controls}/User.stories.tsx (100%) rename src/components/{ => controls}/User.test.tsx (97%) rename src/components/{ => controls}/User.tsx (100%) rename src/components/{ => controls}/VisitInput.stories.tsx (96%) rename src/components/{ => controls}/VisitInput.test.tsx (100%) rename src/components/{ => controls}/VisitInput.tsx (96%) rename src/components/{ => navigation}/Breadcrumbs.stories.tsx (58%) rename src/components/{ => navigation}/Breadcrumbs.test.tsx (90%) rename src/components/{ => navigation}/Breadcrumbs.tsx (95%) rename src/components/{ => navigation}/Footer.stories.tsx (100%) create mode 100644 src/components/navigation/Footer.test.tsx rename src/components/{ => navigation}/Footer.tsx (86%) rename src/components/{ => navigation}/Navbar.stories.tsx (93%) rename src/components/{ => navigation}/Navbar.test.tsx (96%) rename src/components/{ => navigation}/Navbar.tsx (92%) delete mode 100644 src/themes/SciReactUIProvider.tsx diff --git a/.storybook/main.ts b/.storybook/main.ts index a0af066..af2f7f2 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -9,7 +9,6 @@ const config: StorybookConfig = { "@storybook/addon-interactions", "@storybook/addon-links", "storybook-dark-mode", - "storybook-addon-remix-react-router", ], framework: { name: "@storybook/react-webpack5", diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index bfa1e20..5e6c52c 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,7 +1,6 @@ import React from "react"; import { CssBaseline } from "@mui/material"; import type { Preview } from "@storybook/react"; -import { withRouter } from "storybook-addon-remix-react-router"; import { ThemeProvider } from "../src"; import { GenericTheme, DiamondTheme } from "../src"; @@ -12,7 +11,6 @@ const TextThemeBase = "Theme: Generic"; const TextThemeDiamond = "Theme: Diamond"; export const decorators = [ - withRouter, (StoriesWithPadding: React.FC) => { return (
@@ -78,6 +76,11 @@ const preview: Preview = { backgrounds: { disable: true }, layout: "fullscreen", }, + argTypes: { + linkComponent: { + control: false, + }, + }, }; export default preview; diff --git a/changelog.md b/changelog.md index dae51c2..e1fa369 100644 --- a/changelog.md +++ b/changelog.md @@ -2,17 +2,17 @@ SciReactUI Changelog ==================== -[v0.1.1] - 2025-?-? +[v0.2.0] - 2025-?-? -------------------- ### Added -- SciReactUIProvider to use ThemeProvider and BrowserRouter. +- ### Fixed - Styles added to Navbar and Footer incorrectly remove built in styles. ### Changed -- Breadcrumbs component uses react-router-dom for page routing. +- Breaking change Breadcrumbs component requires linkComponent prop for page routing. [v0.1.0] - 2025-04-10 --------------------- diff --git a/eslint.config.js b/eslint.config.js index 4c3dc65..b9df24c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -41,6 +41,7 @@ export default [ }, rules: { "react/react-in-jsx-scope": "off", + "react/prop-types": "off", "no-console": "off", "prettier/prettier": "error", "@typescript-eslint/no-unused-vars": [ diff --git a/package.json b/package.json index 3fea780..3eaea1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@diamondlightsource/sci-react-ui", - "version": "0.1.1alpha", + "version": "0.2", "description": "A theme and component library to make websites at scientific institutions simple to create.", "author": "Diamond Light Source", "license": "ISC", @@ -31,9 +31,7 @@ "@emotion/styled": "^11.13.0", "@mui/icons-material": "^6.1.7", "@mui/material": "^6.1.7", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^7.5.3" + "react": "^18.3.1" }, "devDependencies": { "@babel/core": "^7.26.0", @@ -78,12 +76,13 @@ "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "jest-transform-stub": "^2.0.0", + "react-dom": "^18.3.1", + "react-router-dom": "^7.5.3", "rollup": "^4.27.3", "rollup-plugin-dts": "^6.1.1", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-postcss": "^4.0.2", "storybook": "^8.4.4", - "storybook-addon-remix-react-router": "3", "storybook-dark-mode": "^4.0.2", "tslib": "^2.8.1", "typescript": "^5.6.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 383e553..529ee44 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,15 +23,9 @@ importers: react: specifier: ^18.3.1 version: 18.3.1 - react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) react-icons: specifier: ^5.3.0 version: 5.4.0(react@18.3.1) - react-router-dom: - specifier: ^7.5.3 - version: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) devDependencies: '@babel/core': specifier: ^7.26.0 @@ -159,6 +153,12 @@ importers: jest-transform-stub: specifier: ^2.0.0 version: 2.0.0 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + react-router-dom: + specifier: ^7.5.3 + version: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) rollup: specifier: ^4.27.3 version: 4.30.0 @@ -174,9 +174,6 @@ importers: storybook: specifier: ^8.4.4 version: 8.4.7(prettier@3.4.2) - storybook-addon-remix-react-router: - specifier: '3' - version: 3.1.0(@storybook/blocks@8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)))(@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2)))(@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/core-events@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/manager-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/preview-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/theming@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) storybook-dark-mode: specifier: ^4.0.2 version: 4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)) @@ -1549,11 +1546,6 @@ packages: typescript: optional: true - '@storybook/channels@8.6.12': - resolution: {integrity: sha512-31hhRuAcEJjb8whU9nlbStD/5SK2lPW3dDfJLg8+LqHh8MadXKPXbZYG08oETrP53jZadVPhhZU6th1duF2Gcg==} - peerDependencies: - storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/components@8.4.7': resolution: {integrity: sha512-uyJIcoyeMWKAvjrG9tJBUCKxr2WZk+PomgrgrUwejkIfXMO76i6jw9BwLa0NZjYdlthDv30r9FfbYZyeNPmF0g==} peerDependencies: @@ -2371,9 +2363,6 @@ packages: commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - compare-versions@6.1.1: - resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -4196,11 +4185,6 @@ packages: peerDependencies: react: '*' - react-inspector@6.0.2: - resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} - peerDependencies: - react: ^16.8.4 || ^17.0.0 || ^18.0.0 - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -4494,25 +4478,6 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} - storybook-addon-remix-react-router@3.1.0: - resolution: {integrity: sha512-h6cOD+afyAddNrDz5ezoJGV6GBSeH7uh92VAPDz+HLuay74Cr9Ozz+aFmlzMEyVJ1hhNIMOIWDsmK56CueZjsw==} - peerDependencies: - '@storybook/blocks': ^8.0.0 - '@storybook/channels': ^8.0.0 - '@storybook/components': ^8.0.0 - '@storybook/core-events': ^8.0.0 - '@storybook/manager-api': ^8.0.0 - '@storybook/preview-api': ^8.0.0 - '@storybook/theming': ^8.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-router-dom: ^6.4.0 || ^7.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - storybook-dark-mode@4.0.2: resolution: {integrity: sha512-zjcwwQ01R5t1VsakA6alc2JDIRVtavryW8J3E3eKLDIlAMcvsgtpxlelWkZs2cuNspk6Z10XzhQVrUWtYc3F0w==} @@ -6567,10 +6532,6 @@ snapshots: - uglify-js - webpack-cli - '@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2))': - dependencies: - storybook: 8.4.7(prettier@3.4.2) - '@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2))': dependencies: storybook: 8.4.7(prettier@3.4.2) @@ -7534,8 +7495,6 @@ snapshots: commondir@1.0.1: {} - compare-versions@6.1.1: {} - concat-map@0.0.1: {} concat-with-sourcemaps@1.1.0: @@ -9700,10 +9659,6 @@ snapshots: dependencies: react: 18.3.1 - react-inspector@6.0.2(react@18.3.1): - dependencies: - react: 18.3.1 - react-is@16.13.1: {} react-is@17.0.2: {} @@ -10056,22 +10011,6 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 - storybook-addon-remix-react-router@3.1.0(@storybook/blocks@8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)))(@storybook/channels@8.6.12(storybook@8.4.7(prettier@3.4.2)))(@storybook/components@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/core-events@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/manager-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/preview-api@8.4.7(storybook@8.4.7(prettier@3.4.2)))(@storybook/theming@8.4.7(storybook@8.4.7(prettier@3.4.2)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): - dependencies: - '@storybook/blocks': 8.4.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)) - '@storybook/channels': 8.6.12(storybook@8.4.7(prettier@3.4.2)) - '@storybook/components': 8.4.7(storybook@8.4.7(prettier@3.4.2)) - '@storybook/core-events': 8.4.7(storybook@8.4.7(prettier@3.4.2)) - '@storybook/manager-api': 8.4.7(storybook@8.4.7(prettier@3.4.2)) - '@storybook/preview-api': 8.4.7(storybook@8.4.7(prettier@3.4.2)) - '@storybook/theming': 8.4.7(storybook@8.4.7(prettier@3.4.2)) - compare-versions: 6.1.1 - react-inspector: 6.0.2(react@18.3.1) - react-router-dom: 7.5.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - optionalDependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - storybook-dark-mode@4.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.7(prettier@3.4.2)): dependencies: '@storybook/components': 8.4.7(storybook@8.4.7(prettier@3.4.2)) diff --git a/readme.md b/readme.md index ca2fc9c..92018f6 100644 --- a/readme.md +++ b/readme.md @@ -19,45 +19,78 @@ npm i @diamondlightsource/sci-react-ui ### Usage -First use SciReactUIProvider and supply a theme to use the ThemeProvider and BrowserRouter. +First use the ThemeProvider and supply a theme. ```js +import { ThemeProvider, DiamondTheme } from "@diamondlightsource/sci-react-ui"; + +root.render( + + + , +); +``` + +There are currently two themes, `GenericTheme` or `DiamondTheme`, but you can - and should - adapt your own. + +To use the Breadcrumbs component, use a route provider from your preferred library. For example, to use react-router's BrowserRouter, install react-router-dom: + +```sh +npm i react-router-dom +``` + +Next, use the BrowserRouter which can be used at the top level: + +```js +import { BrowserRouter } from "react-router-dom"; import { - SciReactUIProvider, + ThemeProvider, DiamondTheme } from "@diamondlightsource/sci-react-ui"; root.render( - - - + + + + + ) ``` -There are currently two themes, `GenericTheme` or `DiamondTheme`, but you can - and should - adapt your own. +Then pass your library's corresponding Link component to Breadcrumbs, for example: -There are various components, here's an example of how to use the NavBar: +```js +import { Link } from "react-router-dom"; +import { Breadcrumbs } from "@diamondlightsource/sci-react-ui"; + +function App() { + return ; +} +export default App; +``` + +There are various other components, here's an example of how to use the NavBar: ```js -import {Container, Typography} from "@mui/material"; -import { - Navbar, - NavLink, - NavLinks -} from "@diamondlightsource/sci-react-ui"; +import { Container, Typography } from "@mui/material"; +import { Navbar, NavLink, NavLinks } from "@diamondlightsource/sci-react-ui"; function App() { - return <> - - - A link - - - - Scientific UI Collection - A collection of science based React components. - - + return ( + <> + + + + A link + + + + + Scientific UI Collection + A collection of science based React components. + + + ); } export default App; ``` diff --git a/src/components/Footer.test.tsx b/src/components/Footer.test.tsx deleted file mode 100644 index 6bf319d..0000000 --- a/src/components/Footer.test.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { screen, waitFor } from "@testing-library/react"; -import "@testing-library/jest-dom"; - -import dlsLogo from "../public/generic/logo-short.svg"; -import { Footer, FooterLink, FooterLinks } from "./Footer"; -import { ImageColorSchemeSwitch } from "./ImageColorSchemeSwitch"; -import { renderWithProviders } from "../__test-utils__/helpers"; - -jest.mock("./ImageColorSchemeSwitch"); -// @ts-expect-error: doesn't find mockImplementation outside of testing. -ImageColorSchemeSwitch.mockImplementation(() => alt); - -describe("Footer logo and copyright", () => { - test("Should render", async () => { - renderWithProviders(
); - expect(await screen.findByRole("contentinfo")).toBeInTheDocument(); - }); - - test("Should renders correctly with styles", async () => { - const borderStyle = "1px solid orange"; - renderWithProviders(
); - - const footerComputedStyle = window.getComputedStyle( - await screen.findByRole("contentinfo"), - ); - - // check new style is set - expect(footerComputedStyle.border).toBe(borderStyle); - - // Check default values are still set - expect(footerComputedStyle.minHeight).toBe("50px"); - }); -}); - -describe("Footer logo and copyright", () => { - test("Should render logo only", () => { - renderWithProviders(
); - - expect(screen.getByRole("img")).toBeInTheDocument(); - // No copyright text - expect(screen.queryByRole("paragraph")).not.toBeInTheDocument(); - }); - - test("Should render logo via theme", () => { - renderWithProviders(
); - - expect(screen.getByRole("img")).toBeInTheDocument(); - }); - - test("Should render copyright only", async () => { - const copyrightText = "add text here"; - const currentYear = new Date().getFullYear(); - renderWithProviders(
); - - await waitFor(() => { - expect(screen.queryByRole("paragraph")).toBeInTheDocument(); - expect(screen.queryByRole("paragraph")?.textContent).toStrictEqual( - `Copyright © ${currentYear} ${copyrightText}`, - ); - // No logo - expect(screen.queryByRole("img")).not.toBeTruthy(); - }); - }); - - test("Should render logo and copyright", async () => { - const copyrightText = "add text here"; - const currentYear = new Date().getFullYear(); - renderWithProviders( -
, - ); - - await waitFor(() => { - expect(screen.getByRole("img")).toBeInTheDocument(); - const paragraph = screen.getByRole("paragraph"); - expect(paragraph).toBeInTheDocument(); - expect(paragraph.textContent).toStrictEqual( - `Copyright © ${currentYear} ${copyrightText}`, - ); - }); - }); -}); - -describe("Footer Links", () => { - test("Should render with one link", async () => { - const lineOneText = "Link one"; - const linkOneName = "link-one-href"; - - renderWithProviders( -
- - {lineOneText} - -
, - ); - - await waitFor(() => { - const linkOneContainer = screen.getByText(lineOneText); - - expect(linkOneContainer).toBeInTheDocument(); - expect(linkOneContainer.getAttribute("href")).toStrictEqual(linkOneName); - expect(linkOneContainer.textContent).toStrictEqual(lineOneText); - }); - }); - - test("Should render with two links", async () => { - const linkOneText = "Link one"; - const linkTwoText = "Link two"; - const linkOneName = "link-one-href"; - const linkTwoName = "link-two-href"; - renderWithProviders( -
- - {linkOneText} - {linkTwoText} - -
, - ); - - await waitFor(() => { - const linkTwoContainer = screen.getByText(linkTwoText); - - expect(linkTwoContainer).toBeInTheDocument(); - expect(linkTwoContainer.getAttribute("href")).toStrictEqual(linkTwoName); - expect(linkTwoContainer.textContent).toStrictEqual(linkTwoText); - }); - }); -}); diff --git a/src/components/ColourSchemeButton.stories.tsx b/src/components/controls/ColourSchemeButton.stories.tsx similarity index 100% rename from src/components/ColourSchemeButton.stories.tsx rename to src/components/controls/ColourSchemeButton.stories.tsx diff --git a/src/components/ColourSchemeButton.test.tsx b/src/components/controls/ColourSchemeButton.test.tsx similarity index 91% rename from src/components/ColourSchemeButton.test.tsx rename to src/components/controls/ColourSchemeButton.test.tsx index da246b1..12ac28f 100644 --- a/src/components/ColourSchemeButton.test.tsx +++ b/src/components/controls/ColourSchemeButton.test.tsx @@ -2,14 +2,14 @@ import "@testing-library/jest-dom"; import { render, fireEvent } from "@testing-library/react"; import { ColourSchemeButton } from "./ColourSchemeButton"; -import { ColourSchemes } from "../utils/globals"; +import { ColourSchemes } from "../../utils/globals"; const mockSetColorScheme = jest.fn(); jest.mock("@mui/material", () => { return { ...jest.requireActual("@mui/material"), useColorScheme: jest.fn().mockReturnValue({ - colorScheme: jest.requireActual("../utils/globals").ColourSchemes.Dark, + colorScheme: jest.requireActual("../../utils/globals").ColourSchemes.Dark, setColorScheme: (scheme: ColourSchemes) => mockSetColorScheme(scheme), }), }; diff --git a/src/components/ColourSchemeButton.tsx b/src/components/controls/ColourSchemeButton.tsx similarity index 96% rename from src/components/ColourSchemeButton.tsx rename to src/components/controls/ColourSchemeButton.tsx index 8fc629d..71bd01d 100644 --- a/src/components/ColourSchemeButton.tsx +++ b/src/components/controls/ColourSchemeButton.tsx @@ -2,7 +2,7 @@ import { useColorScheme, useTheme } from "@mui/material"; import { IconButton, IconButtonProps } from "@mui/material"; import { LightMode, Bedtime } from "@mui/icons-material"; -import { ColourSchemes } from "../utils/globals"; +import { ColourSchemes } from "../../utils/globals"; const ColourSchemeButton = (props: IconButtonProps) => { const theme = useTheme(); diff --git a/src/components/ImageColorSchemeSwitch.stories.tsx b/src/components/controls/ImageColorSchemeSwitch.stories.tsx similarity index 92% rename from src/components/ImageColorSchemeSwitch.stories.tsx rename to src/components/controls/ImageColorSchemeSwitch.stories.tsx index d7f93aa..4f75801 100644 --- a/src/components/ImageColorSchemeSwitch.stories.tsx +++ b/src/components/controls/ImageColorSchemeSwitch.stories.tsx @@ -1,8 +1,8 @@ import { Meta, StoryObj } from "@storybook/react"; import { ImageColorSchemeSwitch } from "./ImageColorSchemeSwitch"; -import imageDark from "../public/generic/logo-dark.svg"; -import imageLight from "../public/generic/logo-light.svg"; +import imageDark from "../../public/generic/logo-dark.svg"; +import imageLight from "../../public/generic/logo-light.svg"; const meta: Meta = { title: "SciReactUI/Control/ImageColorSchemeSwitch", diff --git a/src/components/ImageColorSchemeSwitch.test.tsx b/src/components/controls/ImageColorSchemeSwitch.test.tsx similarity index 96% rename from src/components/ImageColorSchemeSwitch.test.tsx rename to src/components/controls/ImageColorSchemeSwitch.test.tsx index 4aa50b5..ae97d6b 100644 --- a/src/components/ImageColorSchemeSwitch.test.tsx +++ b/src/components/controls/ImageColorSchemeSwitch.test.tsx @@ -1,7 +1,7 @@ import "@testing-library/jest-dom"; import { ImageColorSchemeSwitch } from "./ImageColorSchemeSwitch"; -import { renderWithProviders } from "../__test-utils__/helpers"; +import { renderWithProviders } from "../../__test-utils__/helpers"; import { screen } from "@testing-library/react"; describe("ImageColorSchemeSwitch", () => { diff --git a/src/components/ImageColorSchemeSwitch.tsx b/src/components/controls/ImageColorSchemeSwitch.tsx similarity index 100% rename from src/components/ImageColorSchemeSwitch.tsx rename to src/components/controls/ImageColorSchemeSwitch.tsx diff --git a/src/components/User.stories.tsx b/src/components/controls/User.stories.tsx similarity index 100% rename from src/components/User.stories.tsx rename to src/components/controls/User.stories.tsx diff --git a/src/components/User.test.tsx b/src/components/controls/User.test.tsx similarity index 97% rename from src/components/User.test.tsx rename to src/components/controls/User.test.tsx index 2ca16db..5d75016 100644 --- a/src/components/User.test.tsx +++ b/src/components/controls/User.test.tsx @@ -3,7 +3,7 @@ import "@testing-library/jest-dom"; import { fireEvent } from "@testing-library/react"; import { Avatar } from "@mui/material"; import { User } from "./User"; -import { renderWithProviders } from "../__test-utils__/helpers"; +import { renderWithProviders } from "../../__test-utils__/helpers"; describe("User", () => { it("should render", () => { diff --git a/src/components/User.tsx b/src/components/controls/User.tsx similarity index 100% rename from src/components/User.tsx rename to src/components/controls/User.tsx diff --git a/src/components/VisitInput.stories.tsx b/src/components/controls/VisitInput.stories.tsx similarity index 96% rename from src/components/VisitInput.stories.tsx rename to src/components/controls/VisitInput.stories.tsx index 7602068..b677ce2 100644 --- a/src/components/VisitInput.stories.tsx +++ b/src/components/controls/VisitInput.stories.tsx @@ -1,5 +1,5 @@ import { Meta, StoryObj } from "@storybook/react"; -import { Visit } from "../utils/diamond"; +import { Visit } from "../../utils/diamond"; import { VisitInput } from "./VisitInput"; const meta: Meta = { diff --git a/src/components/VisitInput.test.tsx b/src/components/controls/VisitInput.test.tsx similarity index 100% rename from src/components/VisitInput.test.tsx rename to src/components/controls/VisitInput.test.tsx diff --git a/src/components/VisitInput.tsx b/src/components/controls/VisitInput.tsx similarity index 96% rename from src/components/VisitInput.tsx rename to src/components/controls/VisitInput.tsx index 8d1a394..ea43cd4 100644 --- a/src/components/VisitInput.tsx +++ b/src/components/controls/VisitInput.tsx @@ -1,6 +1,11 @@ import React, { ChangeEvent, useState } from "react"; import { Button, Stack, TextField } from "@mui/material"; -import { regexToVisit, Visit, visitRegex, visitToText } from "../utils/diamond"; +import { + regexToVisit, + Visit, + visitRegex, + visitToText, +} from "../../utils/diamond"; interface VisitInputTextProps { visitText: string; diff --git a/src/components/Breadcrumbs.stories.tsx b/src/components/navigation/Breadcrumbs.stories.tsx similarity index 58% rename from src/components/Breadcrumbs.stories.tsx rename to src/components/navigation/Breadcrumbs.stories.tsx index dfa78d1..9ca40dc 100644 --- a/src/components/Breadcrumbs.stories.tsx +++ b/src/components/navigation/Breadcrumbs.stories.tsx @@ -1,5 +1,31 @@ import { Meta, StoryObj } from "@storybook/react"; import { Breadcrumbs } from "./Breadcrumbs"; +import { Link as MuiLink } from "@mui/material"; + +interface MockLinkProps extends React.HTMLAttributes { + to: string; + children: React.ReactNode; + [key: string]: unknown; +} + +const MockLink: React.FC = ({ to, children, ...props }) => { + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + console.log(`Mock navigation to: ${to}`); + }; + + return ( + + {children} + + ); +}; const meta: Meta = { title: "SciReactUI/Navigation/Breadcrumbs", @@ -13,18 +39,21 @@ type Story = StoryObj; export const Default: Story = { args: { path: "/first/second/third/last/", + linkComponent: MockLink, }, }; export const ShortPath: Story = { args: { path: "just one", + linkComponent: MockLink, }, }; export const LongPath: Story = { args: { path: "/first/the second/third/fourth/almost last/last one/", + linkComponent: MockLink, }, }; @@ -35,18 +64,21 @@ export const DifferentLinkToPathName: Story = { { name: "second", href: "other link" }, { name: "last", href: "/" }, ], + linkComponent: MockLink, }, }; export const Empty: Story = { args: { path: [], + linkComponent: MockLink, }, }; export const ColorChange: Story = { args: { path: ["first", "second", "third", "last"], + linkComponent: MockLink, rootProps: { sx: { backgroundColor: "blue" }, }, diff --git a/src/components/Breadcrumbs.test.tsx b/src/components/navigation/Breadcrumbs.test.tsx similarity index 90% rename from src/components/Breadcrumbs.test.tsx rename to src/components/navigation/Breadcrumbs.test.tsx index 41efb09..83c68f5 100644 --- a/src/components/Breadcrumbs.test.tsx +++ b/src/components/navigation/Breadcrumbs.test.tsx @@ -4,7 +4,7 @@ import { RenderResult, screen, } from "@testing-library/react"; -import { MemoryRouter, Route, Routes } from "react-router-dom"; +import { MemoryRouter, Route, Routes, Link } from "react-router-dom"; import { Breadcrumbs, getCrumbs } from "./Breadcrumbs"; import "@testing-library/jest-dom"; import { CustomLink } from "types/links"; @@ -56,7 +56,7 @@ describe("Breadcrumbs", () => { it("should render without errors", () => { render( - + , ); }); @@ -64,7 +64,7 @@ describe("Breadcrumbs", () => { it("should show just home when an empty string", () => { const renderResult = render( - + , ); testHomeExists(renderResult); @@ -74,7 +74,7 @@ describe("Breadcrumbs", () => { it("should show just home when an empty array", () => { const renderResult = render( - + , ); testHomeExists(renderResult); @@ -85,7 +85,7 @@ describe("Breadcrumbs", () => { testCrumbsExist( render( - + , ), ); @@ -95,7 +95,7 @@ describe("Breadcrumbs", () => { testCrumbsExist( render( - + , ), ); @@ -104,7 +104,7 @@ describe("Breadcrumbs", () => { it("should use path as object array", () => { const { getByRole, queryByRole, getByText } = render( - + , ); let crumb = getByRole("link", { name: crumbFirstTitle }); @@ -133,7 +133,9 @@ describe("Breadcrumbs", () => { /> } + element={ + + } /> , @@ -145,7 +147,7 @@ describe("Breadcrumbs", () => { it("should render correctly with different routes", () => { const { rerender } = render( - + , ); @@ -153,7 +155,7 @@ describe("Breadcrumbs", () => { expect(screen.getByText("Second")).toBeInTheDocument(); rerender( - + , ); diff --git a/src/components/Breadcrumbs.tsx b/src/components/navigation/Breadcrumbs.tsx similarity index 95% rename from src/components/Breadcrumbs.tsx rename to src/components/navigation/Breadcrumbs.tsx index 3231c82..490779d 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/navigation/Breadcrumbs.tsx @@ -9,13 +9,13 @@ import { Typography, useTheme, } from "@mui/material"; -import { Link } from "react-router-dom"; import HomeIcon from "@mui/icons-material/Home"; import NavigateNextIcon from "@mui/icons-material/NavigateNext"; import { CustomLink } from "types/links"; interface BreadcrumbsProps { path: string | string[] | CustomLink[]; + linkComponent: React.ElementType; rootProps?: PaperProps; muiBreadcrumbsProps?: Mui_BreadcrumbsProps; } @@ -57,6 +57,7 @@ export function getCrumbs( const Breadcrumbs = ({ path, + linkComponent, rootProps, muiBreadcrumbsProps, }: BreadcrumbsProps) => { @@ -82,7 +83,7 @@ const Breadcrumbs = ({ key={"crumb-0"} underline="hover" color="inherit" - component={Link} + component={linkComponent} to="/" > @@ -96,7 +97,7 @@ const Breadcrumbs = ({ sx={{ fontSize: "smaller" }} underline="hover" color="inherit" - component={Link} + component={linkComponent} to={crumb.href} > {crumb.name} diff --git a/src/components/Footer.stories.tsx b/src/components/navigation/Footer.stories.tsx similarity index 100% rename from src/components/Footer.stories.tsx rename to src/components/navigation/Footer.stories.tsx diff --git a/src/components/navigation/Footer.test.tsx b/src/components/navigation/Footer.test.tsx new file mode 100644 index 0000000..53cad09 --- /dev/null +++ b/src/components/navigation/Footer.test.tsx @@ -0,0 +1,101 @@ +import type { ImageColorSchemeSwitchType } from "../controls/ImageColorSchemeSwitch"; +jest.mock("../controls/ImageColorSchemeSwitch", () => ({ + __esModule: true, + ImageColorSchemeSwitch: ({ + image, + }: { + image: ImageColorSchemeSwitchType; + }) => ( + {image.alt} + ), +})); + +import { screen } from "@testing-library/react"; +import "@testing-library/jest-dom"; + +import dlsLogo from "../public/generic/logo-short.svg"; +import { Footer, FooterLink, FooterLinks } from "./Footer"; +import { renderWithProviders } from "../../__test-utils__/helpers"; + +describe("Footer logo and copyright", () => { + test("Should render", async () => { + renderWithProviders(
); + expect(await screen.findByRole("contentinfo")).toBeInTheDocument(); + }); + + test("Should render correctly with styles", async () => { + const borderStyle = "1px solid orange"; + renderWithProviders(
); + const footerComputedStyle = window.getComputedStyle( + await screen.findByRole("contentinfo"), + ); + + // check new style is set + expect(footerComputedStyle.border).toBe(borderStyle); + + // Check default values are still set + expect(footerComputedStyle.minHeight).toBe("50px"); + }); + + test("Should render logo only", async () => { + renderWithProviders(
); + expect(await screen.findByAltText("t")).toBeInTheDocument(); + expect(screen.queryByText(/^Copyright © \d{4}/)).not.toBeInTheDocument(); + }); + + test("Should render logo via theme", async () => { + renderWithProviders(
); + expect(await screen.findByTestId("mock-logo")).toBeInTheDocument(); + }); + + test("Should render copyright only", async () => { + const copyrightText = "add text here"; + const currentYear = new Date().getFullYear(); + renderWithProviders(
); + expect( + await screen.findByText(`Copyright © ${currentYear} ${copyrightText}`), + ).toBeInTheDocument(); + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + }); + + test("Should render logo and copyright", async () => { + const copyrightText = "add text here"; + const currentYear = new Date().getFullYear(); + renderWithProviders( +
, + ); + expect(await screen.findByAltText("logo")).toBeInTheDocument(); + expect( + screen.getByText(`Copyright © ${currentYear} ${copyrightText}`), + ).toBeInTheDocument(); + }); +}); + +describe("Footer Links", () => { + test("Should render with one link", async () => { + renderWithProviders( +
+ + Link one + +
, + ); + const link = await screen.findByText("Link one"); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "link-one-href"); + }); + + test("Should render with two links", async () => { + renderWithProviders( +
+ + Link one + Link two + +
, + ); + const link = await screen.findByText("Link two"); + expect(link).toBeInTheDocument(); + expect(link).toHaveAttribute("href", "link-two-href"); + }); +}); diff --git a/src/components/Footer.tsx b/src/components/navigation/Footer.tsx similarity index 86% rename from src/components/Footer.tsx rename to src/components/navigation/Footer.tsx index e355586..1c75e08 100644 --- a/src/components/Footer.tsx +++ b/src/components/navigation/Footer.tsx @@ -13,7 +13,7 @@ import React from "react"; import { ImageColorSchemeSwitch, ImageColorSchemeSwitchType, -} from "./ImageColorSchemeSwitch"; +} from "../controls/ImageColorSchemeSwitch"; interface FooterLinksProps extends React.HTMLProps { children: React.ReactElement | React.ReactElement[]; @@ -81,16 +81,21 @@ const BoxStyled = styled(Box)(({ theme }) => ({ */ const Footer = ({ logo, copyright, children, ...props }: FooterProps) => { const theme = useTheme(); + let resolvedLogo: ImageColorSchemeSwitchType | null | undefined = null; if (logo === "theme") { - logo = theme.logos?.short; + resolvedLogo = theme.logos?.short; + } else if (logo && typeof logo === "object") { + resolvedLogo = logo; } return ( {
- {(logo || copyright) && ( + {(resolvedLogo || copyright) && (
{ textAlign: "right", }} > - {logo && } + {resolvedLogo && } {copyright && ( = { title: "SciReactUI/Navigation/Navbar", diff --git a/src/components/Navbar.test.tsx b/src/components/navigation/Navbar.test.tsx similarity index 96% rename from src/components/Navbar.test.tsx rename to src/components/navigation/Navbar.test.tsx index 640ce10..e9da211 100644 --- a/src/components/Navbar.test.tsx +++ b/src/components/navigation/Navbar.test.tsx @@ -1,7 +1,7 @@ import { fireEvent, screen } from "@testing-library/react"; import { Navbar, NavLinks, NavLink } from "./Navbar"; import "@testing-library/jest-dom"; -import { renderWithProviders } from "../__test-utils__/helpers"; +import { renderWithProviders } from "../../__test-utils__/helpers"; describe("Navbar", () => { it("should render", async () => { diff --git a/src/components/Navbar.tsx b/src/components/navigation/Navbar.tsx similarity index 92% rename from src/components/Navbar.tsx rename to src/components/navigation/Navbar.tsx index 20ad9e9..f213b53 100644 --- a/src/components/Navbar.tsx +++ b/src/components/navigation/Navbar.tsx @@ -17,7 +17,7 @@ import React, { useState } from "react"; import { ImageColorSchemeSwitch, ImageColorSchemeSwitchType, -} from "./ImageColorSchemeSwitch"; +} from "../controls/ImageColorSchemeSwitch"; interface NavLinksProps { children: React.ReactElement | React.ReactElement[]; @@ -128,9 +128,11 @@ const BoxStyled = styled(Box)(({ theme }) => ({ */ const Navbar = ({ children, logo, ...props }: NavbarProps) => { const theme = useTheme(); - + let resolvedLogo: ImageColorSchemeSwitchType | null | undefined = null; if (logo === "theme") { - logo = theme.logos?.normal; + resolvedLogo = theme.logos?.normal; + } else if (logo && typeof logo === "object") { + resolvedLogo = logo; } return ( @@ -141,7 +143,7 @@ const Navbar = ({ children, logo, ...props }: NavbarProps) => { spacing={8} sx={{ height: "100%", alignItems: "center", width: "100%" }} > - {logo ? ( + {resolvedLogo ? ( { marginRight: { xs: "0", md: "50px" }, }} > - + ) : null} diff --git a/src/index.ts b/src/index.ts index 0b4b2c6..9545691 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,14 @@ // components -export * from "./components/Breadcrumbs"; -export * from "./components/ColourSchemeButton"; -export * from "./components/Footer"; -export * from "./components/Navbar"; -export * from "./components/User"; -export * from "./components/VisitInput"; -export * from "./components/ImageColorSchemeSwitch"; +// components/navigation +export * from "./components/navigation/Breadcrumbs"; +export * from "./components/navigation/Footer"; +export * from "./components/navigation/Navbar"; + +// components/controls +export * from "./components/controls/ColourSchemeButton"; +export * from "./components/controls/User"; +export * from "./components/controls/VisitInput"; +export * from "./components/controls/ImageColorSchemeSwitch"; // themes export * from "./themes/BaseTheme"; diff --git a/src/storybook/theme/Logos.mdx b/src/storybook/theme/Logos.mdx index c72c8b3..c1e7d1b 100644 --- a/src/storybook/theme/Logos.mdx +++ b/src/storybook/theme/Logos.mdx @@ -1,5 +1,5 @@ import { Meta, ColorPalette, ColorItem } from '@storybook/blocks'; -import {ImageColorSchemeSwitch} from "../../components/ImageColorSchemeSwitch"; +import {ImageColorSchemeSwitch} from "../../components/controls/ImageColorSchemeSwitch"; diff --git a/src/themes/BaseTheme.ts b/src/themes/BaseTheme.ts index 29fc516..400a268 100644 --- a/src/themes/BaseTheme.ts +++ b/src/themes/BaseTheme.ts @@ -1,5 +1,5 @@ // import {ThemeOptions} from "@mui/material/styles"; -import { ImageColorSchemeSwitchType } from "../components/ImageColorSchemeSwitch"; +import { ImageColorSchemeSwitchType } from "../components/controls/ImageColorSchemeSwitch"; // Make additions to theme, so that anything can be available throughout the app declare module "@mui/material/styles" { diff --git a/src/themes/SciReactUIProvider.tsx b/src/themes/SciReactUIProvider.tsx deleted file mode 100644 index cfec935..0000000 --- a/src/themes/SciReactUIProvider.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import * as React from "react"; -import { BrowserRouter } from "react-router-dom"; -import { ThemeProvider, ThemeProviderProps } from "./ThemeProvider"; - -type SciReactUIProviderProps = React.PropsWithChildren; - -export const SciReactUIProvider: React.FC = ( - props, -) => { - return ( - - {props.children} - - ); -}; From c41370a0e03b05fff37a977a3e8ab3e5d4a17b45 Mon Sep 17 00:00:00 2001 From: VictoriaBeilstenEdmands Date: Tue, 20 May 2025 15:41:52 +0100 Subject: [PATCH 4/4] Keep static link behaviour on Breadcrumbs when no linkComponent passed --- changelog.md | 2 +- readme.md | 17 ++++++++++--- .../navigation/Breadcrumbs.stories.tsx | 16 +++++++++++++ .../navigation/Breadcrumbs.test.tsx | 24 +++++++++++++++++++ src/components/navigation/Breadcrumbs.tsx | 22 ++++++++++++----- 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index e1fa369..ec1bbda 100644 --- a/changelog.md +++ b/changelog.md @@ -12,7 +12,7 @@ SciReactUI Changelog - Styles added to Navbar and Footer incorrectly remove built in styles. ### Changed -- Breaking change Breadcrumbs component requires linkComponent prop for page routing. +- Breadcrumbs component takes optional linkComponent prop for page routing. [v0.1.0] - 2025-04-10 --------------------- diff --git a/readme.md b/readme.md index 92018f6..eaf53c0 100644 --- a/readme.md +++ b/readme.md @@ -8,7 +8,6 @@ A theme and component library to make websites at scientific installations simpl Using ----- - ### Installing Install as usual: @@ -33,7 +32,19 @@ root.render( There are currently two themes, `GenericTheme` or `DiamondTheme`, but you can - and should - adapt your own. -To use the Breadcrumbs component, use a route provider from your preferred library. For example, to use react-router's BrowserRouter, install react-router-dom: +The Breadcrumbs supports either static links or the use of a routing library. +To use static links, omit the linkComponent prop and Breadcrumbs will use a Link component with standard href attributes. + +```js +import { Breadcrumbs } from "@diamondlightsource/sci-react-ui"; + +function App() { + return ; +} +export default App; +``` + +To use the Breadcrumbs component with your routing library, use a route provider from your preferred library. For example, to use react-router's BrowserRouter, install react-router-dom: ```sh npm i react-router-dom @@ -57,7 +68,7 @@ root.render( ) ``` -Then pass your library's corresponding Link component to Breadcrumbs, for example: +Then pass your library's corresponding Link component to Breadcrumbs in the linkComponent prop, for example: ```js import { Link } from "react-router-dom"; diff --git a/src/components/navigation/Breadcrumbs.stories.tsx b/src/components/navigation/Breadcrumbs.stories.tsx index 9ca40dc..da9679a 100644 --- a/src/components/navigation/Breadcrumbs.stories.tsx +++ b/src/components/navigation/Breadcrumbs.stories.tsx @@ -75,6 +75,22 @@ export const Empty: Story = { }, }; +export const NoLinkComponent: Story = { + args: { + path: "/first/second/third/last/", + }, +}; + +export const NoLinkComponentWithCustomPath: Story = { + args: { + path: [ + { name: "first", href: "link" }, + { name: "second", href: "other link" }, + { name: "last", href: "/" }, + ], + }, +}; + export const ColorChange: Story = { args: { path: ["first", "second", "third", "last"], diff --git a/src/components/navigation/Breadcrumbs.test.tsx b/src/components/navigation/Breadcrumbs.test.tsx index 83c68f5..48a7530 100644 --- a/src/components/navigation/Breadcrumbs.test.tsx +++ b/src/components/navigation/Breadcrumbs.test.tsx @@ -251,3 +251,27 @@ describe("getCrumbs", () => { ).toStrictEqual(objectArrayCrumbs); }); }); + +it("should render Link with href when linkComponent is not provided", () => { + const { getByRole, getByText } = render( + , + ); + + expect(getByRole("link", { name: crumbFirstTitle })).toHaveAttribute( + "href", + `/${crumbFirst}`, + ); + expect(getByRole("link", { name: crumbSecondTitle })).toHaveAttribute( + "href", + `/${crumbSecond}`, + ); + expect(getByText(crumbLastTitle)).toBeInTheDocument(); +}); + +it("should render home link with href when linkComponent is not provided", () => { + const { getByTestId } = render(); + const homeIcon = getByTestId("HomeIcon"); + + expect(homeIcon).toBeInTheDocument(); + expect(homeIcon.parentElement).toHaveAttribute("href", "/"); +}); diff --git a/src/components/navigation/Breadcrumbs.tsx b/src/components/navigation/Breadcrumbs.tsx index 490779d..74eb3b9 100644 --- a/src/components/navigation/Breadcrumbs.tsx +++ b/src/components/navigation/Breadcrumbs.tsx @@ -15,7 +15,7 @@ import { CustomLink } from "types/links"; interface BreadcrumbsProps { path: string | string[] | CustomLink[]; - linkComponent: React.ElementType; + linkComponent?: React.ElementType; rootProps?: PaperProps; muiBreadcrumbsProps?: Mui_BreadcrumbsProps; } @@ -83,10 +83,19 @@ const Breadcrumbs = ({ key={"crumb-0"} underline="hover" color="inherit" - component={linkComponent} - to="/" + {...(linkComponent + ? { + component: linkComponent, + to: "/", + } + : { + href: "/", + })} > - + {crumbs.map((crumb, i, all) => { @@ -97,8 +106,9 @@ const Breadcrumbs = ({ sx={{ fontSize: "smaller" }} underline="hover" color="inherit" - component={linkComponent} - to={crumb.href} + {...(linkComponent + ? { component: linkComponent, to: crumb.href } + : { href: crumb.href })} > {crumb.name}