From 32070bcd12fa531fb19fa2f6743de4684ba7696d Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Wed, 26 Jun 2024 17:30:06 +0400 Subject: [PATCH 1/7] feat(SLB-423): add language redirect --- packages/drupal/custom/custom.services.yml | 6 ++ .../EntityLanguageRedirectSubscriber.php | 72 +++++++++++++++++++ .../ui/src/components/Molecules/Messages.tsx | 14 +++- .../components/Molecules/PageTransition.tsx | 60 +++++++++++++++- 4 files changed, 146 insertions(+), 6 deletions(-) create mode 100644 packages/drupal/custom/src/EventSubscriber/EntityLanguageRedirectSubscriber.php diff --git a/packages/drupal/custom/custom.services.yml b/packages/drupal/custom/custom.services.yml index 5adbb9068..7424d6f1d 100644 --- a/packages/drupal/custom/custom.services.yml +++ b/packages/drupal/custom/custom.services.yml @@ -10,3 +10,9 @@ services: custom.menus: class: Drupal\custom\Menus + + custom.entity_language_redirect_subscriber: + class: Drupal\custom\EventSubscriber\EntityLanguageRedirectSubscriber + arguments: ['@language_manager', '@current_route_match'] + tags: + - { name: event_subscriber } diff --git a/packages/drupal/custom/src/EventSubscriber/EntityLanguageRedirectSubscriber.php b/packages/drupal/custom/src/EventSubscriber/EntityLanguageRedirectSubscriber.php new file mode 100644 index 000000000..63016e2af --- /dev/null +++ b/packages/drupal/custom/src/EventSubscriber/EntityLanguageRedirectSubscriber.php @@ -0,0 +1,72 @@ +languageManager = $language_manager; + $this->routeMatch = $route_match; + } + + public static function getSubscribedEvents() { + // We need this subscriber to run after the router_listener service (which + // has priority 32) so that the parameters are set into the request, but + // before the EntityCanonicalViewSubscriber one (with the priority 28). So, + // we set the priority to 30. + return [ + KernelEvents::REQUEST => [ + ['onKernelRequest', 30], + ] + ]; + } + + public function onKernelRequest(RequestEvent $event): void { + // In case the user tries to access a node in a language entity is not + // translated to, we redirect to the entity in the original language and + // display a warning message. + if ($this->routeMatch->getRouteName() === 'entity.node.canonical') { + $entity = $this->routeMatch->getCurrentRouteMatch()->getParameter('node'); + $requestedLanguageId = $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(); + if ($entity->language()->getId() != $requestedLanguageId) { + $routeOptions = [ + 'language' => $entity->language(), + ]; + $routeParameters = [ + 'node' => $entity->id(), + ]; + $url = Url::fromRoute('entity.node.canonical', $routeParameters, $routeOptions); + // Make sure we keep any query strings. + $queryString = (string) $event->getRequest()->getQueryString(); + if ($queryString !== '') { + $queryString .= '&'; + } + $urlString = $url->toString() . '?' . $queryString . 'content_language_not_available=true&requested_language=' . $requestedLanguageId; + + // Add the necessary cache contexts to the response, as redirect + // responses are cached as well. + $metadata = new CacheableMetadata(); + $metadata->addCacheContexts(['languages:language_interface', 'url.query_args']); + $response = new TrustedRedirectResponse($urlString); + $response->addCacheableDependency($entity); + $response->addCacheableDependency($metadata); + + $event->setResponse($response); + } + } + } +} diff --git a/packages/ui/src/components/Molecules/Messages.tsx b/packages/ui/src/components/Molecules/Messages.tsx index b6563150a..c733a5b42 100644 --- a/packages/ui/src/components/Molecules/Messages.tsx +++ b/packages/ui/src/components/Molecules/Messages.tsx @@ -1,10 +1,18 @@ import { Html, Markup } from '@custom/schema'; -import React from 'react'; +import React, { ReactNode } from 'react'; // TODO: Style, add stories. -export function Messages(props: { messages: Array }) { - return
{buildMessages(props.messages)}
; +export function Messages(props: { + messages: Array; + messageComponents?: Array; +}) { + return ( +
+ {buildMessages(props.messages)} + {props.messageComponents} +
+ ); } export const buildMessages = (messages: Array) => ( diff --git a/packages/ui/src/components/Molecules/PageTransition.tsx b/packages/ui/src/components/Molecules/PageTransition.tsx index 72ca7569b..d3fd34fcf 100644 --- a/packages/ui/src/components/Molecules/PageTransition.tsx +++ b/packages/ui/src/components/Molecules/PageTransition.tsx @@ -1,16 +1,26 @@ import { motion, useReducedMotion } from 'framer-motion'; -import React, { PropsWithChildren, useEffect } from 'react'; +import React, { PropsWithChildren, ReactNode, useEffect } from 'react'; import { Messages, readMessages } from './Messages'; export function PageTransition({ children }: PropsWithChildren) { const [messages, setMessages] = React.useState>([]); + const [messageComponents, setMessageComponents] = React.useState< + Array + >([]); useEffect(() => { + // Standard messages. setMessages(readMessages()); + // Language message. + const languageMessage = getLanguageMessage(window.location.href); + if (languageMessage) { + setMessageComponents([languageMessage]); + } }, []); + return useReducedMotion() ? (
- + {children}
) : ( @@ -26,8 +36,52 @@ export function PageTransition({ children }: PropsWithChildren) { duration: 0.3, }} > - + {children} ); } + +function getLanguageMessage(url: string): ReactNode { + const urlObject = new URL(url); + const contentLanguageNotAvailable = + urlObject.searchParams.get('content_language_not_available') === 'true'; + if (contentLanguageNotAvailable) { + const requestedLanguage = urlObject.searchParams.get('requested_language'); + if (requestedLanguage) { + const translations: { + [language: string]: { message: string; goBack: string }; + } = { + en: { + message: 'This page is not available in the requested language.', + goBack: 'Go back', + }, + de: { + message: + 'Diese Seite ist nicht in der angeforderten Sprache verfügbar.', + goBack: 'Zurück', + }, + }; + const translation = translations[requestedLanguage]; + if (translation) { + return ( + + ); + } else { + console.error( + `Requested language "${requestedLanguage}" not found in messages.`, + ); + } + } + } +} From f640bce0f0ef8dd95ed56f640fcd2823226cdd8d Mon Sep 17 00:00:00 2001 From: Alex Tkachev Date: Thu, 27 Jun 2024 13:11:56 +0400 Subject: [PATCH 2/7] chore(SLB-423): force all translations to be set --- packages/ui/src/components/Molecules/PageTransition.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/components/Molecules/PageTransition.tsx b/packages/ui/src/components/Molecules/PageTransition.tsx index d3fd34fcf..5b52b4bd8 100644 --- a/packages/ui/src/components/Molecules/PageTransition.tsx +++ b/packages/ui/src/components/Molecules/PageTransition.tsx @@ -1,3 +1,4 @@ +import { Locale } from '@custom/schema'; import { motion, useReducedMotion } from 'framer-motion'; import React, { PropsWithChildren, ReactNode, useEffect } from 'react'; @@ -50,7 +51,7 @@ function getLanguageMessage(url: string): ReactNode { const requestedLanguage = urlObject.searchParams.get('requested_language'); if (requestedLanguage) { const translations: { - [language: string]: { message: string; goBack: string }; + [language in Locale]: { message: string; goBack: string }; } = { en: { message: 'This page is not available in the requested language.', @@ -62,7 +63,7 @@ function getLanguageMessage(url: string): ReactNode { goBack: 'Zurück', }, }; - const translation = translations[requestedLanguage]; + const translation = translations[requestedLanguage as Locale]; if (translation) { return (
From ff6836bb0baa69c40e316dc1d9d8c370eb9cf52b Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Thu, 27 Jun 2024 14:42:44 +0000 Subject: [PATCH 3/7] feat(SLB-438): prestyling on messages --- packages/ui/src/components/Atoms/List.css | 7 +- .../components/Molecules/Messages.stories.tsx | 27 +++++ .../ui/src/components/Molecules/Messages.tsx | 112 ++++++++++++++++-- .../Organisms/PageContent/BlockMarkup.tsx | 2 +- 4 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 packages/ui/src/components/Molecules/Messages.stories.tsx diff --git a/packages/ui/src/components/Atoms/List.css b/packages/ui/src/components/Atoms/List.css index f185321e6..9b20bc358 100644 --- a/packages/ui/src/components/Atoms/List.css +++ b/packages/ui/src/components/Atoms/List.css @@ -1,5 +1,5 @@ ul li { - @apply !text-base !font-medium !text-gray-900; + @apply text-base !font-medium !text-gray-900; } ul { @@ -20,6 +20,11 @@ ul li::marker { @apply !text-gray-500; } +/* Remove hardcoded colors on lists within messages so they can be set to their message type inherited color scheme */ +ul li.messages::marker { + @apply !text-inherit; +} + .list-style--arrows li::before { position: absolute; left: -1.25rem; diff --git a/packages/ui/src/components/Molecules/Messages.stories.tsx b/packages/ui/src/components/Molecules/Messages.stories.tsx new file mode 100644 index 000000000..a342e9db5 --- /dev/null +++ b/packages/ui/src/components/Molecules/Messages.stories.tsx @@ -0,0 +1,27 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { Messages as Component } from './Messages'; + +export default { + parameters: { + layout: 'fullscreen', + }, + component: Component, +} satisfies Meta; + +export const Info: StoryObj = { + parameters: { + location: new URL('local:/gatsby-turbo'), + }, + args: { + messages: [ + '

This is an Info message, linked item

  • Fribourgeoise
  • Moitié-Moitié
', + '

This is a Warning message, linked item

', + '

This is a Danger message, linked item

', + '

This is a Success message, linked item

', + ], + messageComponents: [ + 'This page is not available in the requested language.', + ], + }, +}; diff --git a/packages/ui/src/components/Molecules/Messages.tsx b/packages/ui/src/components/Molecules/Messages.tsx index c733a5b42..93fce8e63 100644 --- a/packages/ui/src/components/Molecules/Messages.tsx +++ b/packages/ui/src/components/Molecules/Messages.tsx @@ -1,27 +1,115 @@ import { Html, Markup } from '@custom/schema'; -import React, { ReactNode } from 'react'; +import clsx from 'clsx'; +import React, { + PropsWithChildren, + ReactNode, + useEffect, + useState, +} from 'react'; -// TODO: Style, add stories. +import { unorderedItems } from '../Organisms/PageContent/BlockMarkup'; export function Messages(props: { messages: Array; messageComponents?: Array; }) { + const [displayMessages, setDisplayMessages] = useState([]); + + useEffect(() => { + setDisplayMessages(props.messages); + }, [props.messages]); + return ( -
- {buildMessages(props.messages)} - {props.messageComponents} +
+
+ {buildMessages(displayMessages, setDisplayMessages)} + {props.messageComponents} +
); } -export const buildMessages = (messages: Array) => ( - <> - {messages.map((message, index) => ( - - ))} - -); +export const buildMessages = ( + messages: Array, + setDisplayMessages: React.Dispatch>, +) => { + return messages.map((message, index) => ( +
+ + Info +
+ ) => { + return ( +
  • + {children} +
  • + ); + }, + }} + /> +
    + +
    + )); +}; export const storeMessages = (messages: Array): void => { localStorage.setItem('messages', JSON.stringify(messages)); diff --git a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx index d59b68162..40c2c21b5 100644 --- a/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx +++ b/packages/ui/src/components/Organisms/PageContent/BlockMarkup.tsx @@ -7,7 +7,7 @@ import { Plugin } from 'unified'; import { FadeUp } from '../../Molecules/FadeUp'; -const unorderedItems: Plugin<[], Element> = () => (tree) => { +export const unorderedItems: Plugin<[], Element> = () => (tree) => { selectAll('ul > li', tree).forEach((node) => { node.properties!.unordered = true; }); From e45e767ff5fb126daf2704cf88c573dfdfe85854 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 28 Jun 2024 08:58:37 +0000 Subject: [PATCH 4/7] style(SLB-438): refactor style --- .../ui/src/components/Molecules/Messages.tsx | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/packages/ui/src/components/Molecules/Messages.tsx b/packages/ui/src/components/Molecules/Messages.tsx index 93fce8e63..32b8236fb 100644 --- a/packages/ui/src/components/Molecules/Messages.tsx +++ b/packages/ui/src/components/Molecules/Messages.tsx @@ -19,19 +19,32 @@ export function Messages(props: { setDisplayMessages(props.messages); }, [props.messages]); + const handleRemoveMessage = (index: number) => { + const newMessages = displayMessages.filter((_, i) => i !== index); + setDisplayMessages(newMessages); + storeMessages(newMessages); + }; + return (
    - {buildMessages(displayMessages, setDisplayMessages)} - {props.messageComponents} + {buildMessages(displayMessages, handleRemoveMessage)} + {/* {buildMessages(displayMessages, setDisplayMessages)} */}
    ); } +// export const buildMessages = (messages: Array) => ( +// <> +// {messages.map((message, index) => ( +// +// ))} +// +// ); export const buildMessages = ( messages: Array, - setDisplayMessages: React.Dispatch>, + handleRemoveMessage?: (index: number) => void, ) => { return messages.map((message, index) => (
    - + Close + + + )}
    )); }; From 3749f663802f01b64203699c87b38dc31ea6c7c7 Mon Sep 17 00:00:00 2001 From: Luqmaan Essop Date: Fri, 28 Jun 2024 10:10:50 +0000 Subject: [PATCH 5/7] style(SLB-438): fix tests annd TS errors --- .../components/Molecules/Messages.stories.tsx | 13 +- .../ui/src/components/Molecules/Messages.tsx | 159 +++++++++--------- 2 files changed, 94 insertions(+), 78 deletions(-) diff --git a/packages/ui/src/components/Molecules/Messages.stories.tsx b/packages/ui/src/components/Molecules/Messages.stories.tsx index a342e9db5..58574999c 100644 --- a/packages/ui/src/components/Molecules/Messages.stories.tsx +++ b/packages/ui/src/components/Molecules/Messages.stories.tsx @@ -1,4 +1,5 @@ import { Meta, StoryObj } from '@storybook/react'; +import React from 'react'; import { Messages as Component } from './Messages'; @@ -21,7 +22,17 @@ export const Info: StoryObj = { '

    This is a Success message, linked item

    ', ], messageComponents: [ - 'This page is not available in the requested language.', +
    + {'This page is not available in the requested language.'}{' '} + { + window.history.back(); + }} + > + {'Go back'} + +
    , ], }, }; diff --git a/packages/ui/src/components/Molecules/Messages.tsx b/packages/ui/src/components/Molecules/Messages.tsx index 32b8236fb..eedd7c9fe 100644 --- a/packages/ui/src/components/Molecules/Messages.tsx +++ b/packages/ui/src/components/Molecules/Messages.tsx @@ -14,9 +14,13 @@ export function Messages(props: { messageComponents?: Array; }) { const [displayMessages, setDisplayMessages] = useState([]); + const [messageComponents, setMessageComponents] = React.useState< + Array + >([]); useEffect(() => { setDisplayMessages(props.messages); + props.messageComponents && setMessageComponents(props.messageComponents); }, [props.messages]); const handleRemoveMessage = (index: number) => { @@ -29,98 +33,99 @@ export function Messages(props: {
    {buildMessages(displayMessages, handleRemoveMessage)} - {/* {buildMessages(displayMessages, setDisplayMessages)} */} + {buildMessages(messageComponents)}
    ); } -// export const buildMessages = (messages: Array) => ( -// <> -// {messages.map((message, index) => ( -// -// ))} -// -// ); export const buildMessages = ( - messages: Array, + messages: Array | Array, handleRemoveMessage?: (index: number) => void, ) => { - return messages.map((message, index) => ( -
    - - Info -
    - + {messages.map((message, index) => ( +
    ) => { - return ( -
  • - {children} -
  • - ); - }, - }} - /> -
    - {handleRemoveMessage && ( - - )} -
    - )); + Info +
    + {typeof message === 'string' ? ( + ) => { + return ( +
  • + {children} +
  • + ); + }, + }} + /> + ) : ( + message + )} +
    + {handleRemoveMessage && ( + + )} +
    + ))} + + ); }; export const storeMessages = (messages: Array): void => { From 3788808807a199f1ff4f96f50e4b8e4afd7d8a22 Mon Sep 17 00:00:00 2001 From: Philipp Melab Date: Thu, 27 Jun 2024 12:20:15 +0200 Subject: [PATCH 6/7] refactor: update to rsc-compatible packages And fix all breaking changes. --- apps/decap/package.json | 2 +- apps/decap/src/helpers/frame.tsx | 45 +- apps/decap/src/main.tsx | 8 +- apps/preview/src/App.tsx | 10 +- apps/website/package.json | 4 +- apps/website/src/layouts/index.tsx | 14 +- apps/website/src/pages/404.tsx | 8 +- apps/website/src/templates/home.tsx | 10 +- apps/website/src/templates/page.tsx | 20 +- apps/website/src/utils/locale.ts | 33 + packages/schema/package.json | 4 +- packages/schema/src/index.ts | 50 +- packages/schema/src/schema.graphql | 3 +- packages/ui/.storybook/preview.tsx | 2 +- packages/ui/package.json | 4 +- .../ui/src/components/Client/MobileMenu.tsx | 2 +- .../Molecules/Breadcrumbs.stories.tsx | 8 +- .../Molecules/InquiryForm.stories.tsx | 8 +- .../src/components/Molecules/InquiryForm.tsx | 2 +- .../Molecules/LanguageSwitcher.stories.tsx | 41 +- .../components/Molecules/LanguageSwitcher.tsx | 1 + .../components/Molecules/PageTransition.tsx | 17 +- .../src/components/Molecules/Pagination.tsx | 2 +- .../src/components/Molecules/SearchForm.tsx | 2 +- .../Organisms/ContentHub.stories.tsx | 20 +- .../src/components/Organisms/ContentHub.tsx | 2 +- .../components/Organisms/Footer.stories.tsx | 13 +- .../ui/src/components/Organisms/Footer.tsx | 3 +- .../components/Organisms/Header.stories.tsx | 13 +- .../ui/src/components/Organisms/Header.tsx | 3 +- .../ui/src/components/Organisms/Inquiry.tsx | 1 + .../src/components/Organisms/PageDisplay.tsx | 1 + .../ui/src/components/Routes/ContentHub.tsx | 20 +- .../src/components/Routes/Frame.stories.tsx | 8 +- packages/ui/src/components/Routes/Frame.tsx | 81 +- .../ui/src/components/Routes/HomePage.tsx | 34 +- packages/ui/src/components/Routes/Inquiry.tsx | 13 +- packages/ui/src/components/Routes/Menu.tsx | 2 +- .../ui/src/components/Routes/NotFoundPage.tsx | 29 +- .../ui/src/components/Routes/Page.stories.tsx | 15 +- packages/ui/src/components/Routes/Page.tsx | 30 +- packages/ui/src/components/Routes/Preview.tsx | 1 + packages/ui/src/pages.stories.tsx | 37 +- packages/ui/src/utils/language-negotiator.tsx | 22 + .../{schema/src => ui/src/utils}/locale.ts | 8 +- packages/ui/src/utils/operation.ts | 7 +- packages/ui/src/utils/translations.tsx | 33 +- packages/ui/src/utils/with-operation.tsx | 27 + pnpm-lock.yaml | 1517 ++++++++++++----- 49 files changed, 1549 insertions(+), 691 deletions(-) create mode 100644 apps/website/src/utils/locale.ts create mode 100644 packages/ui/src/utils/language-negotiator.tsx rename packages/{schema/src => ui/src/utils}/locale.ts (79%) create mode 100644 packages/ui/src/utils/with-operation.tsx diff --git a/apps/decap/package.json b/apps/decap/package.json index 4ed3ea046..fbd478fcb 100644 --- a/apps/decap/package.json +++ b/apps/decap/package.json @@ -32,7 +32,7 @@ "remark-rehype": "^10.1.0", "unified": "^10.1.2", "yaml": "^2.3.4", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@amazeelabs/decap-cms-backend-token-auth": "^1.1.7", diff --git a/apps/decap/src/helpers/frame.tsx b/apps/decap/src/helpers/frame.tsx index 192a4ab00..c181fa330 100644 --- a/apps/decap/src/helpers/frame.tsx +++ b/apps/decap/src/helpers/frame.tsx @@ -1,4 +1,9 @@ -import { FrameQuery, Locale, OperationExecutor, Url } from '@custom/schema'; +import { + FrameQuery, + Locale, + OperationExecutorsProvider, + Url, +} from '@custom/schema'; import { NavigationItemSource } from '@custom/schema/source'; import { Frame } from '@custom/ui/routes/Frame'; import { PropsWithChildren } from 'react'; @@ -16,25 +21,29 @@ const menuItems = (amount: number) => export function PreviewFrame({ children }: PropsWithChildren) { return ( - {children} - + ); } diff --git a/apps/decap/src/main.tsx b/apps/decap/src/main.tsx index 8514165f0..5a9d031d8 100644 --- a/apps/decap/src/main.tsx +++ b/apps/decap/src/main.tsx @@ -1,7 +1,7 @@ import { TokenAuthBackend } from '@amazeelabs/decap-cms-backend-token-auth/backend'; import { Locale, - OperationExecutor, + OperationExecutorsProvider, PreviewDecapPageQuery, ViewPageQuery, } from '@custom/schema'; @@ -95,9 +95,11 @@ CMS.registerPreviewTemplate( pageSchema, (data) => { return ( - + - + ); }, 'previewDecapPage', diff --git a/apps/preview/src/App.tsx b/apps/preview/src/App.tsx index 981244d51..3f9e259ec 100644 --- a/apps/preview/src/App.tsx +++ b/apps/preview/src/App.tsx @@ -1,4 +1,4 @@ -import { OperationExecutor } from '@custom/schema'; +import { OperationExecutorsProvider } from '@custom/schema'; import { Frame } from '@custom/ui/routes/Frame'; import { Preview, usePreviewRefresh } from '@custom/ui/routes/Preview'; import { useEffect } from 'react'; @@ -24,17 +24,17 @@ const updates$ = webSocket({ function App() { const refresh = usePreviewRefresh(); useEffect(() => { - const sub = updates$.subscribe(refresh); + const sub = updates$.subscribe(() => refresh({})); return sub.unsubscribe; }, [refresh]); return ( - - + ); } diff --git a/apps/website/package.json b/apps/website/package.json index 8ba4d3ae7..c1a3ec4c4 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -35,8 +35,8 @@ "@netlify/edge-functions": "^2.3.1", "@netlify/functions": "^2.6.0", "@testing-library/react": "^14.1.2", - "@types/react": "^18.2.46", - "@types/react-dom": "^18.2.18", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@types/serve-static": "^1.15.5", "happy-dom": "^12.10.3", "start-server-and-test": "^2.0.3", diff --git a/apps/website/src/layouts/index.tsx b/apps/website/src/layouts/index.tsx index 6bc8438cf..f5ff0c329 100644 --- a/apps/website/src/layouts/index.tsx +++ b/apps/website/src/layouts/index.tsx @@ -1,5 +1,5 @@ import { graphql, useStaticQuery } from '@amazeelabs/gatsby-plugin-operations'; -import { FrameQuery, OperationExecutor } from '@custom/schema'; +import { FrameQuery, OperationExecutorsProvider } from '@custom/schema'; import { Frame } from '@custom/ui/routes/Frame'; import React, { PropsWithChildren } from 'react'; @@ -12,10 +12,14 @@ export default function Layout({ }>) { const data = useStaticQuery(graphql(FrameQuery)); return ( - - + + {children} - - + + ); } diff --git a/apps/website/src/pages/404.tsx b/apps/website/src/pages/404.tsx index a7062457c..bfe49d2c4 100644 --- a/apps/website/src/pages/404.tsx +++ b/apps/website/src/pages/404.tsx @@ -1,5 +1,5 @@ import { graphql } from '@amazeelabs/gatsby-plugin-operations'; -import { NotFoundPageQuery, OperationExecutor } from '@custom/schema'; +import { NotFoundPageQuery, OperationExecutorsProvider } from '@custom/schema'; import { NotFoundPage } from '@custom/ui/routes/NotFoundPage'; import { PageProps } from 'gatsby'; import React from 'react'; @@ -8,8 +8,10 @@ export const query = graphql(NotFoundPageQuery); export default function Index({ data }: PageProps) { return ( - + - + ); } diff --git a/apps/website/src/templates/home.tsx b/apps/website/src/templates/home.tsx index 22096d7e6..8c149c00a 100644 --- a/apps/website/src/templates/home.tsx +++ b/apps/website/src/templates/home.tsx @@ -1,9 +1,11 @@ import { graphql } from '@amazeelabs/gatsby-plugin-operations'; -import { HomePageQuery, OperationExecutor, useLocalized } from '@custom/schema'; +import { HomePageQuery, OperationExecutorsProvider } from '@custom/schema'; import { HomePage } from '@custom/ui/routes/HomePage'; import { HeadProps, PageProps } from 'gatsby'; import React from 'react'; +import { useLocalized } from '../utils/locale'; + export const query = graphql(HomePageQuery); export function Head({ data }: HeadProps) { @@ -37,8 +39,10 @@ export function Head({ data }: HeadProps) { export default function Index({ data }: PageProps) { return ( - + - + ); } diff --git a/apps/website/src/templates/page.tsx b/apps/website/src/templates/page.tsx index f707acbb5..63510fcdb 100644 --- a/apps/website/src/templates/page.tsx +++ b/apps/website/src/templates/page.tsx @@ -1,5 +1,9 @@ import { graphql } from '@amazeelabs/gatsby-plugin-operations'; -import { OperationExecutor, useLocation, ViewPageQuery } from '@custom/schema'; +import { + OperationExecutorsProvider, + useLocation, + ViewPageQuery, +} from '@custom/schema'; import { Page } from '@custom/ui/routes/Page'; import { HeadProps, PageProps } from 'gatsby'; import React from 'react'; @@ -42,12 +46,16 @@ export default function PageTemplate({ data }: PageProps) { // path immediately returns this data. const [location] = useLocation(); return ( - - + ); } diff --git a/apps/website/src/utils/locale.ts b/apps/website/src/utils/locale.ts new file mode 100644 index 000000000..1e7b2699c --- /dev/null +++ b/apps/website/src/utils/locale.ts @@ -0,0 +1,33 @@ +// TODO: straight copy of the same file in the UI package +import { Locale, useLocation } from '@custom/schema'; + +const locales = Object.values(Locale); +export const defaultLocale: Locale = 'en'; + +export function isLocale(input: any): input is Locale { + return locales.includes(input); +} + +/** + * Extract the current locale from the path prefix. + */ +export function useLocale() { + const [{ pathname }] = useLocation(); + const prefix = pathname.split('/')[1]; + return isLocale(prefix) ? prefix : defaultLocale; +} + +type Localized = { locale: Locale }; + +/** + * Select the most appropriate of localization from a list of options. + */ +export function useLocalized( + options?: Array, +): T | undefined { + const locale = useLocale(); + return ( + options?.filter((option) => option?.locale === locale).pop() || + options?.filter((option) => option?.locale === defaultLocale).pop() + ); +} diff --git a/packages/schema/package.json b/packages/schema/package.json index bcbdd2da1..e4a4ddba2 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@amazeelabs/codegen-autoloader": "^1.1.3", - "@amazeelabs/codegen-operation-ids": "^0.1.34", + "@amazeelabs/codegen-operation-ids": "^0.1.43", "@graphql-codegen/cli": "^5.0.0", "@graphql-codegen/schema-ast": "^4.0.0", "@graphql-codegen/typescript": "^4.0.1", @@ -62,7 +62,7 @@ "typescript": "^5.3.3" }, "dependencies": { - "@amazeelabs/executors": "^2.0.2", + "@amazeelabs/executors": "^3.0.2", "@amazeelabs/gatsby-silverback-cloudinary": "^1.2.7", "@amazeelabs/gatsby-source-silverback": "^1.14.0", "@amazeelabs/scalars": "^1.6.13", diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 42559e8d3..35efe8b34 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -1,48 +1,8 @@ -import { - OperationExecutor as UntypedOperationExecutor, - useExecutor as untypedUseExecutor, -} from '@amazeelabs/executors'; -import { PropsWithChildren } from 'react'; - -import type { +export * from './generated/index.js'; +export * from '@amazeelabs/scalars'; +export * from '@amazeelabs/executors'; +export type { AnyOperationId, OperationResult, OperationVariables, -} from './generated/index.js'; - -export * from './generated/index.js'; -export * from '@amazeelabs/scalars'; - -export * from './locale.js'; - -type Executor = - | OperationResult - | (( - id: OperationId, - variables: OperationVariables, - ) => OperationResult | Promise>); - -type VariablesMatcher = - | Partial> - | ((vars: OperationVariables) => boolean); - -export function OperationExecutor( - props: PropsWithChildren<{ - id?: OperationId; - variables?: VariablesMatcher>; - executor: Executor; - }>, -) { - return UntypedOperationExecutor(props); -} - -export function useExecutor( - id: OperationId, - variables?: OperationVariables, -): - | OperationResult - | (( - variables?: OperationVariables, - ) => Promise>) { - return untypedUseExecutor(id, variables); -} +} from '@amazeelabs/codegen-operation-ids'; diff --git a/packages/schema/src/schema.graphql b/packages/schema/src/schema.graphql index 4c0150a42..8784b0a90 100644 --- a/packages/schema/src/schema.graphql +++ b/packages/schema/src/schema.graphql @@ -65,7 +65,7 @@ directive @decapPageTranslations on FIELD_DEFINITION """ Parse a given Url. -For Drupal, this is implicitly implemented in the "grraphql_directives" module. +For Drupal, this is implicitly implemented in the "graphql_directives" module. implementation(gatsby): ./page.js#route """ @@ -392,7 +392,6 @@ type DemoBlock { """ The type provided by translations source (e.g. Decap or Drupal). -Ingested by @mergeTranslatableStrings which handles priorities. """ interface TranslatableString @default @value { """ diff --git a/packages/ui/.storybook/preview.tsx b/packages/ui/.storybook/preview.tsx index 371952850..d0a2f9e0b 100644 --- a/packages/ui/.storybook/preview.tsx +++ b/packages/ui/.storybook/preview.tsx @@ -3,7 +3,7 @@ import '../src/tailwind.css'; import { LocationProvider } from '@custom/schema'; import { Decorator } from '@storybook/react'; import React from 'react'; -import { IntlProvider } from 'react-intl'; +import { IntlProvider } from '@amazeelabs/react-intl'; import { SWRConfig, useSWRConfig } from 'swr'; // Every story is wrapped in an IntlProvider by default. diff --git a/packages/ui/package.json b/packages/ui/package.json index 351391524..5aa19dc32 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -39,6 +39,7 @@ "report": "mkdir -p coverage/storybook && nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook" }, "dependencies": { + "@amazeelabs/react-intl": "^1.1.1", "@amazeelabs/silverback-iframe": "^1.3.0", "@custom/schema": "workspace:*", "@headlessui/react": "^2.0.3", @@ -50,10 +51,9 @@ "hast-util-select": "^5.0.5", "query-string": "^9.0.0", "react-hook-form": "^7.49.2", - "react-intl": "^6.6.2", "swr": "^2.2.4", "unified": "^10.1.2", - "zod": "^3.22.4", + "zod": "^3.23.8", "zustand": "^4.4.7" }, "peerDependencies": { diff --git a/packages/ui/src/components/Client/MobileMenu.tsx b/packages/ui/src/components/Client/MobileMenu.tsx index 022402639..4a3bf3abb 100644 --- a/packages/ui/src/components/Client/MobileMenu.tsx +++ b/packages/ui/src/components/Client/MobileMenu.tsx @@ -1,4 +1,5 @@ 'use client'; +import { useIntl } from '@amazeelabs/react-intl'; import { Dialog, DialogPanel, @@ -9,7 +10,6 @@ import { import { ChevronDownIcon } from '@heroicons/react/20/solid'; import clsx from 'clsx'; import React, { createContext, PropsWithChildren } from 'react'; -import { useIntl } from 'react-intl'; const MobileMenuContext = createContext({ isOpen: false, diff --git a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx index 6fafb4f0a..2ae3abc27 100644 --- a/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx +++ b/packages/ui/src/components/Molecules/Breadcrumbs.stories.tsx @@ -1,4 +1,4 @@ -import { FrameQuery, OperationExecutor } from '@custom/schema'; +import { FrameQuery, OperationExecutorsProvider } from '@custom/schema'; import { Meta, StoryObj } from '@storybook/react'; import React from 'react'; @@ -12,9 +12,11 @@ export default { }, render: () => { return ( - + - + ); }, } satisfies Meta; diff --git a/packages/ui/src/components/Molecules/InquiryForm.stories.tsx b/packages/ui/src/components/Molecules/InquiryForm.stories.tsx index 853793c16..c1576a302 100644 --- a/packages/ui/src/components/Molecules/InquiryForm.stories.tsx +++ b/packages/ui/src/components/Molecules/InquiryForm.stories.tsx @@ -1,6 +1,6 @@ import { CreateSubmissionMutation, - OperationExecutor, + OperationExecutorsProvider, OperationResult, } from '@custom/schema'; import { Meta, StoryObj } from '@storybook/react'; @@ -17,9 +17,11 @@ export default { title: 'Components/Molecules/InquiryForm', render: (args) => { return ( - + - + ); }, } satisfies Meta<{ exec: InquiryFormExecutor }>; diff --git a/packages/ui/src/components/Molecules/InquiryForm.tsx b/packages/ui/src/components/Molecules/InquiryForm.tsx index c58db3309..1ae6c5d39 100644 --- a/packages/ui/src/components/Molecules/InquiryForm.tsx +++ b/packages/ui/src/components/Molecules/InquiryForm.tsx @@ -1,7 +1,7 @@ +import { useIntl } from '@amazeelabs/react-intl'; import { CreateSubmissionMutation } from '@custom/schema'; import React from 'react'; import { useForm } from 'react-hook-form'; -import { useIntl } from 'react-intl'; import { z } from 'zod'; import { useMutation } from '../../utils/operation'; diff --git a/packages/ui/src/components/Molecules/LanguageSwitcher.stories.tsx b/packages/ui/src/components/Molecules/LanguageSwitcher.stories.tsx index e31a6bc01..7efbef9b3 100644 --- a/packages/ui/src/components/Molecules/LanguageSwitcher.stories.tsx +++ b/packages/ui/src/components/Molecules/LanguageSwitcher.stories.tsx @@ -1,33 +1,40 @@ -import { FrameQuery, OperationExecutor, Url } from '@custom/schema'; +import { FrameQuery, OperationExecutorsProvider, Url } from '@custom/schema'; import { Decorator, Meta, StoryObj } from '@storybook/react'; import React from 'react'; -import { Translations, TranslationsProvider } from '../../utils/translations'; +import { + TranslationPaths, + TranslationsProvider, +} from '../../utils/translations'; import { Default } from '../Routes/Frame.stories'; import { LanguageSwitcher } from './LanguageSwitcher'; const TranslationsDecorator = ((Story, ctx) => { return ( - - + ); -}) as Decorator; +}) as Decorator; export default { component: LanguageSwitcher, @@ -35,9 +42,9 @@ export default { parameters: { location: new URL('local:/en/english-version'), }, -} satisfies Meta; +} satisfies Meta; -type Story = StoryObj; +type Story = StoryObj; export const Empty = {} satisfies Story; diff --git a/packages/ui/src/components/Molecules/LanguageSwitcher.tsx b/packages/ui/src/components/Molecules/LanguageSwitcher.tsx index c77683543..05550c75a 100644 --- a/packages/ui/src/components/Molecules/LanguageSwitcher.tsx +++ b/packages/ui/src/components/Molecules/LanguageSwitcher.tsx @@ -1,3 +1,4 @@ +'use client'; import { Link, Locale, useLocation } from '@custom/schema'; import { Menu, Transition } from '@headlessui/react'; import { ChevronDownIcon } from '@heroicons/react/20/solid'; diff --git a/packages/ui/src/components/Molecules/PageTransition.tsx b/packages/ui/src/components/Molecules/PageTransition.tsx index 5b52b4bd8..8b859577d 100644 --- a/packages/ui/src/components/Molecules/PageTransition.tsx +++ b/packages/ui/src/components/Molecules/PageTransition.tsx @@ -1,9 +1,24 @@ +'use client'; import { Locale } from '@custom/schema'; -import { motion, useReducedMotion } from 'framer-motion'; +import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'; import React, { PropsWithChildren, ReactNode, useEffect } from 'react'; import { Messages, readMessages } from './Messages'; +export function PageTransitionWrapper({ children }: PropsWithChildren) { + return ( +
    + {useReducedMotion() ? ( + <>{children} + ) : ( + + {children} + + )} +
    + ); +} + export function PageTransition({ children }: PropsWithChildren) { const [messages, setMessages] = React.useState>([]); const [messageComponents, setMessageComponents] = React.useState< diff --git a/packages/ui/src/components/Molecules/Pagination.tsx b/packages/ui/src/components/Molecules/Pagination.tsx index d85beb270..f26b76880 100644 --- a/packages/ui/src/components/Molecules/Pagination.tsx +++ b/packages/ui/src/components/Molecules/Pagination.tsx @@ -1,3 +1,4 @@ +import { useIntl } from '@amazeelabs/react-intl'; import { Link, useLocation } from '@custom/schema'; import { ArrowLongLeftIcon, @@ -5,7 +6,6 @@ import { } from '@heroicons/react/20/solid'; import clsx from 'clsx'; import React from 'react'; -import { useIntl } from 'react-intl'; import { z } from 'zod'; export const paginationParamsSchema = z.object({ diff --git a/packages/ui/src/components/Molecules/SearchForm.tsx b/packages/ui/src/components/Molecules/SearchForm.tsx index e043a6fcd..989a49fe3 100644 --- a/packages/ui/src/components/Molecules/SearchForm.tsx +++ b/packages/ui/src/components/Molecules/SearchForm.tsx @@ -1,8 +1,8 @@ +import { useIntl } from '@amazeelabs/react-intl'; import { useLocation } from '@custom/schema'; import { zodResolver } from '@hookform/resolvers/zod'; import React from 'react'; import { useForm } from 'react-hook-form'; -import { useIntl } from 'react-intl'; import { z } from 'zod'; const formValueSchema = z.object({ diff --git a/packages/ui/src/components/Organisms/ContentHub.stories.tsx b/packages/ui/src/components/Organisms/ContentHub.stories.tsx index 864d92352..ee15a86c4 100644 --- a/packages/ui/src/components/Organisms/ContentHub.stories.tsx +++ b/packages/ui/src/components/Organisms/ContentHub.stories.tsx @@ -1,7 +1,7 @@ import { ContentHubQuery, ContentHubResultItemFragment, - OperationExecutor, + OperationExecutorsProvider, OperationResult, OperationVariables, Url, @@ -26,9 +26,11 @@ export default { title: 'Components/Organisms/ContentHub', render: (args) => { return ( - + - + ); }, } satisfies Meta<{ exec: ContentHubExecutor }>; @@ -58,7 +60,7 @@ export const Error = { }, } satisfies ContentHubStory; -export const WithResults = { +export const WithResults: ContentHubStory = { args: { exec: async (_, vars) => { const items = [...Array(82).keys()].map( @@ -93,18 +95,18 @@ export const WithResults = { }; }, }, -} satisfies ContentHubStory; +}; -export const Filtered = { +export const Filtered: ContentHubStory = { ...WithResults, parameters: { location: new URL('local:/content-hub?keyword=Article'), }, -} satisfies ContentHubStory; +}; -export const Paged = { +export const Paged: ContentHubStory = { ...WithResults, parameters: { location: new URL('local:/content-hub?page=2'), }, -} satisfies ContentHubStory; +}; diff --git a/packages/ui/src/components/Organisms/ContentHub.tsx b/packages/ui/src/components/Organisms/ContentHub.tsx index c6ccd3475..5dface3d5 100644 --- a/packages/ui/src/components/Organisms/ContentHub.tsx +++ b/packages/ui/src/components/Organisms/ContentHub.tsx @@ -1,7 +1,7 @@ +import { useIntl } from '@amazeelabs/react-intl'; import { ContentHubQuery, Image, Link, Locale } from '@custom/schema'; import qs from 'query-string'; import React from 'react'; -import { useIntl } from 'react-intl'; import { isTruthy } from '../../utils/isTruthy'; import { useOperation } from '../../utils/operation'; diff --git a/packages/ui/src/components/Organisms/Footer.stories.tsx b/packages/ui/src/components/Organisms/Footer.stories.tsx index abef3981e..d3bef7003 100644 --- a/packages/ui/src/components/Organisms/Footer.stories.tsx +++ b/packages/ui/src/components/Organisms/Footer.stories.tsx @@ -1,4 +1,9 @@ -import { FrameQuery, Locale, OperationExecutor, Url } from '@custom/schema'; +import { + FrameQuery, + Locale, + OperationExecutorsProvider, + Url, +} from '@custom/schema'; import { Meta, StoryObj } from '@storybook/react'; import React from 'react'; @@ -14,9 +19,11 @@ export default { export const Footer = { render: (args) => { return ( - args}> + - + ); }, args: { diff --git a/packages/ui/src/components/Organisms/Footer.tsx b/packages/ui/src/components/Organisms/Footer.tsx index 73dc34bdf..e66a600da 100644 --- a/packages/ui/src/components/Organisms/Footer.tsx +++ b/packages/ui/src/components/Organisms/Footer.tsx @@ -1,6 +1,7 @@ +'use client'; +import { useIntl } from '@amazeelabs/react-intl'; import { FrameQuery, Link } from '@custom/schema'; import React from 'react'; -import { useIntl } from 'react-intl'; import { isTruthy } from '../../utils/isTruthy'; import { buildNavigationTree } from '../../utils/navigation'; diff --git a/packages/ui/src/components/Organisms/Header.stories.tsx b/packages/ui/src/components/Organisms/Header.stories.tsx index c895dfe2e..84e06473d 100644 --- a/packages/ui/src/components/Organisms/Header.stories.tsx +++ b/packages/ui/src/components/Organisms/Header.stories.tsx @@ -1,4 +1,9 @@ -import { FrameQuery, Locale, OperationExecutor, Url } from '@custom/schema'; +import { + FrameQuery, + Locale, + OperationExecutorsProvider, + Url, +} from '@custom/schema'; import { Meta, StoryObj } from '@storybook/react'; import { userEvent, within } from '@storybook/test'; import React from 'react'; @@ -16,9 +21,11 @@ export default { export const Idle = { render: (args) => { return ( - args}> +
    - + ); }, args: { diff --git a/packages/ui/src/components/Organisms/Header.tsx b/packages/ui/src/components/Organisms/Header.tsx index b132e4ceb..d9ef03f49 100644 --- a/packages/ui/src/components/Organisms/Header.tsx +++ b/packages/ui/src/components/Organisms/Header.tsx @@ -1,7 +1,8 @@ +'use client'; +import { useIntl } from '@amazeelabs/react-intl'; import { FrameQuery, Link, Url } from '@custom/schema'; import clsx from 'clsx'; import React from 'react'; -import { useIntl } from 'react-intl'; import { isTruthy } from '../../utils/isTruthy'; import { buildNavigationTree } from '../../utils/navigation'; diff --git a/packages/ui/src/components/Organisms/Inquiry.tsx b/packages/ui/src/components/Organisms/Inquiry.tsx index 6a5bb6404..21441243c 100644 --- a/packages/ui/src/components/Organisms/Inquiry.tsx +++ b/packages/ui/src/components/Organisms/Inquiry.tsx @@ -1,3 +1,4 @@ +'use client'; import React from 'react'; import { InquiryForm } from '../Molecules/InquiryForm'; diff --git a/packages/ui/src/components/Organisms/PageDisplay.tsx b/packages/ui/src/components/Organisms/PageDisplay.tsx index 438d43893..c5999faf9 100644 --- a/packages/ui/src/components/Organisms/PageDisplay.tsx +++ b/packages/ui/src/components/Organisms/PageDisplay.tsx @@ -1,3 +1,4 @@ +'use client'; import { PageFragment } from '@custom/schema'; import React from 'react'; diff --git a/packages/ui/src/components/Routes/ContentHub.tsx b/packages/ui/src/components/Routes/ContentHub.tsx index c9b001b80..1cf661b62 100644 --- a/packages/ui/src/components/Routes/ContentHub.tsx +++ b/packages/ui/src/components/Routes/ContentHub.tsx @@ -1,20 +1,24 @@ +'use client'; import { Locale } from '@custom/schema'; import React from 'react'; -import { useTranslations } from '../../utils/translations'; +import { Translations } from '../../utils/translations'; import { PageTransition } from '../Molecules/PageTransition'; import { ContentHub as ContentHubOrganism } from '../Organisms/ContentHub'; export function ContentHub(props: { pageSize: number }) { - // Initialize the content hub in each language. - useTranslations( - Object.fromEntries( - Object.values(Locale).map((locale) => [locale, `/${locale}/content-hub`]), - ), - ); return ( - + [ + locale, + `/${locale}/content-hub`, + ]), + )} + > + + ); } diff --git a/packages/ui/src/components/Routes/Frame.stories.tsx b/packages/ui/src/components/Routes/Frame.stories.tsx index a100b1016..efaca21e6 100644 --- a/packages/ui/src/components/Routes/Frame.stories.tsx +++ b/packages/ui/src/components/Routes/Frame.stories.tsx @@ -1,4 +1,4 @@ -import { FrameQuery, OperationExecutor } from '@custom/schema'; +import { FrameQuery, OperationExecutorsProvider } from '@custom/schema'; import { Meta, StoryObj } from '@storybook/react'; import React from 'react'; @@ -16,9 +16,11 @@ export default { export const Default = { render: (args) => { return ( - args} id={FrameQuery}> + - + ); }, args: { diff --git a/packages/ui/src/components/Routes/Frame.tsx b/packages/ui/src/components/Routes/Frame.tsx index 6efe5c1c9..2ab1ec148 100644 --- a/packages/ui/src/components/Routes/Frame.tsx +++ b/packages/ui/src/components/Routes/Frame.tsx @@ -1,11 +1,11 @@ -import { FrameQuery, Locale, useLocale } from '@custom/schema'; -import { AnimatePresence, useReducedMotion } from 'framer-motion'; +import { IntlProvider } from '@amazeelabs/react-intl'; +import { FrameQuery, Locale, Operation } from '@custom/schema'; import React, { PropsWithChildren } from 'react'; -import { IntlProvider } from 'react-intl'; import translationSources from '../../../build/translatables.json'; -import { useOperation } from '../../utils/operation'; +import { useLocale } from '../../utils/locale'; import { TranslationsProvider } from '../../utils/translations'; +import { PageTransitionWrapper } from '../Molecules/PageTransition'; import { Footer } from '../Organisms/Footer'; import { Header } from '../Organisms/Header'; @@ -30,44 +30,43 @@ function translationsMap(translatables: FrameQuery['stringTranslations']) { ); } -function useTranslations() { +export function Frame({ children }: PropsWithChildren) { const locale = useLocale(); - const translations = useOperation(FrameQuery).data?.stringTranslations; - return { - ...translationsMap(translations?.filter(filterByLocale('en')) || []), - ...translationsMap(translations?.filter(filterByLocale(locale)) || []), - }; -} - -export function Frame(props: PropsWithChildren<{}>) { - const locale = useLocale(); - const translations = useTranslations(); - const messages = Object.fromEntries( - Object.keys(translationSources).map((key) => [ - key, - translations[ - translationSources[key as keyof typeof translationSources] - .defaultMessage - ] || - translationSources[key as keyof typeof translationSources] - .defaultMessage, - ]), - ); return ( - - -
    -
    - {useReducedMotion() ? ( - <>{props.children} - ) : ( - - {props.children} - - )} -
    -