I am the body
+ + + + + + + +I am the second body
+ + + + + + + +I am the third body
+ + + + + + + + + +test
+ + + +I am p tag
+ + + + + +Testing
+ + + +TEst
+ + + +est
+ + + +test
+ + + +test
+ + + + + + + format: gutenberg + summary: '' diff --git a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml index 3bb3bfca3..dfac09fa5 100644 --- a/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml +++ b/packages/drupal/test_content/content/node/a397ca48-8fad-411e-8901-0eba2feb989c.yml @@ -102,6 +102,10 @@ default:+ + @@ -202,13 +206,13 @@ translations:Quote
Citation
+ - - - - - + + + format: gutenberg summary: '' diff --git a/packages/schema/src/fragments/Page.gql b/packages/schema/src/fragments/Page.gql index 59285ccc4..28f7127f2 100644 --- a/packages/schema/src/fragments/Page.gql +++ b/packages/schema/src/fragments/Page.gql @@ -37,6 +37,7 @@ fragment Page on Page { ...BlockQuote ...BlockHorizontalSeparator ...BlockAccordion + ...BlockInfoGrid } metaTags { tag diff --git a/packages/schema/src/fragments/PageContent/BlockInfoGrid.gql b/packages/schema/src/fragments/PageContent/BlockInfoGrid.gql new file mode 100644 index 000000000..56999b5d4 --- /dev/null +++ b/packages/schema/src/fragments/PageContent/BlockInfoGrid.gql @@ -0,0 +1,14 @@ +fragment BlockInfoGrid on BlockInfoGrid { + gridItems { + ...BlockInfoGridItem + } +} + +fragment BlockInfoGridItem on BlockInfoGridItem { + icon + infoGridContent { + __typename + ...BlockCta + ...BlockMarkup + } +} diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index fbeafa4e3..4c0150a42 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -208,6 +208,7 @@ union PageContent @resolveEditorBlockType = | BlockQuote | BlockHorizontalSeparator | BlockAccordion + | BlockInfoGrid type BlockForm @type(id: "custom/form") { url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$") @@ -295,6 +296,24 @@ type BlockQuote @type(id: "custom/quote") { image: MediaImage @resolveEditorBlockMedia } +enum InfoGridIcon @default @value(string: "NONE") { + EMAIL + PHONE + LIFE_RING + NONE +} + +union InfoGridContent @resolveEditorBlockType = BlockMarkup | BlockCta + +type BlockInfoGrid @type(id: "custom/info-grid") { + gridItems: [BlockInfoGridItem]! @resolveEditorBlockChildren +} + +type BlockInfoGridItem { + icon: InfoGridIcon! @resolveEditorBlockAttribute(key: "icon") + infoGridContent: [InfoGridContent] @resolveEditorBlockChildren +} + type BlockHorizontalSeparator @type(id: "custom/horizontal-separator") { markup: Markup! @resolveEditorBlockMarkup } diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index ca58622e6..cf0559aa8 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -1,5 +1,4 @@ import type { StorybookConfig } from '@storybook/react-vite'; -import pluginTurbosnap from 'vite-plugin-turbosnap'; import { mergeConfig, UserConfig } from 'vite'; import { imagetools } from 'vite-imagetools'; import { resolve, dirname } from 'path'; @@ -19,18 +18,16 @@ const config: StorybookConfig = { ), }, }, - plugins: [ - pluginTurbosnap({ rootDir: config.root ?? process.cwd() }), - imagetools(), - ], + plugins: [imagetools()], } satisfies UserConfig), staticDirs: ['../static/public', '../static/stories'], - stories: ['../src/**/*.stories.@(ts|tsx|mdx)'], + stories: ['../src/**/*.@(mdx|stories.@(ts|tsx))'], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions', '@storybook/addon-coverage', + '@storybook/addon-a11y', ], framework: { name: '@storybook/react-vite', diff --git a/packages/ui/.storybook/preview.tsx b/packages/ui/.storybook/preview.tsx index 28c5c4eb6..26d96ce3e 100644 --- a/packages/ui/.storybook/preview.tsx +++ b/packages/ui/.storybook/preview.tsx @@ -62,6 +62,51 @@ const SWRCacheDecorator: Decorator = (Story) => { export const parameters = { chromatic: { viewports: [320, 840, 1440] }, + a11y: { + // Optional selector to inspect + element: '#storybook-root', + config: { + rules: [ + { + // The autocomplete rule will not run based on the CSS selector provided + id: 'autocomplete-valid', + selector: '*:not([autocomplete="nope"])', + }, + { + // Setting the enabled option to false will disable checks for this particular rule on all stories. + id: 'image-alt', + enabled: false, + }, + { + // Setting the enabled option to false will disable checks for this particular rule on all stories. + id: 'color-contrast', + reviewOnFail: true, + }, + { + id: 'link-name', + reviewOnFail: true, + }, + { + id: 'duplicate-id', + reviewOnFail: true, + }, + { + id: 'landmark-no-duplicate-main', + reviewOnFail: true, + }, + { + id: 'landmark-main-is-top-level', + reviewOnFail: true, + }, + { + id: 'landmark-unique', + reviewOnFail: true, + }, + ], + }, + // Axe's options parameter + options: {}, + }, }; export const decorators = [LocationDecorator, IntlDecorator, SWRCacheDecorator]; diff --git a/packages/ui/.storybook/test-runner.ts b/packages/ui/.storybook/test-runner.ts new file mode 100644 index 000000000..d2b306075 --- /dev/null +++ b/packages/ui/.storybook/test-runner.ts @@ -0,0 +1,32 @@ +import type { TestRunnerConfig } from '@storybook/test-runner'; +import { getStoryContext } from '@storybook/test-runner'; + +import { injectAxe, checkA11y, configureAxe } from 'axe-playwright'; + +/* + * See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api + * to learn more about the test-runner hooks API. + */ +const config: TestRunnerConfig = { + async preVisit(page) { + await injectAxe(page); + }, + async postVisit(page, context) { + // Get the entire context of a story, including parameters, args, argTypes, etc. + const storyContext = await getStoryContext(page, context); + + // Apply story-level a11y rules + await configureAxe(page, { + rules: storyContext.parameters?.a11y?.config?.rules, + }); + + await checkA11y(page, '#storybook-root', { + detailedReport: true, + detailedReportOptions: { + html: true, + }, + }); + }, +}; + +export default config; diff --git a/packages/ui/package.json b/packages/ui/package.json index 99ff25abb..1dc3c6245 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -28,7 +28,7 @@ "prep:iframe": "NODE_ENV=production pnpm postcss src/iframe.css -o build/iframe.css", "prep:gutenberg": "NODE_ENV=production PREFIX=gutenberg pnpm postcss src/tailwind.css -o build/gutenberg.css", "prep:i18n": "formatjs extract 'src/**/*.ts*' --ignore='**/*.d.ts' --ignore='**/*.stories.ts*' --out-file build/translatables.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'\n", - "build": "storybook build", + "build": "storybook build --stats-json", "dev": "storybook dev -p 6006 --no-open", "start": "serve storybook-static -p 6006 > /dev/null 2>&1", "test:static": "tsc --noEmit && eslint \"**/*.{ts,tsx,js,jsx}\" --ignore-path=\"./.eslintignore\"", @@ -63,28 +63,28 @@ "@amazeelabs/bridge-storybook": "^1.2.8", "@amazeelabs/cloudinary-responsive-image": "^1.6.15", "@formatjs/cli": "^6.2.4", - "@storybook/addon-actions": "^7.6.7", - "@storybook/addon-coverage": "^1.0.0", - "@storybook/addon-essentials": "^7.6.7", - "@storybook/addon-interactions": "^7.6.7", - "@storybook/addon-links": "^7.6.7", - "@storybook/blocks": "^7.6.7", - "@storybook/react": "^7.6.7", - "@storybook/react-vite": "^7.6.7", - "@storybook/test": "8.0.0-alpha.14", - "@storybook/test-runner": "^0.16.0", + "@storybook/addon-actions": "^8.1.6", + "@storybook/addon-coverage": "^1.0.4", + "@storybook/addon-essentials": "^8.1.6", + "@storybook/addon-interactions": "^8.1.6", + "@storybook/addon-links": "^8.0.8", + "@storybook/blocks": "^8.1.6", + "@storybook/react": "^8.1.6", + "@storybook/react-vite": "^8.1.6", + "@storybook/test": "8.1.6", + "@storybook/test-runner": "^0.18.2", "@swc/cli": "^0.1.63", "@swc/core": "^1.3.102", "@tailwindcss/aspect-ratio": "^0.4.2", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", - "@testing-library/react": "^14.1.2", "@types/hast": "^2.3.9", "@types/react": "^18.2.46", "@types/react-body-classname": "^1.1.10", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react-swc": "^3.5.0", "autoprefixer": "^10.4.16", + "axe-playwright": "^2.0.1", "cssnano": "^6.0.3", "eslint-plugin-formatjs": "^4.11.3", "eslint-plugin-storybook": "^0.6.15", @@ -100,12 +100,11 @@ "react-dom": "^18.2.0", "serve": "^14.2.1", "start-server-and-test": "^2.0.3", - "storybook": "^7.6.7", + "storybook": "^8.1.6", "tailwindcss": "^3.4.0", "typescript": "^5.3.3", "vite": "^5.0.10", "vite-imagetools": "^6.2.9", - "vite-plugin-turbosnap": "^1.0.3", "vitest": "^1.1.1" } } diff --git a/packages/ui/src/components/Atoms/Container.css b/packages/ui/src/components/Atoms/Container.css index 566f31114..9e228c04d 100644 --- a/packages/ui/src/components/Atoms/Container.css +++ b/packages/ui/src/components/Atoms/Container.css @@ -39,3 +39,17 @@ .container-nested .prose ol:not(ol ol) { @apply mt-0; } + +.consistent-margin { + h1, + h2, + h3, + p, + a { + @apply my-2; + } + p, + a { + @apply text-base; + } +} diff --git a/packages/ui/src/components/Molecules/Colors.stories.mdx b/packages/ui/src/components/Molecules/Colors.mdx similarity index 100% rename from packages/ui/src/components/Molecules/Colors.stories.mdx rename to packages/ui/src/components/Molecules/Colors.mdx diff --git a/packages/ui/src/components/Molecules/FontSize.stories.mdx b/packages/ui/src/components/Molecules/FontSize.mdx similarity index 100% rename from packages/ui/src/components/Molecules/FontSize.stories.mdx rename to packages/ui/src/components/Molecules/FontSize.mdx diff --git a/packages/ui/src/components/Molecules/LanguageSwitcher.tsx b/packages/ui/src/components/Molecules/LanguageSwitcher.tsx index a59fb2f93..c77683543 100644 --- a/packages/ui/src/components/Molecules/LanguageSwitcher.tsx +++ b/packages/ui/src/components/Molecules/LanguageSwitcher.tsx @@ -16,31 +16,25 @@ function getLanguageName(locale: string) { export function LanguageSwitcher() { const translations = useTranslations(); const [location] = useLocation(); - const currentLocale = Object.values(Locale).find((locale) => { - const translationPath = translations[locale]; - return location.pathname.includes(translationPath || ''); - }); - - if (!currentLocale) { - console.error( - 'No matching locale found in current path:', - location.pathname, - ); - return null; - } - - const otherLocales = Object.values(Locale).filter( - (locale) => locale !== currentLocale, - ); return (Quote DE
Citation DE