diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 000000000..48a4efd1a --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,5 @@ +[javascripts] +supports es6-module + +[node] +node 22 diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index c5c41edb1..000000000 --- a/.eslintignore +++ /dev/null @@ -1,4 +0,0 @@ -jest.config.js -.eslintrc.js -rollup.config.mjs -dist \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 97688586c..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = { - parserOptions: { - project: './tsconfig.json', - tsconfigRootDir: __dirname, - }, - env: { - browser: true, - jest: true, - }, - settings: { - 'import/resolver': { - typescript: {}, - }, - react: { - version: 'detect', - }, - }, - extends: [ - 'plugin:react/recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier', - 'plugin:import/errors', - 'plugin:import/warnings', - 'plugin:import/typescript', - 'plugin:jsx-a11y/recommended', - 'plugin:react-hooks/recommended', - ], - rules: { - 'react/prop-types': 0, - 'jsx-a11y/anchor-has-content': 0, - 'jsx-a11y/alt-text': 0, - 'jsx-a11y/heading-has-content': 0, - 'react-hooks/exhaustive-deps': 0, - }, - overrides: [ - { - files: ['*.stories.tsx'], - rules: { '@typescript-eslint/no-unused-vars': 'off' }, - }, - ], -}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aaf9601b2..0079701cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,9 +22,9 @@ jobs: - name: Yarn Install run: yarn - name: Lint - run: yarn lint:ci + run: yarn lint - name: Jest Tests - run: yarn test:ci + run: yarn test --coverage - name: Typescript build run: yarn build - name: Storybook build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3d4732f7..da2ef3182 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,10 +28,10 @@ jobs: run: yarn - name: Lint - run: yarn lint:ci + run: yarn lint - name: Jest Tests - run: yarn test:ci + run: yarn test --coverage - name: Typescript build run: yarn build diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..3741fba3f --- /dev/null +++ b/.prettierignore @@ -0,0 +1,12 @@ +# Node.js modules +node_modules/ + +# Test coverage +coverage/ + +# Build output +dist/ + +# Files to ignore +.yarnrc.yml +package-lock.json diff --git a/.prettierrc b/.prettierrc index ca8527e0d..feb652799 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,7 +1,8 @@ { + "printWidth": 100, + "quoteProps": "consistent", "semi": true, - "trailingComma": "all", "singleQuote": true, - "printWidth": 100, - "tabWidth": 2 + "tabWidth": 2, + "trailingComma": "all" } diff --git a/.storybook/main.ts b/.storybook/main.ts index f42bd8fea..41dbb8186 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -4,21 +4,29 @@ import tsConfigPaths from 'vite-tsconfig-paths'; const config: StorybookConfig = { stories: ['../stories/**/*.stories.@(ts|tsx)', '../stories/**/*.mdx'], - addons: ['@storybook/addon-links', '@storybook/addon-essentials'], + addons: ['@storybook/addon-links', '@storybook/addon-docs'], framework: { name: '@storybook/react-vite', options: {}, }, - docs: { - autodocs: true, - }, + typescript: { reactDocgen: 'react-docgen-typescript', }, + viteFinal(config) { return mergeConfig(config, { plugins: [tsConfigPaths()], + css: { + preprocessorOptions: { + scss: { + quietDeps: true, + loadPaths: ['node_modules'], + }, + }, + }, }); }, }; + export default config; diff --git a/.storybook/manager.ts b/.storybook/manager.ts index 0e2f6b663..2a4e81347 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,15 +1,18 @@ -import { addons } from '@storybook/manager-api'; +import { addons } from 'storybook/manager-api'; import nhsTheme from './theme'; import { startCase, upperFirst } from 'lodash'; -const sentenceCase = (string) => { - if (typeof string !== 'string') return ''; - return upperFirst(startCase(string).toLowerCase()); +const sentenceCase = (name = '') => { + if (!name || typeof name !== 'string') { + return ''; + } + + return upperFirst(startCase(name).toLowerCase()); }; addons.setConfig({ sidebar: { - renderLabel: ({ name, type }) => sentenceCase(name), + renderLabel: ({ name }) => sentenceCase(name), }, theme: nhsTheme, }); diff --git a/.storybook/preview.ts b/.storybook/preview.ts index fce54dde7..763bb4067 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,5 +1,5 @@ import './storybook.scss'; -import { Preview } from '@storybook/react'; +import { type Preview } from '@storybook/react-vite'; const preview: Preview = { parameters: { @@ -18,5 +18,8 @@ const preview: Preview = { }, }, }, + + tags: ['autodocs'], }; + export default preview; diff --git a/.storybook/storybook.scss b/.storybook/storybook.scss index b168b6395..ea4b70a2d 100644 --- a/.storybook/storybook.scss +++ b/.storybook/storybook.scss @@ -1,5 +1,4 @@ -// Allow current nhsuk styles to override legacy -@import 'nhsuk-frontend/dist/nhsuk'; +@forward 'nhsuk-frontend/dist/nhsuk'; .tag-wrapper { display: flex; diff --git a/.storybook/theme.ts b/.storybook/theme.ts index 5d3ea6517..a7f0b8e9e 100644 --- a/.storybook/theme.ts +++ b/.storybook/theme.ts @@ -1,5 +1,5 @@ -import { create } from '@storybook/theming/create'; -const version = require('../package.json').version; +import { create } from 'storybook/theming/create'; +import packageJson from '../package.json' with { type: 'json' }; export default create({ base: 'light', @@ -31,6 +31,6 @@ export default create({ inputTextColor: '#212b32', inputBorderRadius: 4, - brandTitle: `NHS.UK React Components (v${version})`, + brandTitle: `NHS.UK React Components (v${packageJson.version})`, brandUrl: 'https://github.com/NHSDigital/nhsuk-react-components', }); diff --git a/.vscode/settings.json b/.vscode/settings.json index 667ff68ee..340494c77 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,11 +6,9 @@ "source.fixAll": "explicit", "source.fixAll.eslint": "explicit" }, - "eslint.validate": ["javascript", "typescript"], + "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "eslint.codeAction.showDocumentation": { "enable": true }, - "eslint.alwaysShowStatus": true, - "eslint.workingDirectories": ["src"], "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/.yarnrc.yml b/.yarnrc.yml index 7af902845..52c22f2df 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -1,2 +1,17 @@ nodeLinker: node-modules -npmRegistryServer: https://registry.yarnpkg.com + +npmRegistryServer: "https://registry.yarnpkg.com" + +packageExtensions: + "@storybook/addon-docs@*": + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + + "@storybook/react-vite@*": + peerDependencies: + typescript: "*" + + "storybook@*": + peerDependencies: + "@testing-library/dom": "*" diff --git a/CHANGELOG.md b/CHANGELOG.md index 5141e92b8..9c08600f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # NHS.UK React components +## 6.0.0-beta.1 - 8 October 2025 + +This version adds the panel component from NHS.UK frontend v9.3.0 and supports React v19. + +For a full list of changes in this release please refer to the [migration doc](https://github.com/NHSDigital/nhsuk-react-components/blob/main/docs/upgrade-to-6.0.md). + ## 6.0.0-beta.0 - 30 September 2025 This version provides support for nhsuk-frontend version 10. diff --git a/README.md b/README.md index f67086622..4b61a13a5 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ yarn add nhsuk-react-components ## Usage ```jsx -import React, { PureComponent } from 'react'; +import { PureComponent } from 'react'; // You can import components from the global module import { Button } from 'nhsuk-react-components'; // Or you can import components directly -import Button from 'nhsuk-react-components/lib/components/button'; +import { Button } from 'nhsuk-react-components/dist/esm/components/button'; class GetStartedButton extends PureComponent { render() { @@ -43,6 +43,7 @@ class GetStartedButton extends PureComponent { - [Upgrading to 3.0](/docs/upgrade-to-3.0.md) - [Upgrading to 4.0](/docs/upgrade-to-4.0.md) - [Upgrading to 5.0](/docs/upgrade-to-5.0.md) +- [Upgrading to 6.0](/docs/upgrade-to-6.0.md) ## Maintainers diff --git a/babel.config.cjs b/babel.config.cjs new file mode 100644 index 000000000..114f1daf9 --- /dev/null +++ b/babel.config.cjs @@ -0,0 +1,69 @@ +const { NODE_ENV } = process.env; + +/** + * Babel config + * + * @type {TransformOptions} + */ +module.exports = { + browserslistEnv: 'javascripts', + presets: [ + [ + '@babel/preset-env', + { + // Apply bug fixes to avoid transforms + bugfixes: true, + + // Apply smaller "loose" transforms for browsers + loose: true, + + // Apply ES module transforms for Jest + // https://jestjs.io/docs/ecmascript-modules + modules: NODE_ENV === 'test' ? 'auto' : false, + }, + ], + [ + '@babel/preset-react', + { + development: NODE_ENV === 'development', + runtime: 'automatic', + useBuiltIns: true, + }, + ], + '@babel/preset-typescript', + ], + env: { + test: { + browserslistEnv: 'node', + plugins: [ + // Override package.json "imports" for Jest to use sources + // otherwise a build step to output `./dist` is necessary + [ + 'module-resolver', + { + alias: { + '#components': './src/components', + '#patterns': './src/patterns', + '#util': './src/util', + 'nhsuk-react-components': './src/index.ts', + }, + }, + ], + // Remove mandatory ES module file extensions for Jest + // https://nodejs.org/api/esm.html#mandatory-file-extensions + [ + 'replace-import-extension', + { + extMapping: { + '.js': '', + }, + }, + ], + ], + }, + }, +}; + +/** + * @import { TransformOptions } from '@babel/core' + */ diff --git a/bundle-base.tsconfig.json b/bundle-base.tsconfig.json deleted file mode 100644 index f2c7791e9..000000000 --- a/bundle-base.tsconfig.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "compilerOptions": { - "jsx": "react", - "target": "es6", - "module": "esnext", - "moduleResolution": "node", - "baseUrl": "./", - "types": ["jest", "node"], - "sourceMap": true, - "forceConsistentCasingInFileNames": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "skipLibCheck": true, - "strict": true, - "strictNullChecks": true, - "resolveJsonModule": true, - "allowJs": true, - "outDir": "./dist", - "paths": { - "@components/*": ["src/components/*"], - "@content-presentation/*": ["src/components/content-presentation/*"], - "@form-elements/*": ["src/components/form-elements/*"], - "@navigation/*": ["src/components/navigation/*"], - "@typography/*": ["src/components/typography/*"], - "@util/*": ["src/util/*"], - "@patterns/*": ["src/patterns/*"] - } - }, - "include": ["src"], - "exclude": ["node_modules", "**/__tests__", "src/setupTests.ts"] -} diff --git a/docs/upgrade-to-6.0.md b/docs/upgrade-to-6.0.md index b16402fff..8c09dc05b 100644 --- a/docs/upgrade-to-6.0.md +++ b/docs/upgrade-to-6.0.md @@ -6,6 +6,31 @@ There are some breaking changes you'll need to be aware of when upgrading to v6. You must read and apply these updates carefully to make sure your service does not break. +## New features + +### New header component with account section + +The updated [header](https://service-manual.nhs.uk/design-system/components/header) component from NHS.UK frontend v10.x has been added, including support for account information and links. As part of this work we’ve also made some other improvements to the header: + +- show currently active section or page in the navigation +- align navigation items to the left by default +- update navigation label from ’Primary navigation’ to ‘Menu’, and remove superfluous `role` and `id` attributes +- update NHS logo in the header to have higher contrast when focused +- refactor CSS classes and BEM naming, use hidden attributes instead of modifier classes, use generic search element + +### Panel component + +The [panel](https://service-manual.nhs.uk/design-system/components/panel) component from NHS.UK frontend v9.3.0 has been added: + +```jsx + + Booking complete + We have sent you a confirmation email + +``` + +This replaces the [list panel component](#list-panel) which was removed in NHS.UK frontend v6.0.0. + ## Breaking changes ### Update the JavaScript supported script snippet @@ -210,7 +235,7 @@ To align with NHS.UK frontend, the date input component automatically renders it The custom `autoSelectNext` prop is no longer supported. -### New header component with account section +### Header The updated header component from NHS.UK frontend v10.x has been added. You will need to make the following changes: @@ -464,3 +489,22 @@ To align with NHS.UK frontend, the warning callout `WarningCallout.Label` compon

``` + +## Fixes + +- [#52: Expose header navigation open/close state (with setter)](https://github.com/NHSDigital/nhsuk-react-components/issues/52) +- [#69: Unable to use ref attribute on some components](https://github.com/NHSDigital/nhsuk-react-components/issues/69) +- [#71: Expose FormGroup component to consumers](https://github.com/NHSDigital/nhsuk-react-components/issues/71) +- [#105: getHeadingsFromChildren forces use of string as table cell child](https://github.com/NHSDigital/nhsuk-react-components/issues/105) +- [#166: SkipLink double jumps to first heading then #maincontent if disableDefaultBehaviour is not set](https://github.com/NHSDigital/nhsuk-react-components/issues/166) +- [#174: Responsive tables and validation errors](https://github.com/NHSDigital/nhsuk-react-components/issues/174) +- [#214: Hints and errors are not semantically associated with fieldsets](https://github.com/NHSDigital/nhsuk-react-components/issues/214) +- [#215: Suggestion: remove all 'boolean' examples from storybook](https://github.com/NHSDigital/nhsuk-react-components/issues/215) +- [#243: Use correct NHS.UK frontend JavaScript when rendered client-side](https://github.com/NHSDigital/nhsuk-react-components/issues/243) +- [#244: Breaking change: remove default legend and label sizes or else change to l](https://github.com/NHSDigital/nhsuk-react-components/issues/244) +- [#245: Fieldset incorrectly gets set in error when a child input is in error](https://github.com/NHSDigital/nhsuk-react-components/issues/245) +- [#247: Date component uses label rather than fieldset with legend](https://github.com/NHSDigital/nhsuk-react-components/issues/247) +- [#256: SkipLink does not work if intended target header is rerendered](https://github.com/NHSDigital/nhsuk-react-components/issues/256) +- [#259: Remove pattern="[0-9]\*" from date inputs](https://github.com/NHSDigital/nhsuk-react-components/issues/259) +- [#260: Allow custom component for button links](https://github.com/NHSDigital/nhsuk-react-components/issues/260) +- [#265: Header logo is not labeled correctly when organisation info is provided](https://github.com/NHSDigital/nhsuk-react-components/issues/265) diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..2d8cf8cfd --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,103 @@ +import { join } from 'node:path'; +import configPrettier from 'eslint-config-prettier/flat'; +import pluginReact from 'eslint-plugin-react'; +import pluginReactHooks from 'eslint-plugin-react-hooks'; +import eslint from '@eslint/js'; +import pluginJsxA11y from 'eslint-plugin-jsx-a11y'; +import { includeIgnoreFile } from '@eslint/compat'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import globals from 'globals'; +import pluginImport from 'eslint-plugin-import'; +import pluginTypeScript from 'typescript-eslint'; + +const rootPath = import.meta.dirname; +const gitignorePath = join(rootPath, '.gitignore'); + +export default defineConfig([ + { + files: ['**/*.{js,mjs,ts,tsx}'], + extends: [ + configPrettier, + eslint.configs.recommended, + pluginTypeScript.configs.recommended, + pluginImport.flatConfigs.recommended, + pluginImport.flatConfigs.typescript, + ], + languageOptions: { + parser: pluginTypeScript.parser, + parserOptions: { + ecmaVersion: 'latest', + projectService: true, + tsconfigRootDir: rootPath, + }, + }, + rules: { + // Turn off rules that are handled by TypeScript + // https://typescript-eslint.io/troubleshooting/typed-linting/performance/#eslint-plugin-import + 'import/default': 'off', + 'import/named': 'off', + 'import/namespace': 'off', + 'import/no-cycle': 'off', + 'import/no-deprecated': 'off', + 'import/no-named-as-default': 'off', + 'import/no-named-as-default-member': 'off', + 'import/no-unresolved': 'off', + 'import/no-unused-modules': 'off', + }, + settings: { + 'import/resolver': { + node: true, + typescript: true, + }, + }, + }, + { + files: ['**/*.{ts,tsx}'], + extends: [ + pluginJsxA11y.flatConfigs.recommended, + pluginReact.configs.flat.recommended, + pluginReact.configs.flat['jsx-runtime'], + 'react-hooks/recommended-latest', + ], + languageOptions: { + globals: globals.browser, + parserOptions: { + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': pluginReactHooks, + }, + settings: { + react: { + version: 'detect', + }, + }, + }, + { + files: ['**/*.{cjs,js,mjs}'], + languageOptions: { + globals: globals.node, + }, + }, + { + files: ['**/*.cjs'], + rules: { + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-var-requires': 'off', + }, + }, + { + files: ['**/*.test.{ts,tsx}'], + languageOptions: { + globals: globals.jest, + }, + }, + { + files: ['**/*.stories.tsx'], + rules: { '@typescript-eslint/no-unused-vars': 'off' }, + }, + globalIgnores(['**/coverage/', '**/dist/']), + includeIgnoreFile(gitignorePath, 'Imported .gitignore patterns'), +]); diff --git a/jest.config.js b/jest.config.js index 4c0cc8c5e..975ac97ad 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,24 +1,18 @@ -const { pathsToModuleNameMapper } = require('ts-jest'); -const { compilerOptions } = require('./tsconfig.json'); - -const jestConfig = { - testEnvironment: 'jsdom', - rootDir: './', +/** + * Jest config + * + * @type {Config} + */ +export default { + collectCoverageFrom: ['/src/**/*.{js,mjs,ts,tsx}'], + extensionsToTreatAsEsm: ['.jsx', '.ts', '.tsx'], setupFilesAfterEnv: ['/src/setupTests.ts'], - collectCoverageFrom: ['/src/**/*.{ts,tsx}'], - moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { - prefix: '', - }), + testEnvironment: 'jsdom', transform: { - '^.+\\.(t|j)sx?$': [ - 'ts-jest', - { - babelConfig: { - plugins: ['@babel/plugin-transform-modules-commonjs'], - }, - }, - ], + '^.+\\.(js|mjs|ts|tsx)$': ['babel-jest', { rootMode: 'upward' }], }, }; -module.exports = jestConfig; +/** + * @import { Config } from 'jest' + */ diff --git a/package.json b/package.json index b5330e402..d0cc1bd23 100644 --- a/package.json +++ b/package.json @@ -1,96 +1,132 @@ { "name": "nhsuk-react-components", - "version": "6.0.0-beta.0", + "version": "6.0.0-beta.1", + "license": "MIT", "author": { "name": "NHS England" }, + "sideEffects": false, + "type": "module", + "imports": { + "#components/*": { + "import": "./dist/esm/components/*", + "require": "./dist/cjs/components/*" + }, + "#components": { + "import": "./dist/esm/components/index.js", + "require": "./dist/cjs/components/index.cjs" + }, + "#patterns/*": { + "import": "./dist/esm/patterns/*", + "require": "./dist/cjs/patterns/*" + }, + "#patterns": { + "import": "./dist/esm/patterns/index.js", + "require": "./dist/cjs/patterns/index.cjs" + }, + "#util/*": { + "import": "./dist/esm/util/*", + "require": "./dist/cjs/util/*" + } + }, + "exports": { + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/cjs/index.d.ts", + "default": "./dist/cjs/index.cjs" + } + }, + "./*": "./*", + "./package.json": "./package.json" + }, + "main": "dist/cjs/index.cjs", + "module": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", "files": [ - "dist", - "lib" + "src", + "dist" ], - "main": "dist/cjs/index.js", - "module": "dist/esm", - "types": "dist/index.d.ts", "scripts": { - "cleanup": "rm -rf dist/ > /dev/null && rm -rf lib/ > /dev/null", - "storybook": "storybook dev -p 6006", + "cleanup": "rm -rf dist/ > /dev/null", "build": "yarn cleanup && rollup -c", + "storybook": "NODE_ENV=development storybook dev -p 6006", + "build-storybook": "storybook build", "test": "jest", "test:watch": "jest --watch", - "test:ci": "jest --coverage", - "lint": "eslint 'src/**/*.{js,ts,tsx}' 'stories/**/*.{js,ts,tsx}'", - "lint:fix": "eslint 'src/**/*.{js,ts,tsx}' 'stories/**/*.{js,ts,tsx}' --fix", - "lint:ci": "eslint 'src/**/*.{js,ts,tsx}' 'stories/**/*.{js,ts,tsx}'", - "build-storybook": "storybook build", - "prepublishOnly": "yarn lint:ci && yarn test:ci && yarn storybook --smoke-test" + "lint": "yarn lint:types && yarn lint:js && yarn lint:prettier", + "lint:fix": "yarn lint:js:fix && yarn lint:prettier:fix", + "lint:prettier": "prettier --check .", + "lint:prettier:fix": "prettier --write .", + "lint:js": "eslint . --max-warnings 0", + "lint:js:fix": "yarn lint:js --fix", + "lint:types": "tsc --build tsconfig.json --pretty", + "prepublishOnly": "yarn lint && yarn test && yarn storybook --smoke-test" }, - "license": "MIT", "devDependencies": { - "@babel/core": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.23.3", - "@rollup/plugin-commonjs": "^25.0.7", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-terser": "^0.4.4", - "@rollup/plugin-typescript": "^11.1.6", - "@storybook/addon-actions": "^8.0.5", - "@storybook/addon-essentials": "^8.0.5", - "@storybook/addon-links": "^8.0.5", - "@storybook/blocks": "^8.0.5", - "@storybook/manager-api": "^8.0.5", - "@storybook/preview-api": "^8.0.5", - "@storybook/react": "^8.0.5", - "@storybook/react-vite": "^8.0.5", - "@storybook/theming": "^8.0.5", - "@testing-library/jest-dom": "^6.4.2", - "@testing-library/react": "^14.2.1", - "@types/babel__core": "^7", - "@types/jest": "^29.5.12", + "@babel/core": "^7.28.4", + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "@eslint/compat": "^1.4.0", + "@eslint/js": "^9.37.0", + "@rollup/plugin-babel": "^6.0.4", + "@rollup/plugin-commonjs": "^28.0.6", + "@rollup/plugin-node-resolve": "^16.0.1", + "@rollup/plugin-typescript": "^12.1.4", + "@storybook/addon-docs": "9.1.10", + "@storybook/addon-links": "^9.1.10", + "@storybook/react-vite": "^9.1.10", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.0", + "@types/eslint": "^9.6.1", + "@types/jest": "^30.0.0", "@types/jest-axe": "^3.5.9", - "@types/node": "^15.0.2", - "@types/react": "^18.2.60", - "@types/react-dom": "^18.2.19", - "@types/rollup-plugin-peer-deps-external": "^2.2.1", - "@typescript-eslint/eslint-plugin": "^7.1.0", - "@typescript-eslint/parser": "^7.1.0", - "babel-jest": "^29.7.0", - "chromatic": "^6.17.3", - "eslint": "^8.57.0", - "eslint-config-airbnb": "^19.0.4", - "eslint-config-prettier": "^9.1.0", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "jest": "^29.7.0", - "jest-axe": "^8.0.0", - "jest-environment-jsdom": "^29.7.0", + "@types/lodash": "^4.17.20", + "@types/node": "^24.6.2", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.1", + "babel-jest": "^30.2.0", + "babel-plugin-module-resolver": "^5.0.2", + "babel-plugin-replace-import-extension": "^1.1.5", + "classnames": "^2.5.1", + "eslint": "^9.37.0", + "eslint-config-prettier": "^10.1.8", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^6.1.0", + "globals": "^16.4.0", + "jest": "^30.2.0", + "jest-axe": "^10.0.0", + "jest-environment-jsdom": "^30.2.0", + "lodash": "^4.17.21", "nhsuk-frontend": "^10.0.0", "outdent": "^0.8.0", - "prettier": "^3.2.5", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "regenerator-runtime": "^0.13.7", - "rollup": "^4.13.0", - "rollup-plugin-dts": "^6.1.0", - "rollup-plugin-peer-deps-external": "^2.2.4", + "prettier": "^3.6.2", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "rollup": "^4.52.4", "rollup-plugin-preserve-directives": "^0.4.0", - "rollup-plugin-tsconfig-paths": "^1.5.2", - "sass": "^1.53.0", - "storybook": "^8.0.5", - "ts-jest": "^29.1.2", - "typescript": "5.3.3", - "vite": "^4.5.3", - "vite-tsconfig-paths": "^4.3.2" - }, - "dependencies": { - "classnames": "^2.2.6" + "sass-embedded": "^1.93.2", + "storybook": "^9.1.10", + "tslib": "^2.8.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.45.0", + "vite": "^7.1.9", + "vite-tsconfig-paths": "^5.1.4" }, "peerDependencies": { + "classnames": ">=2.5.0", "nhsuk-frontend": ">=10.0.0 <11.0.0", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react": ">=18.2.0", + "react-dom": ">=18.2.0", + "tslib": ">=2.8.0" }, - "packageManager": "yarn@4.1.1" + "packageManager": "yarn@4.10.3" } diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 000000000..fadfee86c --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,81 @@ +import { join } from 'node:path'; +import { babel } from '@rollup/plugin-babel'; +import commonjs from '@rollup/plugin-commonjs'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import typescript from '@rollup/plugin-typescript'; +import preserveDirectives from 'rollup-plugin-preserve-directives'; +import { defineConfig } from 'rollup'; +import packageJson from './package.json' with { type: 'json' }; +import tsBuildConfig from './tsconfig.build.json' with { type: 'json' }; + +const { outDir } = tsBuildConfig.compilerOptions; +const external = Object.keys(packageJson.peerDependencies); + +export default defineConfig( + /** @satisfies {OutputOptions[]} */ ([ + { + entryFileNames: '[name].cjs', + format: 'cjs', + }, + { + entryFileNames: '[name].js', + format: 'esm', + }, + ]).map( + /** + * Rollup options for each module format + * + * @returns {RollupOptions} + */ + (options) => ({ + input: 'src/index.ts', + output: [ + { + ...options, + dir: join(outDir, options.format), + preserveModules: true, + preserveModulesRoot: 'src', + sourcemap: true, + sourcemapExcludeSources: true, + }, + ], + external: ['react/jsx-runtime', ...external], + jsx: /** @type {const} */ ('react-jsx'), + treeshake: false, + plugins: [ + nodeResolve({ + browser: true, + }), + commonjs(), + typescript({ + tsconfig: 'tsconfig.build.json', + compilerOptions: { + emitDeclarationOnly: true, + outDir: join(outDir, options.format), + }, + }), + preserveDirectives(), + babel({ + babelHelpers: 'bundled', + exclude: 'node_modules/**', + }), + ], + + // Handle warnings as errors + onwarn(warning) { + const { code, message } = warning; + + // Skip warnings about "use client" directives + if (code === 'MODULE_LEVEL_DIRECTIVE' && message.includes(`"use client"`)) { + return; + } + + throw new Error(warning.message, { cause: warning }); + }, + }), + ), +); + +/** + * @import { OutputOptions, RollupOptions } from 'rollup' + */ diff --git a/rollup.config.mjs b/rollup.config.mjs deleted file mode 100644 index 7dc3c9725..000000000 --- a/rollup.config.mjs +++ /dev/null @@ -1,89 +0,0 @@ -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import typescript from '@rollup/plugin-typescript'; -import terser from '@rollup/plugin-terser'; -import external from 'rollup-plugin-peer-deps-external'; -import { dts } from 'rollup-plugin-dts'; -import tsPaths from 'rollup-plugin-tsconfig-paths'; -import preserveDirectives from 'rollup-plugin-preserve-directives'; - -import tsBuildConfig from './bundle-base.tsconfig.json' with { type: 'json' }; -import packageJson from './package.json' with { type: 'json' }; - -// suppresses warnings printed to console as part of bundling components with directives present. -const onWarnSuppression = { - onwarn(warning, warn) { - if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes(`"use client"`)) { - return; - } - warn(warning); - }, -}; - -const commonPlugins = [external(), tsPaths(), resolve(), commonjs()]; - -export default [ - // cjs export - { - input: 'src/index.ts', - output: [ - { - file: packageJson.main, - format: 'cjs', - sourcemap: true, - }, - ], - plugins: [ - ...commonPlugins, - typescript({ - tsconfig: 'bundle-base.tsconfig.json', - compilerOptions: { - declaration: false, - }, - }), - terser(), - ], - ...onWarnSuppression, - }, - // esm export - { - input: 'src/index.ts', - output: [ - { - dir: packageJson.module, - format: 'esm', - sourcemap: true, - preserveModules: true, - preserveModulesRoot: 'src', - }, - ], - plugins: [ - ...commonPlugins, - typescript({ - tsconfig: 'bundle-base.tsconfig.json', - compilerOptions: { - declaration: true, - declarationDir: 'dist/esm', - emitDeclarationOnly: true, - outDir: 'dist/esm', - }, - }), - preserveDirectives(), - terser({ compress: { directives: false } }), - ], - ...onWarnSuppression, - }, - // type bundling - { - input: 'src/index.ts', - output: [{ file: 'dist/index.d.ts', format: 'esm' }], - external: [], - plugins: [ - dts({ - compilerOptions: { - paths: tsBuildConfig.compilerOptions.paths, - }, - }), - ], - }, -]; diff --git a/src/__mocks__/styleMock.ts b/src/__mocks__/styleMock.ts deleted file mode 100644 index f053ebf79..000000000 --- a/src/__mocks__/styleMock.ts +++ /dev/null @@ -1 +0,0 @@ -module.exports = {}; diff --git a/src/__tests__/index.test.ts b/src/__tests__/index.test.ts index 2f325f87b..f20617293 100644 --- a/src/__tests__/index.test.ts +++ b/src/__tests__/index.test.ts @@ -1,10 +1,11 @@ -import * as index from '../index'; +import * as index from '..'; describe('Index', () => { it('contains all expected elements', () => { const sortedIndex = Object.keys(index).sort((a, b) => a.localeCompare(b)); expect(sortedIndex).toEqual([ + '__esModule', // Synthetic default export 'ActionLink', 'ArrowLeftIcon', 'ArrowRightCircleIcon', @@ -14,15 +15,20 @@ describe('Index', () => { 'Breadcrumb', 'Button', 'Card', + 'CardContext', + 'cardTypeIsCareCard', 'CharacterCount', 'Checkboxes', + 'CheckboxesContext', 'ChevronRightCircleIcon', + 'childIsOfComponentType', 'Clearfix', 'Col', 'Container', 'ContentsList', 'CrossIcon', 'DateInput', + 'DateInputContext', 'Details', 'DoAndDontList', 'ErrorMessage', @@ -30,10 +36,15 @@ describe('Index', () => { 'Fieldset', 'Footer', 'Form', + 'FormContext', 'FormGroup', + 'FormGroupContext', 'Header', + 'HeaderContext', + 'HeadingLevel', 'Hero', 'HintText', + 'Icon', 'Images', 'InsetText', 'Label', @@ -41,7 +52,9 @@ describe('Index', () => { 'Legend', 'NavAZ', 'Pagination', + 'Panel', 'Radios', + 'RadiosContext', 'ReadingWidth', 'ReviewDate', 'Row', @@ -50,6 +63,9 @@ describe('Index', () => { 'SkipLink', 'SummaryList', 'Table', + 'TableContext', + 'TableSection', + 'TableSectionContext', 'Tabs', 'Tag', 'Textarea', diff --git a/src/components/content-presentation/details/Details.tsx b/src/components/content-presentation/details/Details.tsx index b5dcfc1b1..12c169d64 100644 --- a/src/components/content-presentation/details/Details.tsx +++ b/src/components/content-presentation/details/Details.tsx @@ -1,5 +1,5 @@ -import React, { ComponentPropsWithoutRef, FC, forwardRef } from 'react'; import classNames from 'classnames'; +import { forwardRef, type ComponentPropsWithoutRef, type FC } from 'react'; export interface DetailsProps extends ComponentPropsWithoutRef<'details'> { expander?: boolean; @@ -40,7 +40,7 @@ DetailsSummary.displayName = 'Details.Summary'; DetailsText.displayName = 'Details.Text'; ExpanderGroup.displayName = 'Details.ExpanderGroup'; -export default Object.assign(DetailsComponent, { +export const Details = Object.assign(DetailsComponent, { Summary: DetailsSummary, Text: DetailsText, ExpanderGroup, diff --git a/src/components/content-presentation/details/__tests__/Details.test.tsx b/src/components/content-presentation/details/__tests__/Details.test.tsx index b9e237a0f..5e558eea3 100644 --- a/src/components/content-presentation/details/__tests__/Details.test.tsx +++ b/src/components/content-presentation/details/__tests__/Details.test.tsx @@ -1,7 +1,7 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import { renderClient, renderServer } from '@util/components'; -import Details from '../'; +import { createRef } from 'react'; +import { Details } from '..'; +import { renderClient, renderServer } from '#util/components'; describe('Details', () => { it('matches snapshot', async () => { diff --git a/src/components/content-presentation/details/__tests__/__snapshots__/Details.test.tsx.snap b/src/components/content-presentation/details/__tests__/__snapshots__/Details.test.tsx.snap index 51e90f283..2850e5662 100644 --- a/src/components/content-presentation/details/__tests__/__snapshots__/Details.test.tsx.snap +++ b/src/components/content-presentation/details/__tests__/__snapshots__/Details.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Details Details.ExpanderGroup matches snapshot: Details.ExpanderGroup 1`] = `
diff --git a/src/components/content-presentation/details/index.ts b/src/components/content-presentation/details/index.ts index 72e3cee2a..c55898bc0 100644 --- a/src/components/content-presentation/details/index.ts +++ b/src/components/content-presentation/details/index.ts @@ -1 +1 @@ -export { default } from './Details'; +export * from './Details.js'; diff --git a/src/components/content-presentation/do-and-dont-list/DoAndDontList.tsx b/src/components/content-presentation/do-and-dont-list/DoAndDontList.tsx index 9573da7f1..7c2a12db4 100644 --- a/src/components/content-presentation/do-and-dont-list/DoAndDontList.tsx +++ b/src/components/content-presentation/do-and-dont-list/DoAndDontList.tsx @@ -1,14 +1,16 @@ -import React, { - ComponentPropsWithoutRef, - FC, +'use client'; + +import classNames from 'classnames'; +import { createContext, - useContext, - ReactNode, forwardRef, + useContext, + type ComponentPropsWithoutRef, + type FC, + type ReactNode, } from 'react'; -import classNames from 'classnames'; -import { Tick, Cross } from '@components/content-presentation/icons'; -import HeadingLevel, { HeadingLevelProps } from '@components/utils/HeadingLevel'; +import { CrossIcon, TickIcon } from '../icons/index.js'; +import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js'; type ListType = 'do' | 'dont'; @@ -53,12 +55,12 @@ const DoAndDontItem: FC = ({ prefixText, listItemType, child
  • {(listItemType || listItem) === 'do' ? ( <> - + {actualPrefix} ) : ( <> - + {actualPrefix} )} @@ -70,6 +72,6 @@ const DoAndDontItem: FC = ({ prefixText, listItemType, child DoAndDontListComponent.displayName = 'DoAndDontList'; DoAndDontItem.displayName = 'DoAndDontList.Item'; -export default Object.assign(DoAndDontListComponent, { +export const DoAndDontList = Object.assign(DoAndDontListComponent, { Item: DoAndDontItem, }); diff --git a/src/components/content-presentation/do-and-dont-list/__tests__/DoAndDontList.test.tsx b/src/components/content-presentation/do-and-dont-list/__tests__/DoAndDontList.test.tsx index 129c20207..f7519b4fd 100644 --- a/src/components/content-presentation/do-and-dont-list/__tests__/DoAndDontList.test.tsx +++ b/src/components/content-presentation/do-and-dont-list/__tests__/DoAndDontList.test.tsx @@ -1,7 +1,7 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import DoAndDontList from '../'; -import { renderClient, renderServer } from '@util/components'; +import { createRef } from 'react'; +import { DoAndDontList } from '..'; +import { renderClient, renderServer } from '#util/components'; describe('DoAndDontList', () => { describe('list type "do"', () => { diff --git a/src/components/content-presentation/do-and-dont-list/__tests__/__snapshots__/DoAndDontList.test.tsx.snap b/src/components/content-presentation/do-and-dont-list/__tests__/__snapshots__/DoAndDontList.test.tsx.snap index f45722b76..549880e7e 100644 --- a/src/components/content-presentation/do-and-dont-list/__tests__/__snapshots__/DoAndDontList.test.tsx.snap +++ b/src/components/content-presentation/do-and-dont-list/__tests__/__snapshots__/DoAndDontList.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`DoAndDontList DoDontList.Item matches snapshot: DoDontList.Item 1`] = `
    diff --git a/src/components/content-presentation/do-and-dont-list/index.ts b/src/components/content-presentation/do-and-dont-list/index.ts index f6db6b152..bd8c91926 100644 --- a/src/components/content-presentation/do-and-dont-list/index.ts +++ b/src/components/content-presentation/do-and-dont-list/index.ts @@ -1 +1 @@ -export { default } from './DoAndDontList'; +export * from './DoAndDontList.js'; diff --git a/src/components/content-presentation/hero/Hero.tsx b/src/components/content-presentation/hero/Hero.tsx index ccecc19cf..1020ac53c 100644 --- a/src/components/content-presentation/hero/Hero.tsx +++ b/src/components/content-presentation/hero/Hero.tsx @@ -1,7 +1,7 @@ -import React, { ComponentPropsWithoutRef, FC, forwardRef } from 'react'; import classNames from 'classnames'; -import { Container, Row, Col } from '../../layout'; -import HeadingLevel, { HeadingLevelProps } from '@components/utils/HeadingLevel'; +import { forwardRef, type ComponentPropsWithoutRef, type FC } from 'react'; +import { Col, Container, Row } from '#components/layout/index.js'; +import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js'; export interface HeroContentProps extends ComponentPropsWithoutRef<'div'> { hasImage: boolean; @@ -73,7 +73,7 @@ HeroComponent.displayName = 'Hero'; HeroHeading.displayName = 'Hero.Heading'; HeroText.displayName = 'Hero.Text'; -export default Object.assign(HeroComponent, { +export const Hero = Object.assign(HeroComponent, { Heading: HeroHeading, Text: HeroText, }); diff --git a/src/components/content-presentation/hero/__tests__/Hero.test.tsx b/src/components/content-presentation/hero/__tests__/Hero.test.tsx index 6678abd3f..39c661c7c 100644 --- a/src/components/content-presentation/hero/__tests__/Hero.test.tsx +++ b/src/components/content-presentation/hero/__tests__/Hero.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import Hero from '..'; +import { createRef } from 'react'; +import { Hero } from '..'; describe('Hero', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/hero/__tests__/__snapshots__/Hero.test.tsx.snap b/src/components/content-presentation/hero/__tests__/__snapshots__/Hero.test.tsx.snap index e371cb6c9..245ec3d21 100644 --- a/src/components/content-presentation/hero/__tests__/__snapshots__/Hero.test.tsx.snap +++ b/src/components/content-presentation/hero/__tests__/__snapshots__/Hero.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Hero Hero.Heading matches snapshot: Hero.Heading 1`] = `
    diff --git a/src/components/content-presentation/hero/index.ts b/src/components/content-presentation/hero/index.ts index 9c292bc06..60b8a72de 100644 --- a/src/components/content-presentation/hero/index.ts +++ b/src/components/content-presentation/hero/index.ts @@ -1 +1 @@ -export { default } from './Hero'; +export * from './Hero.js'; diff --git a/src/components/content-presentation/icons/BaseIcon.tsx b/src/components/content-presentation/icons/Icon.tsx similarity index 78% rename from src/components/content-presentation/icons/BaseIcon.tsx rename to src/components/content-presentation/icons/Icon.tsx index 04e7c1bbc..ccbbb90d3 100644 --- a/src/components/content-presentation/icons/BaseIcon.tsx +++ b/src/components/content-presentation/icons/Icon.tsx @@ -1,7 +1,7 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; -export interface BaseIconSVGProps extends ComponentPropsWithoutRef<'svg'> { +export interface IconProps extends ComponentPropsWithoutRef<'svg'> { title?: string; modifier?: | 'arrow-left' @@ -16,10 +16,10 @@ export interface BaseIconSVGProps extends ComponentPropsWithoutRef<'svg'> { /** * @deprecated Use `modifier` instead. */ - iconType?: BaseIconSVGProps['modifier']; + iconType?: IconProps['modifier']; } -export const BaseIconSVG: FC = ({ +export const Icon: FC = ({ className, children, iconType, diff --git a/src/components/content-presentation/icons/__tests__/Icons.test.tsx b/src/components/content-presentation/icons/__tests__/Icons.test.tsx index 9cdff5e4e..41ad773e1 100644 --- a/src/components/content-presentation/icons/__tests__/Icons.test.tsx +++ b/src/components/content-presentation/icons/__tests__/Icons.test.tsx @@ -1,10 +1,11 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import * as Icons from '../'; +import * as Icons from '..'; describe('Icons', () => { it('all icons match snapshots', () => { - for (const [name, Icon] of Object.entries(Icons)) { + for (const [name, Icon] of Object.entries(Icons).filter( + ([, Icon]) => Icon instanceof Function, + )) { const { container } = render(); expect(container).toMatchSnapshot(name); } diff --git a/src/components/content-presentation/icons/__tests__/__snapshots__/Icons.test.tsx.snap b/src/components/content-presentation/icons/__tests__/__snapshots__/Icons.test.tsx.snap index f3214c393..494382042 100644 --- a/src/components/content-presentation/icons/__tests__/__snapshots__/Icons.test.tsx.snap +++ b/src/components/content-presentation/icons/__tests__/__snapshots__/Icons.test.tsx.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`Icons all icons match snapshots: ArrowLeft 1`] = ` +exports[`Icons all icons match snapshots: ArrowLeftIcon 1`] = `
    `; -exports[`Icons all icons match snapshots: ArrowRightCircle 1`] = ` +exports[`Icons all icons match snapshots: ArrowRightIcon 1`] = `
    `; -exports[`Icons all icons match snapshots: ChevronRightCircle 1`] = ` +exports[`Icons all icons match snapshots: ChevronRightCircleIcon 1`] = `
    + +
    +`; + +exports[`Icons all icons match snapshots: SearchIcon 1`] = `
    {alt} @@ -14,6 +14,4 @@ const ImagesComponent = forwardRef( ), ); -ImagesComponent.displayName = 'Images'; - -export default ImagesComponent; +Images.displayName = 'Images'; diff --git a/src/components/content-presentation/images/__tests__/Images.test.tsx b/src/components/content-presentation/images/__tests__/Images.test.tsx index 36e8c93e8..061375340 100644 --- a/src/components/content-presentation/images/__tests__/Images.test.tsx +++ b/src/components/content-presentation/images/__tests__/Images.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import Images from '../'; +import { createRef } from 'react'; +import { Images } from '..'; describe('Images', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/images/__tests__/__snapshots__/Images.test.tsx.snap b/src/components/content-presentation/images/__tests__/__snapshots__/Images.test.tsx.snap index 1a36030e6..e4a05a753 100644 --- a/src/components/content-presentation/images/__tests__/__snapshots__/Images.test.tsx.snap +++ b/src/components/content-presentation/images/__tests__/__snapshots__/Images.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Images matches snapshot: Images 1`] = `
    diff --git a/src/components/content-presentation/images/index.ts b/src/components/content-presentation/images/index.ts index c75230f3c..9d399e721 100644 --- a/src/components/content-presentation/images/index.ts +++ b/src/components/content-presentation/images/index.ts @@ -1 +1 @@ -export { default } from './Images'; +export * from './Images.js'; diff --git a/src/components/content-presentation/index.ts b/src/components/content-presentation/index.ts new file mode 100644 index 000000000..55a38c078 --- /dev/null +++ b/src/components/content-presentation/index.ts @@ -0,0 +1,12 @@ +export * from './details/index.js'; +export * from './do-and-dont-list/index.js'; +export * from './hero/index.js'; +export * from './icons/index.js'; +export * from './images/index.js'; +export * from './inset-text/index.js'; +export * from './panel/index.js'; +export * from './summary-list/index.js'; +export * from './table/index.js'; +export * from './tabs/index.js'; +export * from './tag/index.js'; +export * from './warning-callout/index.js'; diff --git a/src/components/content-presentation/inset-text/InsetText.tsx b/src/components/content-presentation/inset-text/InsetText.tsx index cdd0ee5ad..40446b65c 100644 --- a/src/components/content-presentation/inset-text/InsetText.tsx +++ b/src/components/content-presentation/inset-text/InsetText.tsx @@ -1,9 +1,9 @@ -import React, { ComponentPropsWithoutRef, forwardRef } from 'react'; import classNames from 'classnames'; +import { forwardRef, type ComponentPropsWithoutRef } from 'react'; -type InsetTextProps = ComponentPropsWithoutRef<'div'>; +export type InsetTextProps = ComponentPropsWithoutRef<'div'>; -const InsetTextComponent = forwardRef( +export const InsetText = forwardRef( ({ className, children, ...rest }, forwardedRef) => (
    Information: @@ -12,6 +12,4 @@ const InsetTextComponent = forwardRef( ), ); -InsetTextComponent.displayName = 'InsetText'; - -export default InsetTextComponent; +InsetText.displayName = 'InsetText'; diff --git a/src/components/content-presentation/inset-text/__tests__/InsetText.test.tsx b/src/components/content-presentation/inset-text/__tests__/InsetText.test.tsx index 947a45d49..1e44a5ae4 100644 --- a/src/components/content-presentation/inset-text/__tests__/InsetText.test.tsx +++ b/src/components/content-presentation/inset-text/__tests__/InsetText.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import InsetText from '../'; +import { createRef } from 'react'; +import { InsetText } from '..'; describe('InsetText', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/inset-text/__tests__/__snapshots__/InsetText.test.tsx.snap b/src/components/content-presentation/inset-text/__tests__/__snapshots__/InsetText.test.tsx.snap index 26e2812e8..c8a5225b0 100644 --- a/src/components/content-presentation/inset-text/__tests__/__snapshots__/InsetText.test.tsx.snap +++ b/src/components/content-presentation/inset-text/__tests__/__snapshots__/InsetText.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`InsetText matches snapshot: InsetText 1`] = `
    diff --git a/src/components/content-presentation/inset-text/index.ts b/src/components/content-presentation/inset-text/index.ts index 4839c4894..1e600b983 100644 --- a/src/components/content-presentation/inset-text/index.ts +++ b/src/components/content-presentation/inset-text/index.ts @@ -1 +1 @@ -export { default } from './InsetText'; +export * from './InsetText.js'; diff --git a/src/components/content-presentation/panel/Panel.tsx b/src/components/content-presentation/panel/Panel.tsx new file mode 100644 index 000000000..ce2d1f352 --- /dev/null +++ b/src/components/content-presentation/panel/Panel.tsx @@ -0,0 +1,36 @@ +import classNames from 'classnames'; +import { Children, forwardRef, type ComponentPropsWithoutRef, type FC } from 'react'; +import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js'; +import { childIsOfComponentType } from '#util/types/TypeGuards.js'; + +export type PanelTitleProps = HeadingLevelProps; + +const PanelTitle: FC = ({ children, headingLevel = 'h1', ...rest }) => ( + + {children} + +); + +export type PanelProps = ComponentPropsWithoutRef<'div'>; + +const PanelComponent = forwardRef( + ({ children, className, ...rest }, forwardedRef) => { + const items = Children.toArray(children); + const title = items.find((child) => childIsOfComponentType(child, PanelTitle)); + const bodyItems = items.filter((child) => !childIsOfComponentType(child, PanelTitle)); + + return ( +
    + {title} + {bodyItems ?
    {bodyItems}
    : null} +
    + ); + }, +); + +PanelComponent.displayName = 'Panel'; +PanelComponent.displayName = 'Panel.Title'; + +export const Panel = Object.assign(PanelComponent, { + Title: PanelTitle, +}); diff --git a/src/components/content-presentation/panel/__tests__/Panel.test.tsx b/src/components/content-presentation/panel/__tests__/Panel.test.tsx new file mode 100644 index 000000000..1cfde1a6e --- /dev/null +++ b/src/components/content-presentation/panel/__tests__/Panel.test.tsx @@ -0,0 +1,74 @@ +import { render } from '@testing-library/react'; +import { createRef } from 'react'; +import { Panel, type PanelTitleProps } from '..'; +import { renderClient, renderServer } from '#util/components'; + +describe('Panel', () => { + it('matches snapshot', async () => { + const { container } = await renderClient( + + Booking complete + We have sent you a confirmation email + , + { className: 'nhsuk-panel' }, + ); + + expect(container).toMatchSnapshot(); + }); + + it('matches snapshot (via server)', async () => { + const { container, element } = await renderServer( + + Booking complete + We have sent you a confirmation email + , + { className: 'nhsuk-panel' }, + ); + + expect(container).toMatchSnapshot('server'); + + await renderClient(element, { + className: 'nhsuk-panel', + hydrate: true, + container, + }); + + expect(container).toMatchSnapshot('client'); + }); + + it('forwards refs', async () => { + const ref = createRef(); + + const { modules } = await renderClient( + + Booking complete + We have sent you a confirmation email + , + { className: 'nhsuk-panel' }, + ); + + const [panelEl] = modules; + + expect(ref.current).toBe(panelEl); + expect(ref.current).toHaveClass('nhsuk-panel'); + }); + + it.each([ + undefined, + { headingLevel: 'h1' }, + { headingLevel: 'h2' }, + { headingLevel: 'h3' }, + { headingLevel: 'h4' }, + ])('renders heading level $headingLevel if specified', (props) => { + const { container } = render( + + Booking complete + We have sent you a confirmation email + , + ); + + const title = container.querySelector('.nhsuk-panel__title'); + + expect(title).toHaveProperty('tagName', props?.headingLevel?.toUpperCase() ?? 'H1'); + }); +}); diff --git a/src/components/content-presentation/panel/__tests__/__snapshots__/Panel.test.tsx.snap b/src/components/content-presentation/panel/__tests__/__snapshots__/Panel.test.tsx.snap new file mode 100644 index 000000000..a2ae20ed3 --- /dev/null +++ b/src/components/content-presentation/panel/__tests__/__snapshots__/Panel.test.tsx.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`Panel matches snapshot (via server): client 1`] = ` +
    +
    +

    + Booking complete +

    +
    + We have sent you a confirmation email +
    +
    +
    +`; + +exports[`Panel matches snapshot (via server): server 1`] = ` +
    +
    +

    + Booking complete +

    +
    + We have sent you a confirmation email +
    +
    +
    +`; + +exports[`Panel matches snapshot 1`] = ` +
    +
    +

    + Booking complete +

    +
    + We have sent you a confirmation email +
    +
    +
    +`; diff --git a/src/components/content-presentation/panel/index.ts b/src/components/content-presentation/panel/index.ts new file mode 100644 index 000000000..81c82d413 --- /dev/null +++ b/src/components/content-presentation/panel/index.ts @@ -0,0 +1 @@ +export * from './Panel.js'; diff --git a/src/components/content-presentation/summary-list/SummaryList.tsx b/src/components/content-presentation/summary-list/SummaryList.tsx index 21bf8da5e..97e2e6787 100644 --- a/src/components/content-presentation/summary-list/SummaryList.tsx +++ b/src/components/content-presentation/summary-list/SummaryList.tsx @@ -1,5 +1,5 @@ -import React, { ComponentPropsWithoutRef, FC, forwardRef } from 'react'; import classNames from 'classnames'; +import { forwardRef, type ComponentPropsWithoutRef, type FC } from 'react'; const SummaryListRow: FC> = ({ className, ...rest }) => (
    @@ -41,7 +41,7 @@ SummaryListKey.displayName = 'SummaryList.Key'; SummaryListValue.displayName = 'SummaryList.Value'; SummaryListActions.displayName = 'SummaryList.Actions'; -export default Object.assign(SummaryListComponent, { +export const SummaryList = Object.assign(SummaryListComponent, { Row: SummaryListRow, Key: SummaryListKey, Value: SummaryListValue, diff --git a/src/components/content-presentation/summary-list/__tests__/SummaryList.test.tsx b/src/components/content-presentation/summary-list/__tests__/SummaryList.test.tsx index 005689611..a34c39b41 100644 --- a/src/components/content-presentation/summary-list/__tests__/SummaryList.test.tsx +++ b/src/components/content-presentation/summary-list/__tests__/SummaryList.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import SummaryList from '../'; +import { createRef } from 'react'; +import { SummaryList } from '..'; describe('SummaryList', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/summary-list/__tests__/__snapshots__/SummaryList.test.tsx.snap b/src/components/content-presentation/summary-list/__tests__/__snapshots__/SummaryList.test.tsx.snap index e8e99c411..5cf97f27f 100644 --- a/src/components/content-presentation/summary-list/__tests__/__snapshots__/SummaryList.test.tsx.snap +++ b/src/components/content-presentation/summary-list/__tests__/__snapshots__/SummaryList.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`SummaryList SummaryList.Actions matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/summary-list/index.ts b/src/components/content-presentation/summary-list/index.ts index b5626639e..7a6b76892 100644 --- a/src/components/content-presentation/summary-list/index.ts +++ b/src/components/content-presentation/summary-list/index.ts @@ -1 +1 @@ -export { default } from './SummaryList'; +export * from './SummaryList.js'; diff --git a/src/components/content-presentation/table/Table.tsx b/src/components/content-presentation/table/Table.tsx index 6dde12172..07bad0aeb 100644 --- a/src/components/content-presentation/table/Table.tsx +++ b/src/components/content-presentation/table/Table.tsx @@ -1,13 +1,24 @@ -import React, { ComponentPropsWithoutRef, ReactNode, forwardRef, useMemo, useState } from 'react'; +'use client'; + import classNames from 'classnames'; -import TableBody from './components/TableBody'; -import TableCaption, { TableCaptionProps } from './components/TableCaption'; -import TableCell from './components/TableCell'; -import TableContainer from './components/TableContainer'; -import TableHead from './components/TableHead'; -import TableRow from './components/TableRow'; -import TablePanel from './components/TablePanel'; -import TableContext, { ITableContext } from './TableContext'; +import { + forwardRef, + useMemo, + useState, + type ComponentPropsWithoutRef, + type ReactNode, +} from 'react'; +import { + TableBody, + TableCaption, + TableCell, + TableContainer, + TableHead, + TablePanel, + TableRow, + type TableCaptionProps, +} from './components/index.js'; +import { TableContext, type ITableContext } from './TableContext.js'; export interface TableProps extends ComponentPropsWithoutRef<'table'> { firstCellIsHeader?: boolean; @@ -36,7 +47,7 @@ const TableComponent = forwardRef((props, forwarde responsive, setHeadings, }; - }, [responsive, headings, setHeadings]); + }, [firstCellIsHeader, headings, responsive, setHeadings]); return ( @@ -59,7 +70,7 @@ const TableComponent = forwardRef((props, forwarde TableComponent.displayName = 'Table'; -export default Object.assign(TableComponent, { +export const Table = Object.assign(TableComponent, { Container: TableContainer, Panel: TablePanel, Head: TableHead, diff --git a/src/components/content-presentation/table/TableContext.ts b/src/components/content-presentation/table/TableContext.ts index 1b2c027cc..779d58c05 100644 --- a/src/components/content-presentation/table/TableContext.ts +++ b/src/components/content-presentation/table/TableContext.ts @@ -1,4 +1,6 @@ -import { ReactNode, createContext } from 'react'; +'use client'; + +import { createContext, type ReactNode } from 'react'; export interface ITableContext { firstCellIsHeader: boolean; @@ -7,12 +9,9 @@ export interface ITableContext { setHeadings(headings: ReactNode[]): void; } -const TableContext = createContext({ - /* eslint-disable @typescript-eslint/no-empty-function */ +export const TableContext = createContext({ firstCellIsHeader: false, headings: [], responsive: false, setHeadings: () => {}, }); - -export default TableContext; diff --git a/src/components/content-presentation/table/TableHelpers.ts b/src/components/content-presentation/table/TableHelpers.ts index ba818b778..6ce790571 100644 --- a/src/components/content-presentation/table/TableHelpers.ts +++ b/src/components/content-presentation/table/TableHelpers.ts @@ -1,10 +1,11 @@ -import { Children, isValidElement, ReactElement, ReactNode } from 'react'; -import TableCell, { TableCellProps } from './components/TableCell'; +import { Children, type ReactElement, type ReactNode } from 'react'; +import { TableCell, type TableCellProps } from './components/TableCell.js'; +import { childIsOfComponentType } from '#util/types/TypeGuards.js'; export const isTableCell = ( child: ReactNode, ): child is ReactElement => { - return isValidElement(child) && child.type === TableCell; + return childIsOfComponentType(child, TableCell); }; export const getHeadingsFromChildren = (children?: ReactNode) => { diff --git a/src/components/content-presentation/table/TableSectionContext.ts b/src/components/content-presentation/table/TableSectionContext.ts index b812916d1..86b80b5cf 100644 --- a/src/components/content-presentation/table/TableSectionContext.ts +++ b/src/components/content-presentation/table/TableSectionContext.ts @@ -1,3 +1,5 @@ +'use client'; + import { createContext } from 'react'; export enum TableSection { @@ -6,6 +8,4 @@ export enum TableSection { BODY, } -const TableSectionContext = createContext(TableSection.NONE); - -export default TableSectionContext; +export const TableSectionContext = createContext(TableSection.NONE); diff --git a/src/components/content-presentation/table/components/TableBody.tsx b/src/components/content-presentation/table/components/TableBody.tsx index 9034b1515..606465c9c 100644 --- a/src/components/content-presentation/table/components/TableBody.tsx +++ b/src/components/content-presentation/table/components/TableBody.tsx @@ -1,8 +1,10 @@ import classNames from 'classnames'; -import React, { ComponentPropsWithoutRef, FC } from 'react'; -import TableSectionContext, { TableSection } from '../TableSectionContext'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; +import { TableSection, TableSectionContext } from '../TableSectionContext.js'; -const TableBody: FC> = ({ children, className, ...rest }) => ( +export type TableBodyProps = ComponentPropsWithoutRef<'tbody'>; + +export const TableBody: FC = ({ children, className, ...rest }) => ( {children} @@ -11,5 +13,3 @@ const TableBody: FC> = ({ children, className, ); TableBody.displayName = 'Table.Body'; - -export default TableBody; diff --git a/src/components/content-presentation/table/components/TableCaption.tsx b/src/components/content-presentation/table/components/TableCaption.tsx index 6374171f7..b5949cd7a 100644 --- a/src/components/content-presentation/table/components/TableCaption.tsx +++ b/src/components/content-presentation/table/components/TableCaption.tsx @@ -1,12 +1,12 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; -import { NHSUKSize } from '@util/types/NHSUKTypes'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; +import { type NHSUKSize } from '#util/types/NHSUKTypes.js'; export interface TableCaptionProps extends ComponentPropsWithoutRef<'caption'> { size?: NHSUKSize; } -const TableCaption: FC = ({ className, size, ...rest }) => ( +export const TableCaption: FC = ({ className, size, ...rest }) => ( = ({ className, size, ...rest }) => ( ); TableCaption.displayName = 'Table.Caption'; - -export default TableCaption; diff --git a/src/components/content-presentation/table/components/TableCell.tsx b/src/components/content-presentation/table/components/TableCell.tsx index 683989389..07288afc0 100644 --- a/src/components/content-presentation/table/components/TableCell.tsx +++ b/src/components/content-presentation/table/components/TableCell.tsx @@ -1,8 +1,10 @@ +'use client'; + import classNames from 'classnames'; -import React, { ComponentPropsWithoutRef, FC, useContext } from 'react'; -import useDevWarning from '@util/hooks/UseDevWarning'; -import TableContext, { ITableContext } from '../TableContext'; -import TableSectionContext, { TableSection } from '../TableSectionContext'; +import { useContext, type ComponentPropsWithoutRef, type FC } from 'react'; +import { TableContext, type ITableContext } from '../TableContext.js'; +import { TableSection, TableSectionContext } from '../TableSectionContext.js'; +import { useDevWarning } from '#util/hooks/index.js'; const CellOutsideOfSectionWarning = 'Table.Cell used outside of a Table.Head or Table.Body component. Unable to determine section type from context.'; @@ -14,7 +16,13 @@ export interface TableCellProps format?: 'numeric'; } -const TableCell: FC = ({ className, format, children, index = -1, ...rest }) => { +export const TableCell: FC = ({ + className, + format, + children, + index = -1, + ...rest +}) => { const { firstCellIsHeader, headings, responsive } = useContext(TableContext); const section = useContext(TableSectionContext); @@ -61,5 +69,3 @@ const TableCell: FC = ({ className, format, children, index = -1 }; TableCell.displayName = 'Table.Cell'; - -export default TableCell; diff --git a/src/components/content-presentation/table/components/TableContainer.tsx b/src/components/content-presentation/table/components/TableContainer.tsx index a538062de..eb201da46 100644 --- a/src/components/content-presentation/table/components/TableContainer.tsx +++ b/src/components/content-presentation/table/components/TableContainer.tsx @@ -1,14 +1,12 @@ -import React, { ComponentPropsWithoutRef, forwardRef } from 'react'; import classNames from 'classnames'; +import { forwardRef, type ComponentPropsWithoutRef } from 'react'; export type TableContainerProps = ComponentPropsWithoutRef<'div'>; -const TableContainer = forwardRef( +export const TableContainer = forwardRef( ({ className, ...rest }, forwardedRef) => (
    ), ); TableContainer.displayName = 'Table.Container'; - -export default TableContainer; diff --git a/src/components/content-presentation/table/components/TableHead.tsx b/src/components/content-presentation/table/components/TableHead.tsx index 40ea8c421..cc5b7d21f 100644 --- a/src/components/content-presentation/table/components/TableHead.tsx +++ b/src/components/content-presentation/table/components/TableHead.tsx @@ -1,9 +1,13 @@ -import React, { ComponentPropsWithoutRef, FC, useContext } from 'react'; +'use client'; + import classNames from 'classnames'; -import TableContext, { ITableContext } from '../TableContext'; -import TableSectionContext, { TableSection } from '../TableSectionContext'; +import { useContext, type ComponentPropsWithoutRef, type FC } from 'react'; +import { TableContext, type ITableContext } from '../TableContext.js'; +import { TableSection, TableSectionContext } from '../TableSectionContext.js'; + +export type TableHeadProps = ComponentPropsWithoutRef<'thead'>; -const TableHead: FC> = ({ children, className, ...rest }) => { +export const TableHead: FC = ({ children, className, ...rest }) => { const { responsive } = useContext(TableContext); return ( @@ -20,5 +24,3 @@ const TableHead: FC> = ({ children, className, }; TableHead.displayName = 'Table.Head'; - -export default TableHead; diff --git a/src/components/content-presentation/table/components/TablePanel.tsx b/src/components/content-presentation/table/components/TablePanel.tsx index 9484e4a91..2f9eb3e62 100644 --- a/src/components/content-presentation/table/components/TablePanel.tsx +++ b/src/components/content-presentation/table/components/TablePanel.tsx @@ -1,13 +1,13 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; -import HeadingLevel, { HeadingLevelProps } from '@components/utils/HeadingLevel'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; +import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js'; export interface TablePanelProps extends ComponentPropsWithoutRef<'div'> { heading?: string; headingProps?: HeadingLevelProps; } -const TablePanel: FC = ({ +export const TablePanel: FC = ({ className, heading, headingProps, @@ -28,5 +28,3 @@ const TablePanel: FC = ({ ); TablePanel.displayName = 'Table.Panel'; - -export default TablePanel; diff --git a/src/components/content-presentation/table/components/TableRow.tsx b/src/components/content-presentation/table/components/TableRow.tsx index 4567fb6d8..027b30c41 100644 --- a/src/components/content-presentation/table/components/TableRow.tsx +++ b/src/components/content-presentation/table/components/TableRow.tsx @@ -1,17 +1,19 @@ +'use client'; + import classNames from 'classnames'; -import React, { +import { Children, - ComponentPropsWithoutRef, - FC, cloneElement, useContext, useEffect, + type ComponentPropsWithoutRef, + type FC, } from 'react'; -import TableContext from '../TableContext'; -import { getHeadingsFromChildren, isTableCell } from '../TableHelpers'; -import TableSectionContext, { TableSection } from '../TableSectionContext'; +import { TableContext } from '../TableContext.js'; +import { TableSection, TableSectionContext } from '../TableSectionContext.js'; +import { getHeadingsFromChildren, isTableCell } from '../TableHelpers.js'; -const TableRow: FC> = ({ children, className, ...rest }) => { +export const TableRow: FC> = ({ children, className, ...rest }) => { const section = useContext(TableSectionContext); const { responsive, setHeadings } = useContext(TableContext); @@ -19,7 +21,7 @@ const TableRow: FC> = ({ children, className, ... if (responsive && section === TableSection.HEAD) { setHeadings(getHeadingsFromChildren(children)); } - }, [responsive, section, children]); + }, [children, responsive, section, setHeadings]); const tableCells = Children.map(children, (child, index) => { return section === TableSection.BODY && isTableCell(child) @@ -39,5 +41,3 @@ const TableRow: FC> = ({ children, className, ... }; TableRow.displayName = 'Table.Row'; - -export default TableRow; diff --git a/src/components/content-presentation/table/components/__tests__/Table.test.tsx b/src/components/content-presentation/table/components/__tests__/Table.test.tsx index 68c2bc766..3fe50890e 100644 --- a/src/components/content-presentation/table/components/__tests__/Table.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/Table.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; -import { renderClient, renderServer } from '@util/components'; -import Table from '../..'; +import { createRef } from 'react'; +import { Table } from '../..'; +import { renderClient, renderServer } from '#util/components'; describe('Table', () => { const Example = (props: Parameters[0]) => ( diff --git a/src/components/content-presentation/table/components/__tests__/TableBody.test.tsx b/src/components/content-presentation/table/components/__tests__/TableBody.test.tsx index ef92099b8..90fe20429 100644 --- a/src/components/content-presentation/table/components/__tests__/TableBody.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TableBody.test.tsx @@ -1,8 +1,6 @@ -import React, { useContext } from 'react'; import { render } from '@testing-library/react'; -import Table from '../../Table'; -import TableSectionContext, { TableSection } from '../../TableSectionContext'; -import TableBody from '../TableBody'; +import { TableBody } from '..'; +import { Table, TableSection, TableSectionContext } from '../..'; describe('Table.Body', () => { it('matches snapshot', () => { @@ -16,26 +14,20 @@ describe('Table.Body', () => { }); it('exposes TableSectionContext', () => { - let tableSection: TableSection = TableSection.NONE; - - const TestComponent = () => { - const tableContext = useContext(TableSectionContext); - - if (tableSection !== tableContext) { - tableSection = tableContext; - } - - return null; - }; + const mock = jest.fn(); render( - + + {(section) => { + return mock(section); + }} +
    , ); - expect(tableSection).toBe(TableSection.BODY); + expect(mock).toHaveBeenCalledWith(TableSection.BODY); }); }); diff --git a/src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx b/src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx index 2fc87820d..99d64b406 100644 --- a/src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TableCaption.test.tsx @@ -1,8 +1,7 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import { NHSUKSize } from '@util/types/NHSUKTypes'; -import Table from '../..'; -import TableCaption from '../TableCaption'; +import { TableCaption } from '..'; +import { Table } from '../..'; +import { type NHSUKSize } from '#util/types/NHSUKTypes'; describe('TableCaption', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/table/components/__tests__/TableCell.test.tsx b/src/components/content-presentation/table/components/__tests__/TableCell.test.tsx index 7df71ca3a..284950430 100644 --- a/src/components/content-presentation/table/components/__tests__/TableCell.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TableCell.test.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import Table from '../../Table'; +import { Table } from '../..'; describe('Table.Cell', () => { it('matches snapshot', () => { @@ -30,9 +29,7 @@ describe('Table.Cell', () => { , ); - // eslint-disable-next-line no-console expect(console.warn).toHaveBeenCalledTimes(1); - // eslint-disable-next-line no-console expect(console.warn).toHaveBeenLastCalledWith( 'Table.Cell used outside of a Table.Head or Table.Body component. Unable to determine section type from context.', ); diff --git a/src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx b/src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx index 26536195c..88e80167e 100644 --- a/src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TableContainer.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import TableContainer from '../TableContainer'; +import { createRef } from 'react'; +import { TableContainer } from '..'; describe('TableContainer', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/table/components/__tests__/TableHead.test.tsx b/src/components/content-presentation/table/components/__tests__/TableHead.test.tsx index 430692e58..5897dfc8d 100644 --- a/src/components/content-presentation/table/components/__tests__/TableHead.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TableHead.test.tsx @@ -1,8 +1,6 @@ -import React, { useContext } from 'react'; import { render } from '@testing-library/react'; -import Table from '../../Table'; -import TableSectionContext, { TableSection } from '../../TableSectionContext'; -import TableHead from '../TableHead'; +import { TableHead } from '..'; +import { Table, TableSection, TableSectionContext } from '../..'; describe('Table.Head', () => { it('matches snapshot', () => { @@ -16,26 +14,20 @@ describe('Table.Head', () => { }); it('exposes TableSectionContext', () => { - let tableSection: TableSection = TableSection.NONE; - - const TestComponent = () => { - const tableContext = useContext(TableSectionContext); - - if (tableSection !== tableContext) { - tableSection = tableContext; - } - - return null; - }; + const mock = jest.fn(); render( - + + {(section) => { + return mock(section); + }} +
    , ); - expect(tableSection).toBe(TableSection.HEAD); + expect(mock).toHaveBeenCalledWith(TableSection.HEAD); }); }); diff --git a/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx b/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx index b31c65d84..b8dbd79f8 100644 --- a/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TablePanel.test.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import TablePanel from '../TablePanel'; +import { TablePanel } from '..'; describe('Table.Panel', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/table/components/__tests__/TableRow.test.tsx b/src/components/content-presentation/table/components/__tests__/TableRow.test.tsx index f40695c99..466501fd2 100644 --- a/src/components/content-presentation/table/components/__tests__/TableRow.test.tsx +++ b/src/components/content-presentation/table/components/__tests__/TableRow.test.tsx @@ -1,9 +1,6 @@ import { render } from '@testing-library/react'; -import React from 'react'; -import TableContext, { ITableContext } from '../../TableContext'; -import TableSectionContext, { TableSection } from '../../TableSectionContext'; -import TableCell from '../TableCell'; -import TableRow from '../TableRow'; +import { TableCell, TableRow } from '..'; +import { TableContext, TableSection, TableSectionContext, type ITableContext } from '../..'; const assertCellText = (container: HTMLElement, cellNumber: number, text: string) => { expect(container.querySelector(`[data-test="cell-${cellNumber}"]`)).toHaveTextContent(text); diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/Table.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/Table.test.tsx.snap index bc88b88aa..9d3761f42 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/Table.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/Table.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Table matches snapshot (via server): client 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap index 4ecef7082..e624e64f4 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableBody.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Table.Body matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap index d175dfb3f..08d8ede7d 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCaption.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`TableCaption matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap index d636c4f2b..6282b0c36 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableCell.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Table.Cell matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap index 194e45d39..e2ee6f957 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableContainer.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`TableContainer matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap index 90e2ca0b9..a50cbd3cb 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableHead.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Table.Head matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap index 601777559..717a293cb 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TablePanel.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Table.Panel matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap index 7c15e8b43..562a13c8e 100644 --- a/src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap +++ b/src/components/content-presentation/table/components/__tests__/__snapshots__/TableRow.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Table.Row matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/table/components/index.ts b/src/components/content-presentation/table/components/index.ts new file mode 100644 index 000000000..bd87735e4 --- /dev/null +++ b/src/components/content-presentation/table/components/index.ts @@ -0,0 +1,7 @@ +export * from './TableBody.js'; +export * from './TableCaption.js'; +export * from './TableCell.js'; +export * from './TableContainer.js'; +export * from './TableHead.js'; +export * from './TablePanel.js'; +export * from './TableRow.js'; diff --git a/src/components/content-presentation/table/index.ts b/src/components/content-presentation/table/index.ts index ae769447a..f90a825e5 100644 --- a/src/components/content-presentation/table/index.ts +++ b/src/components/content-presentation/table/index.ts @@ -1 +1,3 @@ -export { default } from './Table'; +export * from './TableContext.js'; +export * from './TableSectionContext.js'; +export * from './Table.js'; diff --git a/src/components/content-presentation/tabs/Tabs.tsx b/src/components/content-presentation/tabs/Tabs.tsx index b5fcf045a..9fbcaf56a 100644 --- a/src/components/content-presentation/tabs/Tabs.tsx +++ b/src/components/content-presentation/tabs/Tabs.tsx @@ -1,14 +1,16 @@ +'use client'; + import classNames from 'classnames'; -import React, { - ComponentPropsWithoutRef, - FC, +import { type Tabs as TabsModule } from 'nhsuk-frontend'; +import { createRef, forwardRef, useEffect, useState, + type ComponentPropsWithoutRef, + type FC, } from 'react'; -import HeadingLevel, { HeadingLevelProps } from '@components/utils/HeadingLevel'; -import { type Tabs } from 'nhsuk-frontend'; +import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js'; export type TabsProps = ComponentPropsWithoutRef<'div'>; @@ -54,7 +56,7 @@ const TabsComponent = forwardRef((props, forwardedRef const { children, className, ...rest } = props; const [moduleRef] = useState(() => forwardedRef || createRef()); - const [instance, setInstance] = useState(); + const [instance, setInstance] = useState(); useEffect(() => { if (!('current' in moduleRef) || !moduleRef.current || instance) { @@ -86,7 +88,7 @@ TabList.displayName = 'Tabs.List'; TabListItem.displayName = 'Tabs.ListItem'; TabContents.displayName = 'Tabs.Contents'; -export default Object.assign(TabsComponent, { +export const Tabs = Object.assign(TabsComponent, { Title: TabTitle, List: TabList, ListItem: TabListItem, diff --git a/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx b/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx index c9508666e..552b6937a 100644 --- a/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx +++ b/src/components/content-presentation/tabs/__tests__/Tabs.test.tsx @@ -1,7 +1,7 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import { renderClient, renderServer } from '@util/components'; -import Tabs, { TabTitleProps } from '../Tabs'; +import { createRef } from 'react'; +import { Tabs, type TabTitleProps } from '..'; +import { renderClient, renderServer } from '#util/components'; describe('Tabs', () => { it('matches snapshot', async () => { diff --git a/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap b/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap index 6455e409a..ab80edc2d 100644 --- a/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +++ b/src/components/content-presentation/tabs/__tests__/__snapshots__/Tabs.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Tabs matches snapshot (via server): client 1`] = `
    diff --git a/src/components/content-presentation/tabs/index.ts b/src/components/content-presentation/tabs/index.ts index bc6749b1b..f91d05e5a 100644 --- a/src/components/content-presentation/tabs/index.ts +++ b/src/components/content-presentation/tabs/index.ts @@ -1 +1 @@ -export { default } from './Tabs'; +export * from './Tabs.js'; diff --git a/src/components/content-presentation/tag/Tag.tsx b/src/components/content-presentation/tag/Tag.tsx index 5bdab6050..60d83a6c3 100644 --- a/src/components/content-presentation/tag/Tag.tsx +++ b/src/components/content-presentation/tag/Tag.tsx @@ -1,5 +1,5 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; export interface TagProps extends ComponentPropsWithoutRef<'strong'> { modifier?: @@ -20,13 +20,11 @@ export interface TagProps extends ComponentPropsWithoutRef<'strong'> { color?: TagProps['modifier']; } -const TagComponent: FC = ({ className, color, modifier = color, ...rest }) => ( +export const Tag: FC = ({ className, color, modifier = color, ...rest }) => ( ); -TagComponent.displayName = 'Tag'; - -export default TagComponent; +Tag.displayName = 'Tag'; diff --git a/src/components/content-presentation/tag/__tests__/Tag.test.tsx b/src/components/content-presentation/tag/__tests__/Tag.test.tsx index 419e2a96d..2cf77408a 100644 --- a/src/components/content-presentation/tag/__tests__/Tag.test.tsx +++ b/src/components/content-presentation/tag/__tests__/Tag.test.tsx @@ -1,6 +1,6 @@ -import React, { ComponentPropsWithoutRef } from 'react'; import { render } from '@testing-library/react'; -import Tag from '../Tag'; +import { type ComponentPropsWithoutRef } from 'react'; +import { Tag } from '..'; describe('Tag', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap b/src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap index d0ec444ee..7d78fc40d 100644 --- a/src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap +++ b/src/components/content-presentation/tag/__tests__/__snapshots__/Tag.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Tag matches snapshot 1`] = `
    diff --git a/src/components/content-presentation/tag/index.ts b/src/components/content-presentation/tag/index.ts index 15774bff2..17abdf908 100644 --- a/src/components/content-presentation/tag/index.ts +++ b/src/components/content-presentation/tag/index.ts @@ -1 +1 @@ -export { default } from './Tag'; +export * from './Tag.js'; diff --git a/src/components/content-presentation/warning-callout/WarningCallout.tsx b/src/components/content-presentation/warning-callout/WarningCallout.tsx index 47d366082..80b238bf6 100644 --- a/src/components/content-presentation/warning-callout/WarningCallout.tsx +++ b/src/components/content-presentation/warning-callout/WarningCallout.tsx @@ -1,6 +1,6 @@ -import React, { ComponentPropsWithoutRef, FC, forwardRef } from 'react'; import classNames from 'classnames'; -import HeadingLevel, { HeadingLevelProps } from '@components/utils/HeadingLevel'; +import { forwardRef, type ComponentPropsWithoutRef, type FC } from 'react'; +import { HeadingLevel, type HeadingLevelProps } from '#components/utils/HeadingLevel.js'; const WarningCalloutHeading: FC = ({ children, className, ...rest }) => ( @@ -21,7 +21,7 @@ const WarningCalloutHeading: FC = ({ children, className, ... ); -type WarningCalloutProps = ComponentPropsWithoutRef<'div'>; +export type WarningCalloutProps = ComponentPropsWithoutRef<'div'>; const WarningCalloutComponent = forwardRef( ({ className, ...rest }, forwardedRef) => ( @@ -32,6 +32,6 @@ const WarningCalloutComponent = forwardRef( WarningCalloutComponent.displayName = 'WarningCallout'; WarningCalloutHeading.displayName = 'WarningCallout.Heading'; -export default Object.assign(WarningCalloutComponent, { +export const WarningCallout = Object.assign(WarningCalloutComponent, { Heading: WarningCalloutHeading, }); diff --git a/src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx b/src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx index 1c2b8109e..1edee5f19 100644 --- a/src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx +++ b/src/components/content-presentation/warning-callout/__tests__/WarningCallout.test.tsx @@ -1,6 +1,6 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import WarningCallout from '../WarningCallout'; +import { createRef } from 'react'; +import { WarningCallout } from '..'; describe('WarningCallout', () => { it('matches snapshot', () => { diff --git a/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap b/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap index d9aad1346..768b5efb4 100644 --- a/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap +++ b/src/components/content-presentation/warning-callout/__tests__/__snapshots__/WarningCallout.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`WarningCallout adds visually hidden text when necessary: WarningCalloutWithTextRole 1`] = `
    diff --git a/src/components/content-presentation/warning-callout/index.ts b/src/components/content-presentation/warning-callout/index.ts index 8886011c1..c136d0374 100644 --- a/src/components/content-presentation/warning-callout/index.ts +++ b/src/components/content-presentation/warning-callout/index.ts @@ -1 +1 @@ -export { default } from './WarningCallout'; +export * from './WarningCallout.js'; diff --git a/src/components/form-elements/button/Button.tsx b/src/components/form-elements/button/Button.tsx index 6f3ce2a0b..56d07bbec 100644 --- a/src/components/form-elements/button/Button.tsx +++ b/src/components/form-elements/button/Button.tsx @@ -1,7 +1,16 @@ -import React, { ForwardedRef, MouseEvent, useEffect, useState, forwardRef, createRef } from 'react'; -import { AsElementLink } from '@util/types/LinkTypes'; -import { type Button } from 'nhsuk-frontend'; +'use client'; + import classNames from 'classnames'; +import { type Button as ButtonModule } from 'nhsuk-frontend'; +import { + createRef, + forwardRef, + useEffect, + useState, + type ForwardedRef, + type MouseEvent, +} from 'react'; +import { type AsElementLink } from '#util/types/LinkTypes.js'; export interface ButtonProps extends AsElementLink { href?: never; @@ -37,7 +46,7 @@ const ButtonComponent = forwardRef((props, forwa } = props; const [moduleRef] = useState(() => forwardedRef || createRef()); - const [instance, setInstance] = useState
    @@ -241,7 +238,6 @@ exports[`DateInput matches snapshot 1`] = ` inputmode="numeric" name="date-input-day" type="text" - value="" />
    @@ -264,7 +260,6 @@ exports[`DateInput matches snapshot 1`] = ` inputmode="numeric" name="date-input-month" type="text" - value="" />
    @@ -287,7 +282,6 @@ exports[`DateInput matches snapshot 1`] = ` inputmode="numeric" name="date-input-year" type="text" - value="" />
    @@ -453,7 +447,6 @@ exports[`DateInput matches snapshot with custom date fields and error message 1` inputmode="numeric" name="date-input-day" type="text" - value="" />
    @@ -565,7 +558,6 @@ exports[`DateInput matches snapshot with error message 1`] = ` inputmode="numeric" name="date-input-day" type="text" - value="" />
    @@ -588,7 +580,6 @@ exports[`DateInput matches snapshot with error message 1`] = ` inputmode="numeric" name="date-input-month" type="text" - value="" />
    @@ -611,7 +602,6 @@ exports[`DateInput matches snapshot with error message 1`] = ` inputmode="numeric" name="date-input-year" type="text" - value="" />
    diff --git a/src/components/form-elements/date-input/components/IndividualDateInputs.tsx b/src/components/form-elements/date-input/components/IndividualDateInputs.tsx index a9be9d748..176436d67 100644 --- a/src/components/form-elements/date-input/components/IndividualDateInputs.tsx +++ b/src/components/form-elements/date-input/components/IndividualDateInputs.tsx @@ -1,8 +1,10 @@ -import React, { ComponentPropsWithoutRef, useContext, ChangeEvent, forwardRef } from 'react'; +'use client'; + import classNames from 'classnames'; -import { FormElementProps } from '@util/types/FormTypes'; -import Label from '../../label/Label'; -import DateInputContext, { IDateInputContext } from '../DateInputContext'; +import { forwardRef, useContext, type ChangeEvent, type ComponentPropsWithoutRef } from 'react'; +import { DateInputContext, type IDateInputContext } from '../DateInputContext.js'; +import { Label } from '#components/form-elements/label/index.js'; +import { type FormElementProps } from '#util/types/FormTypes.js'; export interface IndividualDateInputProps extends ComponentPropsWithoutRef<'input'>, diff --git a/src/components/form-elements/date-input/components/index.ts b/src/components/form-elements/date-input/components/index.ts new file mode 100644 index 000000000..18eb91bb6 --- /dev/null +++ b/src/components/form-elements/date-input/components/index.ts @@ -0,0 +1 @@ +export * from './IndividualDateInputs.js'; diff --git a/src/components/form-elements/date-input/index.ts b/src/components/form-elements/date-input/index.ts index a8bd7bc4c..8d099bb3d 100644 --- a/src/components/form-elements/date-input/index.ts +++ b/src/components/form-elements/date-input/index.ts @@ -1 +1,2 @@ -export { default } from './DateInput'; +export * from './DateInputContext.js'; +export * from './DateInput.js'; diff --git a/src/components/form-elements/error-message/ErrorMessage.tsx b/src/components/form-elements/error-message/ErrorMessage.tsx index 8d24b1167..1ea1cb20b 100644 --- a/src/components/form-elements/error-message/ErrorMessage.tsx +++ b/src/components/form-elements/error-message/ErrorMessage.tsx @@ -1,11 +1,11 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; export interface ErrorMessageProps extends ComponentPropsWithoutRef<'span'> { visuallyHiddenText?: string; } -const ErrorMessageComponent: FC = ({ +export const ErrorMessage: FC = ({ className, visuallyHiddenText = 'Error', children, @@ -28,6 +28,4 @@ const ErrorMessageComponent: FC = ({ ); }; -ErrorMessageComponent.displayName = 'ErrorMessage'; - -export default ErrorMessageComponent; +ErrorMessage.displayName = 'ErrorMessage'; diff --git a/src/components/form-elements/error-message/__tests__/ErrorMessage.test.tsx b/src/components/form-elements/error-message/__tests__/ErrorMessage.test.tsx index e4a33884b..c4fa106c6 100644 --- a/src/components/form-elements/error-message/__tests__/ErrorMessage.test.tsx +++ b/src/components/form-elements/error-message/__tests__/ErrorMessage.test.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import ErrorMessage from '../'; +import { ErrorMessage } from '..'; describe('ErrorMessage', () => { it('matches snapshot', () => { diff --git a/src/components/form-elements/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap b/src/components/form-elements/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap index 35874a3ff..c6a3f5483 100644 --- a/src/components/form-elements/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap +++ b/src/components/form-elements/error-message/__tests__/__snapshots__/ErrorMessage.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`ErrorMessage matches snapshot: ErrorMessage 1`] = `
    diff --git a/src/components/form-elements/error-message/index.ts b/src/components/form-elements/error-message/index.ts index 43c8c9cc3..35ed2aa34 100644 --- a/src/components/form-elements/error-message/index.ts +++ b/src/components/form-elements/error-message/index.ts @@ -1 +1 @@ -export { default } from './ErrorMessage'; +export * from './ErrorMessage.js'; diff --git a/src/components/form-elements/error-summary/ErrorSummary.tsx b/src/components/form-elements/error-summary/ErrorSummary.tsx index e4761b5b2..0ba53b5d5 100644 --- a/src/components/form-elements/error-summary/ErrorSummary.tsx +++ b/src/components/form-elements/error-summary/ErrorSummary.tsx @@ -1,16 +1,18 @@ -import React, { +'use client'; + +import classNames from 'classnames'; +import { type ErrorSummary as ErrorSummaryModule } from 'nhsuk-frontend'; +import { Children, - ComponentPropsWithoutRef, - FC, createRef, forwardRef, - useState, useEffect, + useState, + type ComponentPropsWithoutRef, + type FC, } from 'react'; -import classNames from 'classnames'; -import { AsElementLink } from '@util/types/LinkTypes'; -import { childIsOfComponentType } from '@util/types/TypeGuards'; -import { type ErrorSummary } from 'nhsuk-frontend'; +import { childIsOfComponentType } from '#util/types/TypeGuards.js'; +import { type AsElementLink } from '#util/types/LinkTypes.js'; export type TitleProps = ComponentPropsWithoutRef<'h2'>; @@ -65,7 +67,7 @@ export interface ErrorSummaryProps extends ComponentPropsWithoutRef<'div'> { const ErrorSummaryComponent = forwardRef( ({ children, className, disableAutoFocus, ...rest }, forwardedRef) => { const [moduleRef] = useState(() => forwardedRef || createRef()); - const [instance, setInstance] = useState(); + const [instance, setInstance] = useState(); useEffect(() => { if (!('current' in moduleRef) || !moduleRef.current || instance) { @@ -107,7 +109,7 @@ Title.displayName = 'ErrorSummary.Title'; List.displayName = 'ErrorSummary.List'; ListItem.displayName = 'ErrorSummary.ListItem'; -export default Object.assign(ErrorSummaryComponent, { +export const ErrorSummary = Object.assign(ErrorSummaryComponent, { Title, List, ListItem, diff --git a/src/components/form-elements/error-summary/__tests__/ErrorSummary.test.tsx b/src/components/form-elements/error-summary/__tests__/ErrorSummary.test.tsx index 504774a5a..9525c2be5 100644 --- a/src/components/form-elements/error-summary/__tests__/ErrorSummary.test.tsx +++ b/src/components/form-elements/error-summary/__tests__/ErrorSummary.test.tsx @@ -1,7 +1,7 @@ -import React, { createRef } from 'react'; import { render } from '@testing-library/react'; -import { renderClient, renderServer } from '@util/components'; -import ErrorSummary from '../'; +import { createRef } from 'react'; +import { ErrorSummary } from '..'; +import { renderClient, renderServer } from '#util/components'; describe('ErrorSummary', () => { it('matches snapshot', async () => { diff --git a/src/components/form-elements/error-summary/__tests__/__snapshots__/ErrorSummary.test.tsx.snap b/src/components/form-elements/error-summary/__tests__/__snapshots__/ErrorSummary.test.tsx.snap index 80b2b61f1..666eb3265 100644 --- a/src/components/form-elements/error-summary/__tests__/__snapshots__/ErrorSummary.test.tsx.snap +++ b/src/components/form-elements/error-summary/__tests__/__snapshots__/ErrorSummary.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`ErrorSummary ErrorSummary.List matches snapshot: ErrorSummary.List 1`] = `
    diff --git a/src/components/form-elements/error-summary/index.ts b/src/components/form-elements/error-summary/index.ts index 20fb46a19..49eaf1ac3 100644 --- a/src/components/form-elements/error-summary/index.ts +++ b/src/components/form-elements/error-summary/index.ts @@ -1 +1 @@ -export { default } from './ErrorSummary'; +export * from './ErrorSummary.js'; diff --git a/src/components/form-elements/fieldset/Fieldset.tsx b/src/components/form-elements/fieldset/Fieldset.tsx index 3d3e0d3a4..ee23b7e1d 100644 --- a/src/components/form-elements/fieldset/Fieldset.tsx +++ b/src/components/form-elements/fieldset/Fieldset.tsx @@ -1,6 +1,6 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; -import Legend from '../legend/Legend'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; +import { Legend } from '#components/form-elements/legend/index.js'; export type FieldsetProps = ComponentPropsWithoutRef<'fieldset'>; @@ -18,6 +18,6 @@ const FieldsetComponent: FC = ({ children, className, ...rest }) FieldsetComponent.displayName = 'Fieldset'; -export default Object.assign(FieldsetComponent, { +export const Fieldset = Object.assign(FieldsetComponent, { Legend, }); diff --git a/src/components/form-elements/fieldset/__tests__/Fieldset.test.tsx b/src/components/form-elements/fieldset/__tests__/Fieldset.test.tsx index 220a91f13..e12ef428a 100644 --- a/src/components/form-elements/fieldset/__tests__/Fieldset.test.tsx +++ b/src/components/form-elements/fieldset/__tests__/Fieldset.test.tsx @@ -1,7 +1,6 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import Fieldset from '../'; -import TextInput from '@components/form-elements/text-input'; +import { Fieldset } from '..'; +import { TextInput } from '../..'; describe('Fieldset', () => { it('matches snapshot', () => { diff --git a/src/components/form-elements/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap b/src/components/form-elements/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap index 97e8f3fed..7b79d8e34 100644 --- a/src/components/form-elements/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap +++ b/src/components/form-elements/fieldset/__tests__/__snapshots__/Fieldset.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Fieldset matches snapshot: Fieldset 1`] = `
    diff --git a/src/components/form-elements/fieldset/index.ts b/src/components/form-elements/fieldset/index.ts index e5d920071..cc9bf42f1 100644 --- a/src/components/form-elements/fieldset/index.ts +++ b/src/components/form-elements/fieldset/index.ts @@ -1 +1 @@ -export { default } from './Fieldset'; +export * from './Fieldset.js'; diff --git a/src/components/form-elements/form/Form.tsx b/src/components/form-elements/form/Form.tsx index 0bf2b67ea..b438f77db 100644 --- a/src/components/form-elements/form/Form.tsx +++ b/src/components/form-elements/form/Form.tsx @@ -1,16 +1,14 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; -import FormContext from './FormContext'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; +import { FormContext } from './FormContext.js'; -type FormProps = ComponentPropsWithoutRef<'form'> & { +export type FormProps = ComponentPropsWithoutRef<'form'> & { disableErrorFromComponents?: boolean; }; -const FormComponent: FC = ({ disableErrorFromComponents, ...rest }) => ( +export const Form: FC = ({ disableErrorFromComponents, ...rest }) => (
    ); -FormComponent.displayName = 'Form'; - -export default FormComponent; +Form.displayName = 'Form'; diff --git a/src/components/form-elements/form/FormContext.ts b/src/components/form-elements/form/FormContext.ts index 68b12f70a..6fce0b3da 100644 --- a/src/components/form-elements/form/FormContext.ts +++ b/src/components/form-elements/form/FormContext.ts @@ -1,13 +1,13 @@ +'use client'; + import { createContext, useContext } from 'react'; export interface IFormContext { disableErrorFromComponents: boolean; } -const FormContext = createContext({ +export const FormContext = createContext({ disableErrorFromComponents: false, }); export const useFormContext = (): IFormContext => useContext(FormContext); - -export default FormContext; diff --git a/src/components/form-elements/form/index.ts b/src/components/form-elements/form/index.ts index 84bc0acac..061c9d54f 100644 --- a/src/components/form-elements/form/index.ts +++ b/src/components/form-elements/form/index.ts @@ -1 +1,2 @@ -export { default, useFormContext } from './FormContext'; +export * from './FormContext.js'; +export * from './Form.js'; diff --git a/src/components/form-elements/hint-text/HintText.tsx b/src/components/form-elements/hint-text/HintText.tsx index 34f1c5f2d..918041b17 100644 --- a/src/components/form-elements/hint-text/HintText.tsx +++ b/src/components/form-elements/hint-text/HintText.tsx @@ -1,9 +1,9 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; export type HintTextProps = ComponentPropsWithoutRef<'div'>; -const HintTextComponent: FC = ({ children, className, ...rest }) => { +export const HintText: FC = ({ children, className, ...rest }) => { if (!children) { return null; } @@ -15,6 +15,4 @@ const HintTextComponent: FC = ({ children, className, ...rest }) ); }; -HintTextComponent.displayName = 'HintText'; - -export default HintTextComponent; +HintText.displayName = 'HintText'; diff --git a/src/components/form-elements/hint-text/__tests__/Hint.test.tsx b/src/components/form-elements/hint-text/__tests__/Hint.test.tsx index f5787c9c3..e58b3a62d 100644 --- a/src/components/form-elements/hint-text/__tests__/Hint.test.tsx +++ b/src/components/form-elements/hint-text/__tests__/Hint.test.tsx @@ -1,6 +1,5 @@ -import React from 'react'; import { render } from '@testing-library/react'; -import HintText from '../'; +import { HintText } from '..'; describe('Hint', () => { it('matches snapshot', () => { diff --git a/src/components/form-elements/hint-text/__tests__/__snapshots__/Hint.test.tsx.snap b/src/components/form-elements/hint-text/__tests__/__snapshots__/Hint.test.tsx.snap index 17b560193..180450427 100644 --- a/src/components/form-elements/hint-text/__tests__/__snapshots__/Hint.test.tsx.snap +++ b/src/components/form-elements/hint-text/__tests__/__snapshots__/Hint.test.tsx.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing exports[`Hint matches snapshot: Hint 1`] = `
    diff --git a/src/components/form-elements/hint-text/index.ts b/src/components/form-elements/hint-text/index.ts index 708441dbf..9388f600f 100644 --- a/src/components/form-elements/hint-text/index.ts +++ b/src/components/form-elements/hint-text/index.ts @@ -1 +1 @@ -export { default } from './HintText'; +export * from './HintText.js'; diff --git a/src/components/form-elements/index.ts b/src/components/form-elements/index.ts new file mode 100644 index 000000000..23b427ffa --- /dev/null +++ b/src/components/form-elements/index.ts @@ -0,0 +1,15 @@ +export * from './button/index.js'; +export * from './character-count/index.js'; +export * from './checkboxes/index.js'; +export * from './date-input/index.js'; +export * from './error-message/index.js'; +export * from './error-summary/index.js'; +export * from './fieldset/index.js'; +export * from './form/index.js'; +export * from './hint-text/index.js'; +export * from './label/index.js'; +export * from './legend/index.js'; +export * from './radios/index.js'; +export * from './select/index.js'; +export * from './text-input/index.js'; +export * from './textarea/index.js'; diff --git a/src/components/form-elements/label/Label.tsx b/src/components/form-elements/label/Label.tsx index 734503c24..13b0c2428 100644 --- a/src/components/form-elements/label/Label.tsx +++ b/src/components/form-elements/label/Label.tsx @@ -1,13 +1,13 @@ -import React, { ComponentPropsWithoutRef, FC } from 'react'; import classNames from 'classnames'; -import { NHSUKSize } from '@util/types/NHSUKTypes'; +import { type ComponentPropsWithoutRef, type FC } from 'react'; +import { type NHSUKSize } from '#util/types/NHSUKTypes.js'; export interface LabelProps extends ComponentPropsWithoutRef<'label'> { isPageHeading?: boolean; size?: NHSUKSize; } -const Label: FC> = ({ className, size, ...rest }) => ( +const LabelComponent: FC> = ({ className, size, ...rest }) => ( // eslint-disable-next-line jsx-a11y/label-has-associated-control