diff --git a/.changeset/big-rules-poke.md b/.changeset/big-rules-poke.md deleted file mode 100644 index 0b5a6124..00000000 --- a/.changeset/big-rules-poke.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-layouts': patch ---- - -Added @salt-ds/core to @jpmorganchase/mosaic-layouts' package.json. diff --git a/.changeset/brown-tips-cover.md b/.changeset/brown-tips-cover.md deleted file mode 100644 index 672d6b26..00000000 --- a/.changeset/brown-tips-cover.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@jpmorganchase/mosaic-site': patch -'@jpmorganchase/mosaic-standard-generator': patch ---- - -Fixed site font not loading from next/font diff --git a/.changeset/eighty-apes-float.md b/.changeset/eighty-apes-float.md deleted file mode 100644 index dd1dfe78..00000000 --- a/.changeset/eighty-apes-float.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-source-storybook': patch ---- - -Fix storybook source not working with index.json diff --git a/.changeset/fifty-otters-deliver.md b/.changeset/fifty-otters-deliver.md deleted file mode 100644 index e56c3fb1..00000000 --- a/.changeset/fifty-otters-deliver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-plugins': patch ---- - -Fixed DocumentAssetsPlugin not working on Windows diff --git a/.changeset/fifty-peaches-change.md b/.changeset/fifty-peaches-change.md deleted file mode 100644 index 1343db96..00000000 --- a/.changeset/fifty-peaches-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-plugins': patch ---- - -Fixed BreadcrumbsPlugin not working on Windows. diff --git a/.changeset/fifty-schools-cheer.md b/.changeset/fifty-schools-cheer.md deleted file mode 100644 index 452b0526..00000000 --- a/.changeset/fifty-schools-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-components': minor ---- - -Added `themeClassName` to `ThemeProvider` to apply `theme` to `SaltProvider` diff --git a/.changeset/fluffy-onions-cry.md b/.changeset/fluffy-onions-cry.md deleted file mode 100644 index f4fe1c8f..00000000 --- a/.changeset/fluffy-onions-cry.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@jpmorganchase/mosaic-components': patch -'@jpmorganchase/mosaic-labs-components': patch -'@jpmorganchase/mosaic-content-editor-plugin': patch -'@jpmorganchase/mosaic-layouts': patch -'@jpmorganchase/mosaic-site': patch -'@jpmorganchase/mosaic-site-components': patch -'@jpmorganchase/mosaic-sitemap-component': patch ---- - -Updated Salt packages. diff --git a/.changeset/forty-ways-mix.md b/.changeset/forty-ways-mix.md deleted file mode 100644 index d7958034..00000000 --- a/.changeset/forty-ways-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-components': minor ---- - -Added `themeNext` to `ThemeProvider` to enable Salt theme next provider diff --git a/.changeset/funny-experts-brake.md b/.changeset/funny-experts-brake.md deleted file mode 100644 index f5d5e10f..00000000 --- a/.changeset/funny-experts-brake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-site-components': patch ---- - -Fixed pages with breadcrumbs having an empty breadcrumb meta diff --git a/.changeset/fuzzy-carpets-warn.md b/.changeset/fuzzy-carpets-warn.md new file mode 100644 index 00000000..672ba1c9 --- /dev/null +++ b/.changeset/fuzzy-carpets-warn.md @@ -0,0 +1,35 @@ +--- +'@jpmorganchase/mosaic-cli': minor +'@jpmorganchase/mosaic-components': minor +'@jpmorganchase/mosaic-labs-components': minor +'@jpmorganchase/mosaic-content-editor-plugin': minor +'@jpmorganchase/mosaic-core': minor +'@jpmorganchase/mosaic-icons': minor +'@jpmorganchase/mosaic-layouts': minor +'@jpmorganchase/mosaic-loaders': minor +'@jpmorganchase/mosaic-mdx-components': minor +'@jpmorganchase/mosaic-open-api-component': minor +'@jpmorganchase/mosaic-plugins': minor +'@jpmorganchase/mosaic-schemas': minor +'@jpmorganchase/mosaic-serialisers': minor +'@jpmorganchase/mosaic-site': minor +'@jpmorganchase/mosaic-site-components': minor +'@jpmorganchase/mosaic-site-components-next': minor +'@jpmorganchase/mosaic-site-preset-styles': minor +'@jpmorganchase/mosaic-sitemap-component': minor +'@jpmorganchase/mosaic-source-git-repo': minor +'@jpmorganchase/mosaic-source-readme': minor +'@jpmorganchase/mosaic-standard-generator': minor +'@jpmorganchase/mosaic-store': minor +'@jpmorganchase/mosaic-theme': minor +'@jpmorganchase/mosaic-types': minor +'@jpmorganchase/mosaic-create-site': minor +'@jpmorganchase/mosaic-from-http-request': minor +'@jpmorganchase/mosaic-source-figma': minor +'@jpmorganchase/mosaic-source-http': minor +'@jpmorganchase/mosaic-source-local-folder': minor +'@jpmorganchase/mosaic-source-storybook': minor +'@jpmorganchase/mosaic-workflows': minor +--- + +Refactor Mosaic around the Next.js app router diff --git a/.changeset/grumpy-turkeys-crash.md b/.changeset/grumpy-turkeys-crash.md deleted file mode 100644 index 4515bd12..00000000 --- a/.changeset/grumpy-turkeys-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-theme': patch ---- - -Reduce the size of the baseline CSS file diff --git a/.changeset/hungry-owls-jump.md b/.changeset/hungry-owls-jump.md deleted file mode 100644 index b54b1c97..00000000 --- a/.changeset/hungry-owls-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-plugins': patch ---- - -Fix bug where `ensureDir` created a directory and broke the cloning of repos diff --git a/.changeset/lucky-colts-hang.md b/.changeset/lucky-colts-hang.md deleted file mode 100644 index 0ee98147..00000000 --- a/.changeset/lucky-colts-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-components': patch ---- - -Fixed links not having a gap between the text and icon. diff --git a/.changeset/mighty-monkeys-switch.md b/.changeset/mighty-monkeys-switch.md deleted file mode 100644 index ebf150d0..00000000 --- a/.changeset/mighty-monkeys-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-site-components': patch ---- - -Relax navigation width condition for different responsive usage diff --git a/.changeset/nervous-comics-hug.md b/.changeset/nervous-comics-hug.md deleted file mode 100644 index ee16e22e..00000000 --- a/.changeset/nervous-comics-hug.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@jpmorganchase/mosaic-plugins': patch -'@jpmorganchase/mosaic-site-components': patch ---- - -handle Vertical Navigation edge cases - -- Vertical Navigation groups containing single pages should render as links rather than expandable rows -- configured page extensions were ignored by page filter diff --git a/.changeset/perfect-cougars-grab.md b/.changeset/perfect-cougars-grab.md deleted file mode 100644 index 79c2fb81..00000000 --- a/.changeset/perfect-cougars-grab.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-store': patch ---- - -Add displayName for `StoreContext` diff --git a/.changeset/pre.json b/.changeset/pre.json index c1e971ca..0eabd853 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -20,7 +20,6 @@ "@jpmorganchase/mosaic-open-api-component": "0.1.0-beta.9", "@jpmorganchase/mosaic-site": "0.1.0-beta.9", "@jpmorganchase/mosaic-site-components": "0.1.0-beta.9", - "@jpmorganchase/mosaic-site-middleware": "0.1.0-beta.9", "@jpmorganchase/mosaic-site-preset-styles": "0.1.0-beta.9", "@jpmorganchase/mosaic-standard-generator": "0.1.0-beta.9", "@jpmorganchase/mosaic-labs-components": "0.1.0-beta.11", diff --git a/.changeset/pretty-otters-report.md b/.changeset/pretty-otters-report.md deleted file mode 100644 index 484ff8f5..00000000 --- a/.changeset/pretty-otters-report.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@jpmorganchase/mosaic-plugins': patch ---- - -added `remark-mdx` to `DocumentAssetsPlugin` - -when parsing JSX, it was escaping JSX elements it considered un-safe, because it thought they were html diff --git a/.changeset/pretty-socks-judge.md b/.changeset/pretty-socks-judge.md deleted file mode 100644 index 6db54c8c..00000000 --- a/.changeset/pretty-socks-judge.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@jpmorganchase/mosaic-site-components': minor ---- - -Refactor Breadcrumbs to remove usage of lab components. -Fixed non-unique key error in AppHeaderControls. diff --git a/.changeset/rich-queens-breathe.md b/.changeset/rich-queens-breathe.md deleted file mode 100644 index 4eda775c..00000000 --- a/.changeset/rich-queens-breathe.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@jpmorganchase/mosaic-source-figma': patch ---- - -fix Figma source JSON issue - -If a Figma page did not defined `sharedPluginData` then the Figma source would break diff --git a/.changeset/sixty-gifts-prove.md b/.changeset/sixty-gifts-prove.md deleted file mode 100644 index bae4c93c..00000000 --- a/.changeset/sixty-gifts-prove.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-source-figma': patch ---- - -Fixed projects with multiple files not working. diff --git a/.changeset/smooth-clouds-tease.md b/.changeset/smooth-clouds-tease.md deleted file mode 100644 index ba396252..00000000 --- a/.changeset/smooth-clouds-tease.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@jpmorganchase/mosaic-schemas': patch ---- - -- switch the default behaviour of schema validation to exit on error -- improve logging of schema validation errors to prevent silent failures diff --git a/.changeset/smooth-dolls-add.md b/.changeset/smooth-dolls-add.md deleted file mode 100644 index 5d7e465c..00000000 --- a/.changeset/smooth-dolls-add.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@jpmorganchase/mosaic-cli': patch -'@jpmorganchase/mosaic-schemas': patch -'@jpmorganchase/mosaic-site': patch -'@jpmorganchase/mosaic-types': patch -'@jpmorganchase/mosaic-workflows': patch ---- - -Feat: add GithubPullRequestWorkflow - -This workflow requires a [fine-grained github personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) so that the github rest API can be used to create Pull Requests. diff --git a/.changeset/tasty-needles-clean.md b/.changeset/tasty-needles-clean.md deleted file mode 100644 index 5c43ac4e..00000000 --- a/.changeset/tasty-needles-clean.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-components': patch ---- - -Fix Link sometimes removing text children. diff --git a/.changeset/ten-carrots-mix.md b/.changeset/ten-carrots-mix.md deleted file mode 100644 index bfcb2805..00000000 --- a/.changeset/ten-carrots-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-theme': patch ---- - -Added type:module to the package.json diff --git a/.changeset/thick-trees-act.md b/.changeset/thick-trees-act.md deleted file mode 100644 index 242ed812..00000000 --- a/.changeset/thick-trees-act.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-source-storybook': patch ---- - -Fixed storybook-source outputting invalid Storybook links. diff --git a/.changeset/warm-tigers-return.md b/.changeset/warm-tigers-return.md deleted file mode 100644 index 74240c75..00000000 --- a/.changeset/warm-tigers-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-site-components': patch ---- - -Fixed `VerticalNavigation` not showing active for group with hidden items diff --git a/.changeset/weak-suits-shake.md b/.changeset/weak-suits-shake.md deleted file mode 100644 index 61b471f5..00000000 --- a/.changeset/weak-suits-shake.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@jpmorganchase/mosaic-plugins': patch -'@jpmorganchase/mosaic-site': patch ---- - -Add DocumentAssetPlugin - -The `DocumentAssetsPlugin` is responsible for copying assets from a document sub-directory to the public folder of your site. This is particularly useful for co-locating images within your document structure and referencing them from documents using relative paths. diff --git a/.changeset/weak-tigers-add.md b/.changeset/weak-tigers-add.md deleted file mode 100644 index e3eb63a5..00000000 --- a/.changeset/weak-tigers-add.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@jpmorganchase/mosaic-content-editor-plugin': patch -'@jpmorganchase/mosaic-site': patch -'@jpmorganchase/mosaic-theme': patch ---- - -Fixed link style CSS is generated against both light and dark modes. Fixed #640. diff --git a/.changeset/wicked-impalas-develop.md b/.changeset/wicked-impalas-develop.md deleted file mode 100644 index 4f9d832d..00000000 --- a/.changeset/wicked-impalas-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@jpmorganchase/mosaic-site-components': patch ---- - -Remove fragments from DocPaginator to stop React logging an error diff --git a/.prettierignore b/.prettierignore index 425feed8..00661863 100644 --- a/.prettierignore +++ b/.prettierignore @@ -11,6 +11,7 @@ LICENSE *.png *.hbs *.jpg +*.ico **/build **/dist diff --git a/docs/author/refs.mdx b/docs/author/refs.mdx index d8ccbcd2..e6d69d9e 100644 --- a/docs/author/refs.mdx +++ b/docs/author/refs.mdx @@ -157,6 +157,7 @@ You can use Mosaic components with referenced data as well. Below we are using t {meta.data.modes.map(mode => ( ({ - Diagram: withMarkdownSpacing(Diagram), - Sitemap: withMarkdownSpacing(Sitemap) -}); diff --git a/packages/components/package.json b/packages/components/package.json index 2f8710d9..e45dec23 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -36,7 +36,7 @@ "dev": "node ../../scripts/bundle.mjs watch" }, "devDependencies": { - "react": "^18.2.0", + "react": "^18.3.0", "del-cli": "^4.0.1", "typescript": "^5.0.0" }, @@ -44,27 +44,21 @@ "@jpmorganchase/mosaic-store": "^0.1.0-beta.89", "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", "@jpmorganchase/mosaic-icons": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0", - "@salt-ds/lab": "1.0.0-alpha.50", + "@salt-ds/core": "^1.37.1", + "@salt-ds/lab": "1.0.0-alpha.54", "@vanilla-extract/css": "^1.6.0", "@vanilla-extract/sprinkles": "^1.3.0", "@vanilla-extract/recipes": "^0.2.1", "clsx": "^2.0.0", - "deepmerge": "^2.0.1", "hoist-non-react-statics": "^3.3.2", "lodash-es": "^4.17.21", - "prism-react-renderer": "^1.1.1", - "react-live": "^4.0.0", - "react-markdown": "^9.0.0", - "react-responsive-carousel": "3.2.10", "react-table": "^7.8.0", - "swagger-ui-react": "^5.0.0", "use-memo-one": "^1.1.1", "warning": "^3.0.0" }, "peerDependencies": { "@types/react": "^18.3.12", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/components/src/AudioPlayer/index.tsx b/packages/components/src/AudioPlayer/index.tsx index 2ae8db57..2ee53ce2 100644 --- a/packages/components/src/AudioPlayer/index.tsx +++ b/packages/components/src/AudioPlayer/index.tsx @@ -126,7 +126,7 @@ export const AudioPlayer: React.FC = ({ src, title, skipDurati className={styles.slider} min={0} max={durationSeconds} - value={timeNowSeconds} + value={[timeNowSeconds]} onChange={handleSliderInput} /> {durationString} diff --git a/packages/components/src/EditionFilterView/index.tsx b/packages/components/src/EditionFilterView/index.tsx index 49015741..8e1ce677 100644 --- a/packages/components/src/EditionFilterView/index.tsx +++ b/packages/components/src/EditionFilterView/index.tsx @@ -1,10 +1,9 @@ -import React from 'react'; +import { ReactNode, FC } from 'react'; import classnames from 'clsx'; import { FilterResultCount, FilterView } from '../FilterView'; import { FilterDropdown, FilterSortDropdown } from '../FilterToolbar'; import { EditionTileLink } from '../EditionTileLink'; -import { FormattedContent } from '../FormattedContent'; import styles from './styles.css'; export const createCustomFilter = (view, filters) => @@ -38,7 +37,7 @@ export type Edition = { group?: string; link: string; publicationDate: string; - formattedDescription?: string; + formattedDescription?: ReactNode; title?: string; }; @@ -60,11 +59,7 @@ export type EditionFilterViewProps = { export const DefaultEditionFilterViewRenderer: EditionFilterViewRenderer = (item, itemIndex) => ( {item.formattedDescription} - ) : null - } + description={item.formattedDescription ? item.formattedDescription : null} eyebrow={item.eyebrow} image={item.image} key={`editionTile-${itemIndex}`} @@ -72,7 +67,7 @@ export const DefaultEditionFilterViewRenderer: EditionFilterViewRenderer = (item title={item.title} /> ); -export const EditionFilterView: React.FC> = ({ +export const EditionFilterView: FC = ({ className, ItemRenderer = DefaultEditionFilterViewRenderer, view, diff --git a/packages/components/src/EditionTileLink/index.tsx b/packages/components/src/EditionTileLink/index.tsx index 8307dba0..2b7f6849 100644 --- a/packages/components/src/EditionTileLink/index.tsx +++ b/packages/components/src/EditionTileLink/index.tsx @@ -1,9 +1,9 @@ import React, { FC, ReactNode } from 'react'; +import { useImageComponent } from '@jpmorganchase/mosaic-store'; import { LinkBase } from '../LinkBase'; import { LinkText } from '../LinkText'; import { TileBase, useTileState } from '../TileBase'; -import { useImageComponent } from '../ImageProvider'; import { useBreakpoint } from '../useBreakpoint'; import styles, { imageRecipe, tileImageRecipe } from './styles.css'; diff --git a/packages/components/src/Feature/Feature.tsx b/packages/components/src/Feature/Feature.tsx index 4305510b..78096b81 100644 --- a/packages/components/src/Feature/Feature.tsx +++ b/packages/components/src/Feature/Feature.tsx @@ -1,9 +1,9 @@ import React from 'react'; import classnames from 'clsx'; import { feature } from '@jpmorganchase/mosaic-theme'; +import { useImageComponent } from '@jpmorganchase/mosaic-store'; import styles from './styles.css'; -import { useImageComponent } from '../ImageProvider'; export type FeatureClassesType = { content?: string; diff --git a/packages/components/src/FormattedContent.tsx b/packages/components/src/FormattedContent.tsx deleted file mode 100644 index ecd02a32..00000000 --- a/packages/components/src/FormattedContent.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react'; -import ReactMarkdown, { Components } from 'react-markdown'; - -import { getMarkdownElements } from './Markdown/markdownElements'; - -type SupportedComponents = Pick< - Components, - 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'em' | 'strong' | 'ul' | 'ol' | 'li' -> & { - listItem: SupportedComponents['li']; - emphasis: SupportedComponents['em']; -}; - -type FormattedContentProps = { - className?: string; - children: string; - components?: SupportedComponents; -}; - -const { - h1: H1, - h2: H2, - h3: H3, - h4: H4, - h5: H5, - h6: H6, - ol: Ol, - ul: Ul, - li: ListItem, - em: Emphasis, - p: P, - strong: Strong -} = getMarkdownElements(); - -const renderers: SupportedComponents = { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - h1: ({ node, ...props }) =>

, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - h2: ({ node, ...props }) =>

, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - h3: ({ node, ...props }) =>

, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - h4: ({ node, ...props }) =>

, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - h5: ({ node, ...props }) =>

, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - h6: ({ node, ...props }) =>
, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - p: ({ node, children, ...props }) =>

{children}

, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - emphasis: ({ node, children, ...props }) => {children}, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - strong: ({ node, children, ...props }) => {children}, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ul: ({ node, children, ...props }) =>
    {children}
, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ol: ({ node, children, ...props }) =>
    {children}
, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - listItem: ({ node, children, ...props }) => {children} -}; - -export const FormattedContent: React.FC> = ({ - children, - components = {}, - ...rest -}) => ( - - {children} - -); diff --git a/packages/components/src/Hero/index.tsx b/packages/components/src/Hero/index.tsx index d9a33ecb..72b88dd6 100644 --- a/packages/components/src/Hero/index.tsx +++ b/packages/components/src/Hero/index.tsx @@ -1,10 +1,10 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement, ReactNode } from 'react'; import classnames from 'clsx'; +import { useImageComponent } from '@jpmorganchase/mosaic-store'; import { LinkButton } from '../LinkButton'; import styles from './styles.css'; import { Eyebrow, H1, P2 } from '../Typography'; -import { useImageComponent } from '../ImageProvider'; const formatTitle = (fullWidth: boolean, title: string, key: string): React.ReactNode => { const lines = title ? title.split('
') : []; @@ -30,6 +30,8 @@ export interface HeroLink { */ export interface HeroProps { backgroundImage?: string; + /** Optional children to use instead of links */ + children?: ReactNode; /** Additional class name for root class override. */ className?: string; /** Prop to provide a datestamp. */ @@ -42,7 +44,7 @@ export interface HeroProps { /** Image to be displayed. */ image?: string; /** The title of the Hero. */ - title: string | ReactElement; + title?: string | ReactElement; links?: HeroLink[]; /** Defines the variant. * @defaultValue `regular` @@ -95,8 +97,9 @@ function HeroImageContainer({ isFramed, heroBackgroundImage, heroImage, isFullWi * /> * ``` */ -export const Hero: React.FC> = ({ +export const Hero: React.FC = ({ backgroundImage, + children, className, datestamp, datestampLabel = 'Last Modified', @@ -139,7 +142,7 @@ export const Hero: React.FC> = ({ ) : null} {description ? {description} : null} {links ? ( -
+
{links.map((link, linkIndex) => { const isLastLink = linkIndex === links.length - 1; return ( @@ -157,7 +160,9 @@ export const Hero: React.FC> = ({ ); })}
- ) : null} + ) : ( +
{children}
+ )}
{image ? ( = createContext('img'); - -export const useImageComponent = (): ElementType => useContext(ImageContext); - -export const ImageProvider = ImageContext.Provider; diff --git a/packages/components/src/Impact/index.tsx b/packages/components/src/Impact/index.tsx index 32860c32..45b0c090 100644 --- a/packages/components/src/Impact/index.tsx +++ b/packages/components/src/Impact/index.tsx @@ -1,8 +1,8 @@ import React from 'react'; import classnames from 'clsx'; +import { useImageComponent } from '@jpmorganchase/mosaic-store'; import styles from './styles.css'; -import { useImageComponent } from '../ImageProvider'; export interface ImpactProps { /** Additional class name for root class override */ diff --git a/packages/components/src/LinkBase/index.tsx b/packages/components/src/LinkBase/index.tsx index 9929eeb1..add60b29 100644 --- a/packages/components/src/LinkBase/index.tsx +++ b/packages/components/src/LinkBase/index.tsx @@ -1,6 +1,6 @@ import React, { forwardRef, Ref } from 'react'; +import { useLinkComponent } from '@jpmorganchase/mosaic-store'; -import { useLinkComponent } from '../LinkProvider'; import { hasProtocol } from '../utils/hasProtocol'; export interface LinkBaseProps extends Omit, 'ref'> { diff --git a/packages/components/src/LinkProvider.ts b/packages/components/src/LinkProvider.ts deleted file mode 100644 index eff307d0..00000000 --- a/packages/components/src/LinkProvider.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext, Context, ElementType, useContext } from 'react'; - -export type LinkProviderValue = ElementType; - -const LinkContext: Context = createContext('a'); - -export const useLinkComponent = (): ElementType => useContext(LinkContext); - -export const LinkProvider = LinkContext.Provider; diff --git a/packages/components/src/Markdown/Heading.tsx b/packages/components/src/Markdown/Heading.tsx deleted file mode 100644 index a77965b9..00000000 --- a/packages/components/src/Markdown/Heading.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { heading } from '@jpmorganchase/mosaic-theme'; - -import { withStyledTypography } from '../Typography/withStyledTypography'; - -export const H0 = withStyledTypography( - heading({ variant: 'heading0', context: 'markdown' }), - 'h1', - { role: 'heading' } -); -export const H1 = withStyledTypography( - heading({ variant: 'heading1', context: 'markdown' }), - 'h1', - { role: 'heading' } -); -export const H2 = withStyledTypography( - heading({ variant: 'heading2', context: 'markdown' }), - 'h2', - { role: 'heading' } -); -export const H3 = withStyledTypography( - heading({ variant: 'heading3', context: 'markdown' }), - 'h3', - { role: 'heading' } -); -export const H4 = withStyledTypography( - heading({ variant: 'heading4', context: 'markdown' }), - 'h4', - { role: 'heading' } -); -export const H5 = withStyledTypography( - heading({ variant: 'heading5', context: 'markdown' }), - 'h5', - { role: 'heading' } -); -export const H6 = withStyledTypography( - heading({ variant: 'heading6', context: 'markdown' }), - 'h6', - { role: 'heading' } -); diff --git a/packages/components/src/Markdown/Pre/index.css.ts b/packages/components/src/Markdown/Pre/index.css.ts deleted file mode 100644 index cce44f2b..00000000 --- a/packages/components/src/Markdown/Pre/index.css.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { style, globalStyle } from '@vanilla-extract/css'; -import { backgroundColor, foregroundColor, responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; - -const root = style({ - position: 'relative' -}); - -globalStyle(`${root} pre[class*=language-]`, { - textShadow: 'none' -}); - -export default { - button: style({ - top: '0px', - right: '0px', - selectors: { - [`${root} &`]: { - position: 'absolute' - } - } - }), - filename: style([ - backgroundColor({ variant: 'emphasis' }), - foregroundColor({ variant: 'low' }), - responsiveSprinkles({ - paddingLeft: ['x4', 'x4', 'x4', 'x4'] - }) - ]), - pre: style({ - selectors: { - [`${root} &`]: { - fontSize: '85%', - margin: 0, - whiteSpace: 'pre-wrap' - } - } - }), - root -}; diff --git a/packages/components/src/Markdown/Pre/index.tsx b/packages/components/src/Markdown/Pre/index.tsx deleted file mode 100644 index aae3e0d7..00000000 --- a/packages/components/src/Markdown/Pre/index.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React, { useEffect, useState, useRef } from 'react'; -import classnames from 'clsx'; -import Highlight, { defaultProps as defaultPrismProps } from 'prism-react-renderer'; -import type { Language } from 'prism-react-renderer'; -import { Icon } from '../../Icon'; - -import { Button } from '../../Button'; -import { Link } from '../../Link'; -import { ReactLive } from '../../ReactLive'; -import { P2 } from '../../Typography'; -import styles from './index.css'; - -export type CodeBlockPropsType = { - components?: Record; -}; -export type CodeBlockMeta = { - code?: string; - filename?: string; - language?: Language; - live?: boolean; -}; - -export type PreProps = CodeBlockMeta & - React.HTMLProps & { CodeBlockProps?: CodeBlockPropsType }; - -const httpPattern = /^((http[s]?):\/\/)/; - -/** - * Supported syntax: - * - * ```jsx live - *
I will be live editable!
- * ``` - */ -export const Pre: React.FC> = ({ - children, - className, - code: codeProp = '', - CodeBlockProps, - filename, - language: languageProp, - live, - ...rest -}) => { - const preRef = useRef(null); - const [hovered, setHovered] = useState(false); - - const handleMouseEnter = () => { - setHovered(true); - }; - const handleMouseLeave = () => { - setHovered(false); - }; - const handleClickCopy = () => { - if (preRef.current && preRef.current.textContent) { - navigator.clipboard.writeText(preRef.current.textContent); - } - }; - - let code: string | undefined = codeProp.replace(/
/g, '\n'); - let language: Language | undefined = languageProp; - if (children) { - const codeBlock = (children as React.ReactElement<{ className?: string; children: string }>) - .props; - code = codeBlock.children; - language = codeBlock.className?.replace('language-', '') as Language; - } - - if (typeof code !== 'string') { - return null; - } - - let FilenameElement; - if (!filename) { - FilenameElement = null; - } else if (httpPattern.test(filename)) { - const basename = filename.substring(filename.lastIndexOf('/') + 1); - FilenameElement = ( - - {basename} - - ); - } else { - FilenameElement = {filename}; - } - - // If a language other than JSX or TSX is specified, bail out - const isLive = live && (language === 'tsx' || language === 'jsx'); - - return ( -
-
- {FilenameElement} - {hovered && ( - - )} -
- -
- ); -}; - -const CodeBlock = React.memo( - React.forwardRef< - HTMLPreElement | null, - { isLive?: boolean; code: string; components?: Record; language?: Language } - >(({ code, components, isLive = false, language = '' }, ref) => - isLive ? ( - - {code} - - ) : ( -
-        
-      
- ) - ) -); - -const CodeHighlight: React.FC> = - function CodeHighlight({ code, language }) { - const [isClient, setIsClient] = useState(false); - useEffect(() => { - setIsClient(true); - }, []); - - if (!isClient) { - return null; - } - let trimmedCode = code.replace(/\n+$/, ''); - return ( - - {({ tokens, getLineProps, getTokenProps }) => ( - <> - {tokens.map((line, i) => ( - // eslint-disable-next-line react/jsx-key -
- {line.map((token, key) => ( - // eslint-disable-next-line react/jsx-key - - ))} -
- ))} - - )} -
- ); - }; diff --git a/packages/components/src/Markdown/index.tsx b/packages/components/src/Markdown/index.tsx deleted file mode 100644 index 0a9ac220..00000000 --- a/packages/components/src/Markdown/index.tsx +++ /dev/null @@ -1,347 +0,0 @@ -import type { Ref } from 'react'; -import { - action, - amount, - caption, - eyebrow, - heading, - paragraph, - subtitle, - watermark -} from '@jpmorganchase/mosaic-theme'; -import { Icon, IconProps } from '../Icon'; -import { getMarkdownElements } from './markdownElements'; -import { Accordion, AccordionSection, AccordionDetails, AccordionSummary } from '../Accordion'; -import type { AccordionProps } from '../Accordion'; -import { AudioPlayer } from '../AudioPlayer'; -import { Button } from '../Button'; -import { Callout } from '../Callout'; -import type { CalloutProps } from '../Callout'; -import { Card } from '../Card'; -import type { CardProps } from '../Card'; -import { Cards } from '../Cards'; -import type { CardsProps } from '../Cards'; -import { ComponentExample } from '../ComponentExample'; -import type { ComponentExampleProps } from '../ComponentExample'; -import { DataTable } from '../DataTable'; -import type { DataTableProps } from '../DataTable'; -import { Feature, FeatureActions, FeatureContent, FeatureTitle, FeatureEyebrow } from '../Feature'; -import type { FeatureProps } from '../Feature'; -import type { FeaturesProps } from '../Features'; -import { Features } from '../Features'; -import { - FilterDropdown, - FilterPillGroup, - FilterSearch, - FilterSortDropdown, - FilterToolbar -} from '../FilterToolbar'; -import type { - FilterDropdownProps, - FilterPillGroupProps, - FilterSearchProps, - FilterSortDropdownProps, - FilterToolbarProps -} from '../FilterToolbar'; -import { FilterNoResults, FilterResultCount, FilterView } from '../FilterView'; -import type { FilterViewProps, FilterResultCountProps, FilterNoResultsProps } from '../FilterView'; -import { HelpLinks } from '../HelpLinks'; -import type { HelpLinksProps } from '../HelpLinks'; -import { Hero } from '../Hero'; -import { Impact } from '../Impact'; -import type { ImpactProps } from '../Impact'; -import { Impacts } from '../Impacts'; -import type { ImpactsProps } from '../Impacts'; -import { Label } from '../Label'; -import type { LabelProps } from '../Label'; -import { Link } from '../Link'; -import { LinkBase, LinkBaseProps } from '../LinkBase'; -import { LinkText, LinkTextProps } from '../LinkText'; -import { Links } from '../Links'; -import type { LinksProps } from '../Links'; -import { Tag } from '../Tag'; -import type { TagProps } from '../Tag'; -import { EditionFilterView } from '../EditionFilterView'; -import type { EditionTileLinkProps } from '../EditionTileLink'; -import { EditionTileLink } from '../EditionTileLink'; -import { GridBase } from '../GridBase'; -import type { GridBaseProps } from '../GridBase'; -import { Grid } from '../Grid'; -import type { GridProps } from '../Grid'; -import { PageFilterView } from '../PageFilterView'; -import { SecondaryNavbar } from '../SecondaryNavbar'; -import type { SecondaryNavbarProps } from '../SecondaryNavbar'; -import { SectionHeading } from '../SectionHeading'; -import { StickyHeader } from '../StickyHeader'; -import { Story } from '../Story'; -import type { StoryProps } from '../Story'; -import { TabsBase } from '../TabsBase'; -import { Tabs, Tab } from '../Tabs'; -import type { TabsProps } from '../Tabs'; -import { Tiles } from '../Tiles'; -import type { TilesProps } from '../Tiles'; -import { TileBase } from '../TileBase'; -import type { TileBaseProps } from '../TileBase'; -import { TileButton } from '../TileButton'; -import type { TileButtonProps } from '../TileButton'; -import { TileContent } from '../TileContent'; -import type { TileContentProps } from '../TileContent'; -import { TileContentLabel } from '../TileContentLabel'; -import type { TileContentLabelProps } from '../TileContentLabel'; -import { TileLink } from '../TileLink'; -import type { TileLinkProps } from '../TileLink'; -import { VideoPlayer } from '../VideoPlayer'; -import { ViewStack, View } from '../ViewStack'; -import type { ViewStackProps } from '../ViewStack'; -import { withStyledTypography } from '../Typography/withStyledTypography'; -import { withMarkdownSpacing } from './withMarkdownSpacing'; -import { ListItem, OrderedList, UnorderedList } from '../List'; -import type { ListItemProps, OrderedListProps, UnOrderedListProps } from '../List'; -import { LinkButton, LinkButtonProps } from '../LinkButton'; - -export { getMarkdownElements } from './markdownElements'; - -export { withMarkdownSpacing } from './withMarkdownSpacing'; -export * from './Pre'; - -export const getMarkdownComponents = () => { - const markdownElements = getMarkdownElements(); - return { - Accordion: withMarkdownSpacing(Accordion), - AccordionDetails, - AccordionSection, - AccordionSummary, - AudioPlayer, - Button, - Callout: withMarkdownSpacing(Callout), - Card: withMarkdownSpacing(Card), - Cards: withMarkdownSpacing(Cards), - ComponentExample: withMarkdownSpacing(ComponentExample), - DataTable: withMarkdownSpacing< - DataTableProps & { - ref?: Ref; - } - >(DataTable), - EditionFilterView, - EditionTileLink: withMarkdownSpacing(EditionTileLink), - Feature: withMarkdownSpacing(Feature), - FeatureActions, - FeatureContent, - FeatureEyebrow, - FeatureTitle, - Features: withMarkdownSpacing(Features), - FilterView: withMarkdownSpacing>(FilterView), - FilterDropdown: withMarkdownSpacing(FilterDropdown), - FilterToolbar: withMarkdownSpacing(FilterToolbar), - FilterNoResults: withMarkdownSpacing(FilterNoResults), - FilterPillGroup: withMarkdownSpacing(FilterPillGroup), - FilterSortDropdown: withMarkdownSpacing(FilterSortDropdown), - FilterSearch: withMarkdownSpacing(FilterSearch), - FilterResultCount: withMarkdownSpacing(FilterResultCount), - Grid: withMarkdownSpacing(Grid), - GridBase: withMarkdownSpacing(GridBase), - Hero, - HelpLinks: withMarkdownSpacing(HelpLinks), - Icon: withMarkdownSpacing(Icon, 'regular', true), - Impact: withMarkdownSpacing(Impact), - Impacts: withMarkdownSpacing(Impacts), - Label: withMarkdownSpacing(Label), - Link, - LinkBase: withMarkdownSpacing(LinkBase), - LinkButton: withMarkdownSpacing(LinkButton), - LinkText: withMarkdownSpacing(LinkText), - Links: withMarkdownSpacing(Links), - ListItem: withMarkdownSpacing(ListItem), - OrderedList: withMarkdownSpacing(OrderedList), - PageFilterView, - Tag: withMarkdownSpacing(Tag), - SecondaryNavbar: withMarkdownSpacing(SecondaryNavbar), - SectionHeading, - StickyHeader, - Story: withMarkdownSpacing(Story), - Tabs: withMarkdownSpacing(Tabs), - Tab, - TabsBase, - Tiles: withMarkdownSpacing(Tiles), - TileBase: withMarkdownSpacing(TileBase), - TileButton: withMarkdownSpacing(TileButton), - TileContent: withMarkdownSpacing(TileContent), - TileContentLabel: withMarkdownSpacing(TileContentLabel), - TileLink: withMarkdownSpacing(TileLink), - UnorderedList: withMarkdownSpacing(UnorderedList), - ViewStack: withMarkdownSpacing>(ViewStack), - VideoPlayer, - View, - Action1: withStyledTypography( - action({ - variant: 'action1', - context: 'markdown' - }) - ), - Action2: withStyledTypography( - action({ - variant: 'action2', - context: 'markdown' - }) - ), - Action3: withStyledTypography( - action({ - variant: 'action3', - context: 'markdown' - }) - ), - Action4: withStyledTypography( - action({ - variant: 'action4', - context: 'markdown' - }) - ), - Action5: withStyledTypography( - action({ - variant: 'action5', - context: 'markdown' - }) - ), - Action6: withStyledTypography( - action({ - variant: 'action6', - context: 'markdown' - }) - ), - Action7: withStyledTypography( - action({ - variant: 'action7', - context: 'markdown' - }) - ), - Action8: withStyledTypography( - action({ - variant: 'action8', - context: 'markdown' - }) - ), - Caption1: withStyledTypography( - caption({ - variant: 'caption1', - context: 'markdown' - }) - ), - Caption2: withStyledTypography( - caption({ - variant: 'caption2', - context: 'markdown' - }) - ), - Caption3: withStyledTypography( - caption({ - variant: 'caption3', - context: 'markdown' - }) - ), - Caption4: withStyledTypography( - caption({ - variant: 'caption4', - context: 'markdown' - }) - ), - Caption5: withStyledTypography( - caption({ - variant: 'caption5', - context: 'markdown' - }) - ), - Caption6: withStyledTypography( - caption({ - variant: 'caption6', - context: 'markdown' - }) - ), - Hr: markdownElements.hr, - H0: withStyledTypography( - heading({ - variant: 'heading0', - context: 'markdown' - }), - 'h1' - ), - H1: markdownElements.h1, - H2: markdownElements.h2, - H3: markdownElements.h3, - H4: markdownElements.h4, - H5: markdownElements.h5, - H6: markdownElements.h6, - P1: markdownElements.p, - P2: withStyledTypography( - paragraph({ - variant: 'paragraph2', - context: 'markdown' - }) - ), - P3: withStyledTypography( - paragraph({ - variant: 'paragraph3', - context: 'markdown' - }) - ), - P4: withStyledTypography( - paragraph({ - variant: 'paragraph4', - context: 'markdown' - }) - ), - P5: withStyledTypography( - paragraph({ - variant: 'paragraph5', - context: 'markdown' - }) - ), - P6: withStyledTypography( - paragraph({ - variant: 'paragraph6', - context: 'markdown' - }) - ), - Subtitle1: withStyledTypography( - subtitle({ - variant: 'subtitle1', - context: 'markdown' - }) - ), - Subtitle2: withStyledTypography( - subtitle({ - variant: 'subtitle2', - context: 'markdown' - }) - ), - Subtitle3: withStyledTypography( - subtitle({ - variant: 'subtitle3', - context: 'markdown' - }) - ), - Subtitle4: withStyledTypography( - subtitle({ - variant: 'subtitle4', - context: 'markdown' - }) - ), - Subtitle5: withStyledTypography( - subtitle({ - variant: 'subtitle5', - context: 'markdown' - }) - ), - Subtitle6: withStyledTypography( - subtitle({ - variant: 'subtitle6', - context: 'markdown' - }) - ), - Amount: withStyledTypography(amount({ context: 'markdown' })), - Eyebrow: withStyledTypography(eyebrow({ context: 'markdown' })), - Watermark: withStyledTypography(watermark({ context: 'markdown' })), - Emphasis: markdownElements.em, - Strong: markdownElements.strong, - ...markdownElements - }; -}; diff --git a/packages/components/src/Markdown/markdownElements.tsx b/packages/components/src/Markdown/markdownElements.tsx deleted file mode 100644 index fc79f39a..00000000 --- a/packages/components/src/Markdown/markdownElements.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { emphasis, link, paragraph } from '@jpmorganchase/mosaic-theme'; - -import { withAnchorHeading } from './AnchorHeading'; -import { BlockQuote } from './BlockQuote'; -import * as Heading from './Heading'; -import { ListItem, OrderedList, UnorderedList } from '../List'; -import { InlineCode } from './InlineCode'; -import { Link } from './Link'; -import { Pre } from './Pre'; -import { Table } from './Table'; -import { Tbody } from './Tbody'; -import { Thead } from './Thead'; -import { Th } from './Th'; -import { Td } from './Td'; -import { Tr } from './Tr'; -import { withStyledTypography } from '../Typography/withStyledTypography'; -import { withMarkdownSpacing } from './withMarkdownSpacing'; -import { ThematicBreak } from './ThematicBreak'; - -export const getMarkdownElements = () => ({ - a: withMarkdownSpacing(Link, link({ context: 'markdown', variant: 'document' })), - blockquote: withMarkdownSpacing(BlockQuote), - code: withMarkdownSpacing(InlineCode), - ol: withMarkdownSpacing(OrderedList), - ul: withMarkdownSpacing(UnorderedList), - li: withMarkdownSpacing(ListItem, 'none'), - hr: ThematicBreak, - h1: withAnchorHeading(Heading.H1), - h2: withAnchorHeading(Heading.H2), - h3: withAnchorHeading(Heading.H3), - h4: withAnchorHeading(Heading.H4), - h5: withAnchorHeading(Heading.H5), - h6: withAnchorHeading(Heading.H6), - p: withStyledTypography(paragraph({ variant: 'paragraph2', context: 'markdown' })), - pre: withMarkdownSpacing(Pre), - Pre: withMarkdownSpacing(Pre), - inlineCode: withMarkdownSpacing(InlineCode, 'none'), - table: withMarkdownSpacing(Table), - Table: withMarkdownSpacing(Table), - tbody: withMarkdownSpacing(Tbody, 'none'), - Tbody: withMarkdownSpacing(Tbody, 'none'), - thead: withMarkdownSpacing(Thead, 'none'), - Thead: withMarkdownSpacing(Thead, 'none'), - th: withMarkdownSpacing(Th, 'none'), - Th: withMarkdownSpacing(Th, 'none'), - td: withMarkdownSpacing(Td, 'none'), - Td: withMarkdownSpacing(Td, 'none'), - tr: withMarkdownSpacing(Tr, 'none'), - Tr: withMarkdownSpacing(Tr, 'none'), - em: withStyledTypography(emphasis({ variant: 'regular', context: 'markdown' }), 'span'), - strong: withStyledTypography(emphasis({ variant: 'strong', context: 'markdown' }), 'span') -}); diff --git a/packages/components/src/Markdown/withMarkdownSpacing/styles.css.ts b/packages/components/src/Markdown/withMarkdownSpacing/styles.css.ts deleted file mode 100644 index f2ce965b..00000000 --- a/packages/components/src/Markdown/withMarkdownSpacing/styles.css.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; - -export default { - none: responsiveSprinkles({ marginTop: ['none', 'none', 'none', 'none'] }), - regular: responsiveSprinkles({ marginTop: ['x6', 'x6', 'x6', 'x6'] }), - inline: responsiveSprinkles({ paddingLeft: ['x1', 'x1', 'x1', 'x1'] }) -}; diff --git a/packages/components/src/ReactLive/index.tsx b/packages/components/src/ReactLive/index.tsx deleted file mode 100644 index ca23c36b..00000000 --- a/packages/components/src/ReactLive/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useState } from 'react'; -import classnames from 'clsx'; -import { Switch } from '@salt-ds/core'; - -import { ComponentExample } from '../ComponentExample'; -import { getMarkdownComponents } from '../Markdown'; -import { IsomorphicSuspense } from '../IsomorphicSuspense'; -import styles from './styles.css'; - -type ReactLiveProps = { - /** Contents */ - children: string; - /** className determines the type of language flagged in the MD codeblock */ - className: string; - /** Scope (including components) for live editor */ - scope?: Record; -}; - -const LazyPreviewComponent = React.lazy(() => - import('react-live').then(reactLive => ({ - default: ({ className, codeString, scope }) => ( - - ) - })) -); - -function PreviewComponent({ - codeString, - className, - scope = getMarkdownComponents(), - LiveProvider, - LiveEditor, - LiveError, - LivePreview: ReactLivePreview -}) { - const [hidden, setHidden] = useState(true); - - return ( - - `${code.replace(/import(?:["'\s]*([\w*{}\n, ]+)from\s*)["'\s]*([@\w/_-]+)["'\s]*;?/gm, '')}` - } - > -
- - - -
-
- setHidden(!hidden)} /> -
-
- - {hidden ? null : } -
-
- ); -} - -export const ReactLive: React.FC = ({ children: codeString, className, scope }) => ( - - - -); diff --git a/packages/components/src/ReactLive/styles.css.ts b/packages/components/src/ReactLive/styles.css.ts deleted file mode 100644 index f18b2781..00000000 --- a/packages/components/src/ReactLive/styles.css.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { responsiveSprinkles, vars } from '@jpmorganchase/mosaic-theme'; - -const root = style({}); - -export default { - root, - liveError: style([ - { - backgroundColor: 'red', - color: 'white' - }, - responsiveSprinkles({ - marginBottom: ['none', 'none', 'none', 'none'], - padding: ['x4', 'x4', 'x4', 'x4'] - }) - ]), - liveEditor: style([{ backgroundColor: 'black', color: 'white', overflow: 'scroll' }]), - showLiveCodeContainer: style({ - position: 'relative' - }), - showLiveCode: style({ - right: vars.space.horizontal.x4, - bottom: '0px', - position: 'absolute', - lineHeight: 'initial' - }) -}; diff --git a/packages/components/src/SecondaryNavbar/index.tsx b/packages/components/src/SecondaryNavbar/index.tsx index 356f7840..3f99cac3 100644 --- a/packages/components/src/SecondaryNavbar/index.tsx +++ b/packages/components/src/SecondaryNavbar/index.tsx @@ -3,7 +3,7 @@ import classnames from 'clsx'; import { findLastIndex } from 'lodash-es'; import { config } from '@jpmorganchase/mosaic-theme'; -import { TabsBase, TabsMenu, TabMenuItemType, TabsLinkItem } from '../TabsBase'; +import { TabsBase, TabsMenu, TabsLinkItem } from '../TabsBase'; import { canUseDOM } from '../canUseDOM'; import { StickyHeader } from '../StickyHeader'; import styles from './styles.css'; @@ -22,7 +22,7 @@ export interface SecondaryNavbarProps { const getSelectedTabIndex = (items: TabsMenu, itemPath: string): number => findLastIndex(items, (item): boolean => { - if (item.type === TabMenuItemType.MENU) { + if (item.type === 'menu') { return !!item.links.find(link => itemPath.startsWith(link.link)); } return itemPath.startsWith((item as TabsLinkItem).link); diff --git a/packages/components/src/Story/index.tsx b/packages/components/src/Story/index.tsx index 62f25b77..c581ef75 100644 --- a/packages/components/src/Story/index.tsx +++ b/packages/components/src/Story/index.tsx @@ -1,8 +1,8 @@ import React from 'react'; import classnames from 'clsx'; +import { useImageComponent } from '@jpmorganchase/mosaic-store'; import { LinkButton } from '../LinkButton'; -import { useImageComponent } from '../ImageProvider'; import styles from './styles.css'; export interface StoryProps { diff --git a/packages/components/src/Tabs/index.tsx b/packages/components/src/Tabs/index.tsx index 3e1df1d4..d23f06d6 100644 --- a/packages/components/src/Tabs/index.tsx +++ b/packages/components/src/Tabs/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import classnames from 'clsx'; -import { TabsBase, TabsButtonItem, TabMenuItemType } from '../TabsBase'; +import { TabsBase, TabsButtonItem } from '../TabsBase'; import { ViewStack } from '../ViewStack'; import type { ViewProps } from '../ViewStack/View'; import styles from './styles.css'; @@ -36,7 +36,7 @@ export const Tabs: React.FC = ({ className, children }) => { const tabButtonItem: ExtendedTabsButtonItem = { title: tabElement.props.id || `Tab ${childIndex}`, defaultView: Object.prototype.hasOwnProperty.call(child.props, 'defaultView'), - type: TabMenuItemType.BUTTON, + type: 'button', onSelect: (_event, sourceItem) => setCurrentViewId(sourceItem) }; return tabButtonItem; diff --git a/packages/components/src/TabsBase/TabsBase.tsx b/packages/components/src/TabsBase/TabsBase.tsx index bed2448b..ee09ed93 100644 --- a/packages/components/src/TabsBase/TabsBase.tsx +++ b/packages/components/src/TabsBase/TabsBase.tsx @@ -6,11 +6,8 @@ import { TabsLink, TabsLinkItem } from './TabsLink'; import { TabsMenuButton, TabsMenuButtonItem } from './TabsMenuButton'; import styles from './styles.css'; -export enum TabMenuItemType { - MENU = 'menu', - LINK = 'link', - BUTTON = 'button' -} +export type TabMenuItemType = 'menu' | 'link' | 'button'; + export interface TabsBaseProps { /** Additional class name for root class override */ className?: string; @@ -24,10 +21,10 @@ export type TabsMenu = (TabsLinkItem | TabsButtonItem | TabsMenuButtonItem)[]; function TabsItem({ children, item }) { const { type } = item; - if (type === TabMenuItemType.MENU) { + if (type === 'menu') { return {children}; } - if (type === TabMenuItemType.BUTTON) { + if (type === 'button') { return {children}; } return {children}; diff --git a/packages/components/src/TabsBase/TabsButton.tsx b/packages/components/src/TabsBase/TabsButton.tsx index b8408a0f..f09bb145 100644 --- a/packages/components/src/TabsBase/TabsButton.tsx +++ b/packages/components/src/TabsBase/TabsButton.tsx @@ -1,6 +1,5 @@ import React, { FC } from 'react'; -import { TabMenuItemType } from './index'; import styles from './styles.css'; export interface TabsButtonItem { @@ -13,7 +12,7 @@ export interface TabsButtonItem { /** Title of Tab */ title: string; /** Type of Tab */ - type: TabMenuItemType.BUTTON; + type: 'button'; } interface TabButtonProps { diff --git a/packages/components/src/TabsBase/TabsLink.tsx b/packages/components/src/TabsBase/TabsLink.tsx index 098467b2..f52ea92b 100644 --- a/packages/components/src/TabsBase/TabsLink.tsx +++ b/packages/components/src/TabsBase/TabsLink.tsx @@ -1,7 +1,6 @@ import React, { FC } from 'react'; import { Link } from '../Link'; -import { TabMenuItemType } from './index'; import styles from './styles.css'; export interface TabsLinkItem { @@ -14,7 +13,7 @@ export interface TabsLinkItem { /** Title of Tab */ title?: string; /** Type of Tab */ - type: TabMenuItemType.LINK; + type: 'link'; } export interface TabsLinkProps { diff --git a/packages/components/src/TabsBase/TabsMenuButton.tsx b/packages/components/src/TabsBase/TabsMenuButton.tsx index 81630996..56fbe58c 100644 --- a/packages/components/src/TabsBase/TabsMenuButton.tsx +++ b/packages/components/src/TabsBase/TabsMenuButton.tsx @@ -4,7 +4,6 @@ import classnames from 'clsx'; import styles from './tabsMenuButton.css'; import type { TabsLinkItem } from './TabsLink'; -import type { TabMenuItemType } from './index'; import { Icon } from '../Icon'; export interface TabsMenuButtonItem { @@ -19,7 +18,7 @@ export interface TabsMenuButtonItem { /** Title of Tab */ title: string; /** Type of Tab */ - type: TabMenuItemType.MENU; + type: 'menu'; } export interface TabsMenuButtonProps { diff --git a/packages/components/src/TileContent/index.tsx b/packages/components/src/TileContent/index.tsx index f9dc10c4..97ffc82d 100644 --- a/packages/components/src/TileContent/index.tsx +++ b/packages/components/src/TileContent/index.tsx @@ -1,5 +1,6 @@ import React, { FC, forwardRef, ReactNode, Ref } from 'react'; import classnames from 'clsx'; +import { useImageComponent } from '@jpmorganchase/mosaic-store'; import { useTileState } from '../TileBase'; import styles, { @@ -11,7 +12,6 @@ import styles, { titleRecipe } from './styles.css'; import { Size } from '../common/types'; -import { useImageComponent } from '../ImageProvider'; export type TileContentClassesType = { action?: string; diff --git a/packages/components/src/Typography/index.tsx b/packages/components/src/Typography/index.tsx index 99cdf295..87850597 100644 --- a/packages/components/src/Typography/index.tsx +++ b/packages/components/src/Typography/index.tsx @@ -11,6 +11,7 @@ import { } from '@jpmorganchase/mosaic-theme'; import { withStyledTypography } from './withStyledTypography'; +export { withStyledTypography } from './withStyledTypography'; export const Action1 = withStyledTypography(action({ variant: 'action1' })); export const Action2 = withStyledTypography(action({ variant: 'action2' })); diff --git a/packages/components/src/VideoPlayer/index.tsx b/packages/components/src/VideoPlayer/index.tsx index 15b41c34..cd5c759c 100644 --- a/packages/components/src/VideoPlayer/index.tsx +++ b/packages/components/src/VideoPlayer/index.tsx @@ -155,7 +155,7 @@ export const VideoPlayer: React.FC> = className={styles.slider} min={0} max={durationSeconds} - value={timeNowSeconds} + value={[timeNowSeconds]} onChange={handleSliderInput} /> {durationString} diff --git a/packages/components/src/__tests__/ReactLive.test.tsx b/packages/components/src/__tests__/ReactLive.test.tsx deleted file mode 100644 index 5242be62..00000000 --- a/packages/components/src/__tests__/ReactLive.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { ReactLive } from '../ReactLive'; - -describe('GIVEN a ReactLive view', () => - it('THEN it should render code in a live view', async () => { - // arrange - const { container } = render({'

Hello World

'}
); - - // assert - await waitFor(() => { - expect(screen.getByText('Show Live Code')).toBeInTheDocument(); - expect(screen.getByText('Hello World')).toBeInTheDocument(); - }); - // act - const showLiveCode = screen.getByText('Show Live Code'); - fireEvent.click(showLiveCode); - // assert - expect(container.querySelector('pre')).toHaveAttribute('class', 'prism-code language-tsx'); - expect(container.querySelector('pre')).toHaveAttribute('spellcheck', 'false'); - })); diff --git a/packages/components/src/index.tsx b/packages/components/src/index.tsx index 1e3bcb74..42ee16cd 100644 --- a/packages/components/src/index.tsx +++ b/packages/components/src/index.tsx @@ -1,3 +1,5 @@ +'use client'; + export * from './Accordion'; export * from './AudioPlayer'; export * from './Button'; @@ -12,27 +14,22 @@ export * from './Feature'; export * from './Features'; export * from './FilterToolbar'; export * from './FilterView'; -export * from './FormattedContent'; export * from './Grid'; export * from './GridBase'; export * from './HelpLinks'; export * from './Hero'; export * from './Icon'; export * from './IsomorphicSuspense'; -export * from './ImageProvider'; export * from './Impact'; export * from './Impacts'; export * from './Label'; export * from './Link'; export * from './LinkBase'; export * from './LinkButton'; -export * from './LinkProvider'; export * from './Links'; export * from './LinkText'; export * from './List'; -export * from './Markdown'; export * from './PageFilterView'; -export * from './ReactLive'; export * from './SecondaryNavbar'; export * from './SectionHeading'; export * from './StickyHeader'; @@ -40,7 +37,6 @@ export * from './Story'; export * from './Tabs'; export * from './TabsBase'; export * from './Tag'; -export * from './ThemeProvider'; export * from './TileBase'; export * from './TileButton'; export * from './TileContent'; diff --git a/packages/components/src/styles.ts b/packages/components/src/styles.ts index 4dadcd97..3932333d 100644 --- a/packages/components/src/styles.ts +++ b/packages/components/src/styles.ts @@ -25,11 +25,6 @@ import './Impact/styles.css'; import './Link/styles.css'; import './LinkText/styles.css'; import './Links/styles.css'; -import './Markdown/AnchorHeading/index.css'; -import './Markdown/BlockQuote/index.css'; -import './Markdown/Pre/index.css'; -import './Markdown/withMarkdownSpacing/styles.css'; -import './ReactLive/styles.css'; import './SecondaryNavbar/styles.css'; import './SectionHeading/styles.css'; import './StickyHeader/styles.css'; diff --git a/packages/content-editor-plugin/package.json b/packages/content-editor-plugin/package.json index eb6c68af..3e6e1df8 100644 --- a/packages/content-editor-plugin/package.json +++ b/packages/content-editor-plugin/package.json @@ -40,10 +40,11 @@ "dependencies": { "@jpmorganchase/mosaic-components": "^0.1.0-beta.89", "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0", - "@salt-ds/icons": "^1.12.1", - "@salt-ds/lab": "1.0.0-alpha.50", - "@salt-ds/theme": "^1.19.0", + "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-icons": "^0.1.0-beta.89", + "@vanilla-extract/sprinkles": "^1.3.0", + "@salt-ds/core": "^1.37.1", + "@salt-ds/lab": "1.0.0-alpha.54", "@floating-ui/react": "^0.26.6", "@lexical/code": "^0.17.1", "@lexical/link": "^0.17.1", @@ -55,18 +56,19 @@ "@lexical/utils": "^0.17.1", "@lexical/markdown": "^0.17.1", "@vanilla-extract/css": "^1.6.0", + "@vanilla-extract/recipes": "^0.2.1", + "client-only": "^0.0.1", "clsx": "^2.0.0", - "gray-matter": "^4.0.3", "lexical": "^0.17.1", "react-split": "^2.0.14", - "lodash-es": "^4.17.21", "md5": "*", + "use-debounce": "^10.0.0", "yup": "^0.32.9", - "zustand": "^4.1.1" + "zustand": "^4.5.5" }, "peerDependencies": { "@types/react": "^18.3.12", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/content-editor-plugin/src/LazyEditor.tsx b/packages/content-editor-plugin/src/LazyEditor.tsx new file mode 100644 index 00000000..545b2270 --- /dev/null +++ b/packages/content-editor-plugin/src/LazyEditor.tsx @@ -0,0 +1,11 @@ +import { Suspense, lazy } from 'react'; + +import type { EditorProps } from './components/Editor'; + +const LazyEditor = lazy(() => import('./components/Editor')); + +export const Editor = (props: EditorProps) => ( + Loading Editor...}> + + +); diff --git a/packages/content-editor-plugin/src/components/ContentPreview.tsx b/packages/content-editor-plugin/src/components/ContentPreview.tsx new file mode 100644 index 00000000..b765c4a2 --- /dev/null +++ b/packages/content-editor-plugin/src/components/ContentPreview.tsx @@ -0,0 +1,7 @@ +import useContentEditor from '../store/index'; + +export const ContentPreview = ({ children }) => { + const { previewContent } = useContentEditor(); + + return previewContent || children; +}; diff --git a/packages/content-editor-plugin/src/components/Editor.tsx b/packages/content-editor-plugin/src/components/Editor.tsx index dfdd7895..9d9f00ea 100644 --- a/packages/content-editor-plugin/src/components/Editor.tsx +++ b/packages/content-editor-plugin/src/components/Editor.tsx @@ -1,6 +1,5 @@ -import { ComponentType, FC, useRef, useState } from 'react'; +import { FC, ReactNode, useRef, useState } from 'react'; import classnames from 'clsx'; -import matter from 'gray-matter'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { ListPlugin } from '@lexical/react/LexicalListPlugin'; import { TablePlugin } from '@lexical/react/LexicalTablePlugin'; @@ -13,8 +12,8 @@ import { $convertFromMarkdownString } from '@lexical/markdown'; import transformers from '../transformers'; import ContentEditor from './ContentEditor'; import { nodes } from '../nodes'; -import { useEditorUser, usePreviewContent } from '../store'; -import { PreviewPlugin } from '../plugins/PreviewPlugin'; +import { useEditorUser } from '../store'; +import { PreviewPlugin, PreviewPluginProps } from '../plugins/PreviewPlugin'; import styles from './Editor.css'; import Toolbar from './Toolbar/Toolbar'; import theme from '../theme'; @@ -27,6 +26,7 @@ import { ScrollableSection } from './ScrollableSection/ScrollableSection'; import HorizontalRulePlugin from '../plugins/HorizontalRulePlugin'; import { FloatingToolbarPlugin } from '../plugins/FloatingToolbarPlugin'; import { TableActionMenuPlugin } from '../plugins/TableActionMenuPlugin'; +import { ContentPreview } from './ContentPreview'; function onError(error: Error) { console.error(error); @@ -39,18 +39,11 @@ const initialConfig = { theme }; -interface PreviewComponentProps { - source: any; - meta?: any; - components: any; -} - -interface EditorProps extends PreviewComponentProps { +export interface EditorProps extends PreviewPluginProps { content: string; - PreviewComponent?: ComponentType; - previewUrl?: string; persistUrl?: string; user?: any; + children: ReactNode; } const gutter = () => { @@ -59,20 +52,10 @@ const gutter = () => { return gutter; }; -const Editor: FC = ({ - components, - content, - persistUrl, - PreviewComponent, - previewUrl, - source, - user -}) => { - const previewContent = usePreviewContent() || source; +const Editor: FC = ({ content, persistUrl, user, meta, children, onChange }) => { const { setUser } = useEditorUser(); const [focused, setFocused] = useState(false); const containerRef = useRef(null); - const { data: meta, content: markdown } = matter(content); const handleEditorFocus = () => { setFocused(true); @@ -87,7 +70,7 @@ const Editor: FC = ({ $convertFromMarkdownString(markdown, transformers) + editorState: () => $convertFromMarkdownString(content, transformers) }} >
@@ -119,9 +102,7 @@ const Editor: FC = ({ - {PreviewComponent && ( - - )} + {children} @@ -129,7 +110,7 @@ const Editor: FC = ({ - {previewUrl ? : null} + {onChange ? : null} diff --git a/packages/content-editor-plugin/src/index.ts b/packages/content-editor-plugin/src/index.ts index 566ff0ce..f56bbbaf 100644 --- a/packages/content-editor-plugin/src/index.ts +++ b/packages/content-editor-plugin/src/index.ts @@ -1,5 +1,7 @@ -import Editor from './components/Editor'; +import 'client-only'; + +import { Editor } from './LazyEditor'; import { EditorControls } from './components/EditorControls'; -import { default as useContentEditor, usePageState } from './store'; +import useContentEditor, { usePageState } from './store'; export { Editor, EditorControls, usePageState, useContentEditor }; diff --git a/packages/content-editor-plugin/src/plugins/PreviewPlugin.tsx b/packages/content-editor-plugin/src/plugins/PreviewPlugin.tsx index f4ea4ec8..67aa80ba 100644 --- a/packages/content-editor-plugin/src/plugins/PreviewPlugin.tsx +++ b/packages/content-editor-plugin/src/plugins/PreviewPlugin.tsx @@ -1,70 +1,55 @@ +import React from 'react'; import { $convertToMarkdownString } from '@lexical/markdown'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import type { EditorState } from 'lexical'; -import { debounce } from 'lodash-es'; +import { useDebouncedCallback } from 'use-debounce'; import transformers from '../transformers'; -import { useContentEditor } from '../index'; +import useContentEditor from '../store/index'; -interface SourceResponse { - source: { compiledSource: string; frontmatter: any; scope: any }; +export interface SourceResponse { + result?: React.ReactNode; error?: string; - exception?: string; } -async function fetchSource(previewUrl: string, markdown: string) { - const response = await fetch(previewUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ mode: 'markdown', text: markdown }) - }); - - const data = (await response.json()) as SourceResponse; - return data; +export interface PreviewPluginProps { + onChange: ({ + source, + data + }: { + source: string; + data: Record; + }) => Promise; + meta: Record; } function usePreview(onContentChange: (markdown: string) => void) { - const handleContentChange = debounce(onContentChange, 250, { maxWait: 500 }); - - const onChange = (editorState: EditorState) => { + const onChange = useDebouncedCallback((editorState: EditorState) => { editorState.read(() => { const markdown = $convertToMarkdownString(transformers); if (markdown) { - handleContentChange(markdown); + onContentChange(markdown); } }); - }; + }, 400); return { onChange }; } -interface PreviewPluginProps { - previewUrl: string; -} - -export const PreviewPlugin = ({ previewUrl }: PreviewPluginProps) => { - const { setErrorMessage, setPreviewContent } = useContentEditor(); - +export const PreviewPlugin = ({ onChange: onChangeProp, meta }: PreviewPluginProps) => { + const { setPreviewContent, setErrorMessage } = useContentEditor(); const handleContentChange = async (content: string) => { - try { - if (content) { - const data = await fetchSource(previewUrl, content); - if (!data.error && data?.source) { - setPreviewContent(data.source); - } else { - setErrorMessage(`${data.error?.toUpperCase()}: ${data?.exception}`); - } - } - } catch (e) { - if (e instanceof Error) { - setErrorMessage(`MDX Error: ${e.message}`); + if (content) { + const previewResponse = await onChangeProp({ source: content, data: meta }); + + if (previewResponse.error) { + setErrorMessage(`MDX Error: ${previewResponse.error}`); + } else { + setPreviewContent(previewResponse.result); } } }; const { onChange } = usePreview(handleContentChange); - return ; }; diff --git a/packages/core/package.json b/packages/core/package.json index 69b97c9f..d44d846c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,9 +32,6 @@ "url": "git@github.com:jpmorganchase/mosaic.git", "directory": "packages/core" }, - "devDependencies": { - "cors": "^2.8.5" - }, "dependencies": { "gray-matter": "^4.0.3", "@jpmorganchase/mosaic-plugins": "^0.1.0-beta.89", diff --git a/packages/icons/package.json b/packages/icons/package.json index 64f38537..523f180b 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -40,10 +40,10 @@ }, "dependencies": { "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0" + "@salt-ds/icons": "^1.12.1" }, "peerDependencies": { "@types/react": "^18.2.46", - "react": "^18.2.0" + "react": "^18.3.0" } } diff --git a/packages/layouts/package.json b/packages/layouts/package.json index 7613a4ee..648e1733 100644 --- a/packages/layouts/package.json +++ b/packages/layouts/package.json @@ -45,17 +45,14 @@ "@jpmorganchase/mosaic-site-components": "^0.1.0-beta.89", "@jpmorganchase/mosaic-store": "^0.1.0-beta.89", "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0", + "@salt-ds/core": "^1.37.1", "@vanilla-extract/css": "^1.6.0", "@vanilla-extract/sprinkles": "^1.3.0", - "clsx": "^2.0.0", - "lodash-es": "^4.17.21", - "next": "^14.0.0", - "react-transition-group": "^4.4.5" + "clsx": "^2.0.0" }, "peerDependencies": { "@types/react": "^18.3.12", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/layouts/src/Fade.tsx b/packages/layouts/src/Fade.tsx deleted file mode 100644 index 69ec0119..00000000 --- a/packages/layouts/src/Fade.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useRef } from 'react'; -import { Transition } from 'react-transition-group'; - -const transitionStyles = { - entering: { opacity: 1 }, - entered: { opacity: 1 }, - exiting: { opacity: 0 }, - exited: { opacity: 0 } -}; - -type FadeProps = { - children: React.ReactElement; - duration?: { - enter: number; - exit: number; - }; - in: boolean; - style?: React.CSSProperties; -}; - -export const Fade: React.FC = ({ children, duration, in: inProp, style }) => { - const nodeRef = useRef(null); - const defaultStyle = { - transition: `opacity ${duration?.enter}ms ease-in-out`, - opacity: 0 - }; - return ( - - {state => ( -
- {children} -
- )} -
- ); -}; diff --git a/packages/layouts/src/LayoutBase/index.tsx b/packages/layouts/src/LayoutBase/index.tsx index 618fff95..3bc33ac3 100644 --- a/packages/layouts/src/LayoutBase/index.tsx +++ b/packages/layouts/src/LayoutBase/index.tsx @@ -1,9 +1,5 @@ import React from 'react'; -import { Spinner } from '@salt-ds/core'; import classnames from 'clsx'; - -import { useIsLoading } from '../hooks/useIsLoading'; -import { Fade } from '../Fade'; import styles from './styles.css'; export const LayoutBase = ({ @@ -14,21 +10,9 @@ export const LayoutBase = ({ Header?: React.ReactNode; className?: string; children?: React.ReactNode; -}) => { - // Add a delay before showing loading state, so loading screen doesn't appear if page loads quickly - const isLoading = useIsLoading({ loadingDelay: 50 }); - return ( -
-
{Header}
-
- -
-
- -
- - {children} -
-
- ); -}; +}) => ( +
+
{Header}
+
{children}
+
+); diff --git a/packages/layouts/src/LayoutColumns/index.tsx b/packages/layouts/src/LayoutColumns/index.tsx index db94a008..0383d68e 100644 --- a/packages/layouts/src/LayoutColumns/index.tsx +++ b/packages/layouts/src/LayoutColumns/index.tsx @@ -1,20 +1,9 @@ import React from 'react'; -import { Drawer, Sidebar, TriggerElementProps } from '@jpmorganchase/mosaic-site-components'; -import { Button, Icon, useBreakpoint } from '@jpmorganchase/mosaic-components'; +import { Sidebar } from '@jpmorganchase/mosaic-site-components'; import styles from './styles.css'; -const TriggerElement: React.FC = ({ open, onClick: handleClick }) => ( - -); - export const LayoutColumns = ({ PrimarySidebar, SecondarySidebar, @@ -25,26 +14,15 @@ export const LayoutColumns = ({ SecondarySidebar?: React.ReactNode; Footer?: React.ReactNode; children?: React.ReactNode; -}) => { - const breakpoint = useBreakpoint(); - const showDrawer = breakpoint === 'mobile' || breakpoint == 'tablet'; - return ( -
-
- {showDrawer && PrimarySidebar && ( - - {PrimarySidebar} - - )} - {!showDrawer && PrimarySidebar && {PrimarySidebar}} -
-
- {children} - {Footer && Footer} -
-
- {SecondarySidebar && !showDrawer ? {SecondarySidebar} : null} -
+}) => ( +
+
{PrimarySidebar && {PrimarySidebar}}
+
+ {children} + {Footer && Footer}
- ); -}; +
+ {SecondarySidebar ? {SecondarySidebar} : null} +
+
+); diff --git a/packages/layouts/src/LayoutColumns/styles.css.ts b/packages/layouts/src/LayoutColumns/styles.css.ts index d25ea2e8..832ff289 100644 --- a/packages/layouts/src/LayoutColumns/styles.css.ts +++ b/packages/layouts/src/LayoutColumns/styles.css.ts @@ -29,6 +29,10 @@ const rootGridProperties = defineProperties({ const rootGridSprinkles = createSprinkles(rootGridProperties); +const responsiveGridArea = responsiveSprinkles({ + display: ['none', 'none', 'flex', 'flex'] +}); + const styles = { root: style([ style({ @@ -66,18 +70,23 @@ const styles = { marginRight: 'auto', marginLeft: 'auto' }), - sidebar: style({ - gridArea: 'layout-column-sidebar', - position: 'sticky', - top: `${config.appHeader.height}px`, - display: 'flex' - }), - toc: style({ - gridArea: 'layout-column-toc', - position: 'sticky', - top: `${config.appHeader.height}px`, - maxWidth: '500px' - }), + sidebar: style([ + style({ + gridArea: 'layout-column-sidebar', + position: 'sticky', + top: `${config.appHeader.height}px` + }), + responsiveGridArea + ]), + toc: style([ + style({ + gridArea: 'layout-column-toc', + position: 'sticky', + top: `${config.appHeader.height}px`, + maxWidth: '500px' + }), + responsiveGridArea + ]), toggleButton: style([ { diff --git a/packages/layouts/src/LayoutConfigProvider/index.tsx b/packages/layouts/src/LayoutConfigProvider/index.tsx deleted file mode 100644 index 3edfd4cc..00000000 --- a/packages/layouts/src/LayoutConfigProvider/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { ReactNode } from 'react'; - -let LayoutContext; - -export function LayoutConfigProvider({ - children, - config -}: { - children?: ReactNode; - config: unknown; -}) { - LayoutContext = LayoutContext || React.createContext(config); - return children; -} - -export function useLayoutConfig() { - if (!LayoutContext) { - throw new Error('Trying to use `useLayoutConfig` outside of a `LayoutConfigContext`.'); - } - - return React.useContext(LayoutContext); -} diff --git a/packages/layouts/src/LayoutFullWidth/index.tsx b/packages/layouts/src/LayoutFullWidth/index.tsx index 8186b360..49eb64c4 100644 --- a/packages/layouts/src/LayoutFullWidth/index.tsx +++ b/packages/layouts/src/LayoutFullWidth/index.tsx @@ -4,7 +4,7 @@ import classnames from 'clsx'; import styles from './styles.css'; export interface LayoutFullWidthProps { - Footer?: React.ReactElement; + Footer?: React.ReactNode; children: React.ReactNode; className?: string; } @@ -15,7 +15,7 @@ export const LayoutFullWidth: React.FC = ({ className }) => (
-
{children}
+
{children}
{Footer && Footer}
); diff --git a/packages/layouts/src/LayoutProvider.tsx b/packages/layouts/src/LayoutProvider.tsx deleted file mode 100644 index e8f1b19b..00000000 --- a/packages/layouts/src/LayoutProvider.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { FC, ReactNode } from 'react'; -import { useLayout } from '@jpmorganchase/mosaic-store'; -import { usePageState } from '@jpmorganchase/mosaic-content-editor-plugin'; - -import type { LayoutProps } from './types'; -import * as layouts from './layouts'; - -export type LayoutProviderProps = { - layoutComponents?: { - [name: string]: React.FC | undefined; - }; - LayoutProps?: LayoutProps; - children: ReactNode; - defaultLayout?: string; -}; - -export const LayoutProvider: FC = ({ - children, - layoutComponents, - LayoutProps = {}, - defaultLayout = 'FullWidth' -}) => { - const { layout: layoutInStore = defaultLayout } = useLayout(); - const { pageState } = usePageState(); - const layout = pageState !== 'VIEW' ? 'EditLayout' : layoutInStore; - - let LayoutComponent: FC | undefined = layoutComponents?.[layout] as FC; - if (!LayoutComponent) { - console.error(`Layout ${layout} is not supported, defaulting to ${defaultLayout}`); - LayoutComponent = layouts[defaultLayout]; - } - return LayoutComponent ? ( - {children} - ) : ( - <>children - ); -}; diff --git a/packages/layouts/src/hooks/useIsLoading.ts b/packages/layouts/src/hooks/useIsLoading.ts deleted file mode 100644 index 3f168bca..00000000 --- a/packages/layouts/src/hooks/useIsLoading.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useState, useEffect } from 'react'; -import { useRouter } from 'next/router'; -import { debounce } from 'lodash-es'; - -// Unexported type from next/dist/shared/lib/mitt.d.ts -declare type Handler = (...evts: any[]) => void; - -export function useIsLoading({ loadingDelay }: { loadingDelay?: number } = {}) { - const router = useRouter(); - const [isLoading, setIsLoading] = useState(false); - - useEffect(() => { - const start = debounce(() => { - setIsLoading(true); - }, loadingDelay); - - const handleRouteChangeStart: Handler = (_url, { shallow }) => { - if (!shallow) { - start(); - } - }; - - const handleRouteChangeDone: Handler = (_url, { shallow }) => { - if (!shallow) { - setIsLoading(false); - } - }; - - router.events.on('routeChangeStart', handleRouteChangeStart); - router.events.on('routeChangeError', handleRouteChangeDone); - router.events.on('routeChangeComplete', handleRouteChangeDone); - - return () => { - router.events.off('routeChangeStart', handleRouteChangeStart); - router.events.off('routeChangeError', handleRouteChangeDone); - router.events.off('routeChangeComplete', handleRouteChangeDone); - }; - }, [router]); - - return isLoading; -} diff --git a/packages/layouts/src/index.ts b/packages/layouts/src/index.ts index f30ff3ca..399b7413 100644 --- a/packages/layouts/src/index.ts +++ b/packages/layouts/src/index.ts @@ -1,8 +1,5 @@ export * from './LayoutBase'; export * from './LayoutColumns'; export * from './LayoutFullWidth'; -export * from './hooks/useIsLoading'; -export * from './LayoutConfigProvider'; -export * from './LayoutProvider'; export * as layouts from './layouts'; diff --git a/packages/layouts/src/layouts/Detail/DetailContentOnly.tsx b/packages/layouts/src/layouts/Detail/DetailContentOnly.tsx index ca5a25c7..cb4fbbbf 100644 --- a/packages/layouts/src/layouts/Detail/DetailContentOnly.tsx +++ b/packages/layouts/src/layouts/Detail/DetailContentOnly.tsx @@ -1,15 +1,8 @@ import React from 'react'; -import { AppHeader, Breadcrumbs, Footer } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutFullWidth } from '../../LayoutFullWidth'; import type { LayoutProps } from '../../types'; -export const DetailContentOnly: React.FC = ({ FooterProps, children }) => ( - }> - }> - - {children} - - +export const DetailContentOnly: React.FC = ({ children, FooterComponent }) => ( + {children} ); diff --git a/packages/layouts/src/layouts/Detail/DetailHighlight.tsx b/packages/layouts/src/layouts/Detail/DetailHighlight.tsx index 78af1eec..41774fac 100644 --- a/packages/layouts/src/layouts/Detail/DetailHighlight.tsx +++ b/packages/layouts/src/layouts/Detail/DetailHighlight.tsx @@ -1,24 +1,18 @@ import React from 'react'; -import { - AppHeader, - Breadcrumbs, - Footer, - TableOfContents -} from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutColumns } from '../../LayoutColumns'; import type { LayoutProps } from '../../types'; -export const DetailHighlight: React.FC = ({ FooterProps, children }) => ( - }> - } - Footer={
} - > - - {children} - - +export const DetailHighlight: React.FC = ({ + SecondarySidebarComponent, + FooterComponent, + children +}) => ( + + {children} + ); diff --git a/packages/layouts/src/layouts/Detail/DetailOverview.tsx b/packages/layouts/src/layouts/Detail/DetailOverview.tsx index 73db7abb..f0ec1928 100644 --- a/packages/layouts/src/layouts/Detail/DetailOverview.tsx +++ b/packages/layouts/src/layouts/Detail/DetailOverview.tsx @@ -1,15 +1,7 @@ import React from 'react'; import { HelpLinks } from '@jpmorganchase/mosaic-components'; -import { - AppHeader, - BackLink, - Breadcrumbs, - DocPaginator, - Footer, - PageNavigation -} from '@jpmorganchase/mosaic-site-components'; +import { BackLink } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutColumns } from '../../LayoutColumns'; import type { LayoutProps } from '../../types'; import styles from './styles.css'; @@ -17,7 +9,9 @@ import styles from './styles.css'; export const DetailOverview: React.FC = ({ BackLinkProps, SidebarProps, - FooterProps, + FooterComponent, + DocPaginatorComponent, + PrimarySidebarComponent, children }) => { const Sidebar = ( @@ -27,21 +21,14 @@ export const DetailOverview: React.FC = ({ )} - + {PrimarySidebarComponent} {SidebarProps?.helpLinks && } ); return ( - }> - } - > - - {children} - - - + + {children} + {DocPaginatorComponent} + ); }; diff --git a/packages/layouts/src/layouts/Detail/DetailTechnical.tsx b/packages/layouts/src/layouts/Detail/DetailTechnical.tsx index 1c799f95..33328339 100644 --- a/packages/layouts/src/layouts/Detail/DetailTechnical.tsx +++ b/packages/layouts/src/layouts/Detail/DetailTechnical.tsx @@ -1,28 +1,20 @@ import React from 'react'; import { HelpLinks } from '@jpmorganchase/mosaic-components'; -import { - AppHeader, - DocPaginator, - BackLink, - Breadcrumbs, - Footer, - TableOfContents, - PageNavigation -} from '@jpmorganchase/mosaic-site-components'; +import { BackLink } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutColumns } from '../../LayoutColumns'; import type { LayoutProps } from '../../types'; import styles from './styles.css'; export const DetailTechnical: React.FC = ({ BackLinkProps, - FooterProps, + FooterComponent, + DocPaginatorComponent, + SecondarySidebarComponent, + PrimarySidebarComponent, SidebarProps, children }) => { - const Header = ; - const PrimarySidebar = ( <> {BackLinkProps && ( @@ -30,24 +22,19 @@ export const DetailTechnical: React.FC = ({ )} - + {PrimarySidebarComponent} {SidebarProps?.helpLinks && } ); - const SecondarySidebar = ; - return ( - - } - > - - {children} - - - + + {children} + {DocPaginatorComponent} + ); }; diff --git a/packages/layouts/src/layouts/Edit/index.tsx b/packages/layouts/src/layouts/Edit/index.tsx index fca7f7d1..35c8bb8a 100644 --- a/packages/layouts/src/layouts/Edit/index.tsx +++ b/packages/layouts/src/layouts/Edit/index.tsx @@ -1,35 +1,13 @@ -import React, { useEffect } from 'react'; -import { useRouter } from 'next/router'; -import { useContentEditor } from '@jpmorganchase/mosaic-content-editor-plugin'; -import { AppHeader } from '@jpmorganchase/mosaic-site-components'; +import type { ReactNode } from 'react'; -import { LayoutBase } from '../../LayoutBase'; -import type { LayoutProps } from '../../types'; import styles from './styles.css'; -export const EditLayout: React.FC = ({ children }) => { - const router = useRouter(); - const { pageState, stopEditing } = useContentEditor(); +export interface EditLayoutProps { + children: ReactNode; +} - useEffect(() => { - const handleRouteChange = () => { - if (pageState === 'EDIT') { - stopEditing(); - } - }; - - router.events.on('routeChangeStart', handleRouteChange); - - return () => { - router.events.off('routeChangeStart', handleRouteChange); - }; - }, [pageState, router.events, stopEditing]); - - return ( - }> -
- {children} -
-
- ); -}; +export const Edit = ({ children }: EditLayoutProps) => ( +
+ {children} +
+); diff --git a/packages/layouts/src/layouts/FullWidth/index.tsx b/packages/layouts/src/layouts/FullWidth/index.tsx index 06583185..5eee18b6 100644 --- a/packages/layouts/src/layouts/FullWidth/index.tsx +++ b/packages/layouts/src/layouts/FullWidth/index.tsx @@ -1,12 +1,8 @@ import React from 'react'; -import { AppHeader } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutFullWidth } from '../../LayoutFullWidth'; import type { LayoutProps } from '../../types'; export const FullWidth: React.FC = ({ children }) => ( - }> - {children} - + {children} ); diff --git a/packages/layouts/src/layouts/Landing/index.tsx b/packages/layouts/src/layouts/Landing/index.tsx index d68d01c2..efd9896f 100644 --- a/packages/layouts/src/layouts/Landing/index.tsx +++ b/packages/layouts/src/layouts/Landing/index.tsx @@ -1,12 +1,8 @@ import React from 'react'; -import { AppHeader, Footer } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutFullWidth } from '../../LayoutFullWidth'; import type { LayoutProps } from '../../types'; -export const Landing: React.FC = ({ FooterProps, children }) => ( - }> - }>{children} - +export const Landing: React.FC = ({ FooterComponent, children }) => ( + {children} ); diff --git a/packages/layouts/src/layouts/Newsletter/index.tsx b/packages/layouts/src/layouts/Newsletter/index.tsx index afc2b1fb..eb58137c 100644 --- a/packages/layouts/src/layouts/Newsletter/index.tsx +++ b/packages/layouts/src/layouts/Newsletter/index.tsx @@ -1,29 +1,27 @@ import React from 'react'; -import { AppHeader, DocPaginator, BackLink, Footer } from '@jpmorganchase/mosaic-site-components'; +import { BackLink } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutColumns } from '../../LayoutColumns'; import type { LayoutProps } from '../../types'; import styles from '../Detail/styles.css'; export interface NewsletterProps extends LayoutProps {} -export const Newsletter: React.FC = ({ children, BackLinkProps, FooterProps }) => { +export const Newsletter: React.FC = ({ + children, + BackLinkProps, + FooterComponent, + DocPaginatorComponent +}) => { const Sidebar = BackLinkProps?.link ? (
) : null; return ( - }> - } - SecondarySidebar={null} - > - {children} - - - + + {children} + {DocPaginatorComponent} + ); }; diff --git a/packages/layouts/src/layouts/Product/ProductDiscover.tsx b/packages/layouts/src/layouts/Product/ProductDiscover.tsx index 1d91c9f5..e443b952 100644 --- a/packages/layouts/src/layouts/Product/ProductDiscover.tsx +++ b/packages/layouts/src/layouts/Product/ProductDiscover.tsx @@ -1,15 +1,8 @@ import React from 'react'; -import { AppHeader, Breadcrumbs, Footer } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutFullWidth } from '../../LayoutFullWidth'; import type { LayoutProps } from '../../types'; -export const ProductDiscover: React.FC = ({ FooterProps, children }) => ( - }> - }> - - {children} - - +export const ProductDiscover: React.FC = ({ FooterComponent, children }) => ( + {children} ); diff --git a/packages/layouts/src/layouts/Product/ProductPreview.tsx b/packages/layouts/src/layouts/Product/ProductPreview.tsx index 3bed5ab7..ff67d82c 100644 --- a/packages/layouts/src/layouts/Product/ProductPreview.tsx +++ b/packages/layouts/src/layouts/Product/ProductPreview.tsx @@ -1,12 +1,8 @@ import React from 'react'; -import { AppHeader, Footer } from '@jpmorganchase/mosaic-site-components'; -import { LayoutBase } from '../../LayoutBase'; import { LayoutFullWidth } from '../../LayoutFullWidth'; import type { LayoutProps } from '../../types'; -export const ProductPreview: React.FC = ({ FooterProps, children }) => ( - }> - }>{children} - +export const ProductPreview: React.FC = ({ FooterComponent, children }) => ( + {children} ); diff --git a/packages/layouts/src/layouts/PythonDoc/index.tsx b/packages/layouts/src/layouts/PythonDoc/index.tsx deleted file mode 100644 index 9b9c528a..00000000 --- a/packages/layouts/src/layouts/PythonDoc/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import classnames from 'clsx'; -import { HelpLinks, Hero } from '@jpmorganchase/mosaic-components'; -import { - AppHeader, - BackLink, - Breadcrumbs, - DocPaginator, - Footer, - TableOfContents, - PageNavigation -} from '@jpmorganchase/mosaic-site-components'; - -import { LayoutBase } from '../../LayoutBase'; -import { LayoutColumns } from '../../LayoutColumns'; -import type { LayoutProps } from '../../types'; -import styles from './styles.css'; - -// TODO add image support to Hero via Mosaic plugin or provide a custom Layout -const APIHero = ({ description, name, pageType, releaseDate, title, version }) => ( - -); - -export const PythonDoc: React.FC = ({ - BackLinkProps, - FooterProps, - SidebarProps, - ToCProps, - children, - meta -}) => { - // Format the toc so that function calls do not have any arguments - // init(arg1, arg2) becomes init() - const formattedTableOfContentsItems = ToCProps?.items?.map(({ text, ...rest }) => ({ - ...rest, - text: text.replace(/\s*\(.*?\).*/g, '() ') - })); - - const PrimarySidebar = ( - <> - {BackLinkProps && ( -
- -
- )} - - {SidebarProps?.helpLinks && } - - ); - - const SecondarySidebar = ; - - return ( - }> - } - > -
- - {meta?.data?.pageType === 'root' || meta?.data?.pageType === 'index' ? ( - - ) : null} - {children} - -
-
-
- ); -}; diff --git a/packages/layouts/src/layouts/PythonDoc/styles.css.ts b/packages/layouts/src/layouts/PythonDoc/styles.css.ts deleted file mode 100644 index 3b51cba6..00000000 --- a/packages/layouts/src/layouts/PythonDoc/styles.css.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; - -export default { - root: style({ - width: '100%', - zIndex: 1, - display: 'flex' - }), - - main: style({ - width: '100%', - minWidth: 0, - display: 'flex', - justifyContent: 'center' - }), - - locked: style({ - width: '100%', - minWidth: 0, - display: 'flex', - justifyContent: 'center', - position: 'relative', - overflow: 'hidden' - }), - - columnWrapper: style([ - style({ - display: 'flex', - width: '100%', - maxWidth: '1128px' - }), - responsiveSprinkles({ - marginX: ['x8', 'x8', 'x8', 'x8'] - }) - ]), - - contentScrollMargin: style({}), - - contentColumn: style([ - style({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'stretch', - zIndex: 2, - width: '100%' - }), - responsiveSprinkles({ - paddingTop: ['x10', 'x10', 'x20', 'x20'], - marginRight: ['none', 'none', 'none', 'x6'] - }) - ]), - - contentBody: style([ - style({ - flexGrow: 1 - }) - ]), - - sidebarHeader: style({ flexShrink: 0 }), - - wrapper: style({ - width: '100%', - overflow: 'hidden', - position: 'relative' - }) -}; diff --git a/packages/layouts/src/layouts/TypeDoc/ExportsSidebar/index.tsx b/packages/layouts/src/layouts/TypeDoc/ExportsSidebar/index.tsx deleted file mode 100644 index 0e23180d..00000000 --- a/packages/layouts/src/layouts/TypeDoc/ExportsSidebar/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Caption1, Caption2, Link } from '@jpmorganchase/mosaic-components'; - -import styles from './styles.css'; - -export type Item = { level: number; id: string; text: string }; - -function TableOfContentsItem({ item }) { - return ( -
  • - - {item.text} - -
  • - ); -} - -export function ExportsSidebar({ items }) { - if (!items) { - throw new Error('No `items` specified for Exports Sidebar TOC.'); - } - if (!items.length) { - return null; - } - - return ( - - ); -} diff --git a/packages/layouts/src/layouts/TypeDoc/ExportsSidebar/styles.css.ts b/packages/layouts/src/layouts/TypeDoc/ExportsSidebar/styles.css.ts deleted file mode 100644 index 85cc65cd..00000000 --- a/packages/layouts/src/layouts/TypeDoc/ExportsSidebar/styles.css.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { link, navigableBorder, responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; - -export default { - root: style({ - position: 'sticky' - }), - list: style([ - style({ - listStyle: 'none' - }), - responsiveSprinkles({ - marginTop: ['x4', 'x4', 'x4', 'x4'], - marginBottom: ['x6', 'x6', 'x6', 'x6'], - padding: ['none', 'none', 'none', 'none'] - }) - ]), - item: style([ - { - cursor: 'pointer', - position: 'relative', - display: 'block' - }, - navigableBorder({ - variant: 'unselected', - borderLeftWidth: 'medium' - }), - navigableBorder({ - variant: 'hover', - borderLeftWidth: 'medium' - }), - navigableBorder({ - variant: 'selected', - borderLeftWidth: 'medium' - }), - link({ variant: 'selectable' }), - responsiveSprinkles({ - paddingTop: ['x2', 'x2', 'x2', 'x2'], - paddingRight: ['x4', 'x4', 'x4', 'x4'], - paddingBottom: ['x2', 'x2', 'x2', 'x2'], - paddingLeft: ['x4', 'x4', 'x4', 'x4'] - }) - ]) -}; diff --git a/packages/layouts/src/layouts/TypeDoc/icons.css b/packages/layouts/src/layouts/TypeDoc/icons.css deleted file mode 100644 index 29d5aae5..00000000 --- a/packages/layouts/src/layouts/TypeDoc/icons.css +++ /dev/null @@ -1,944 +0,0 @@ -.tsd-kind-icon { - display: block; - position: relative; - padding-left: 20px; - text-indent: -20px; -} -.tsd-kind-icon:before { - content: ''; - display: inline-block; - vertical-align: middle; - width: 17px; - height: 17px; - margin: 0 8px 2px 0; - background-image: url(./icons.png); -} -@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) { - .tsd-kind-icon:before { - background-image: url(./icons@2x.png); - background-size: 238px 204px; - } -} - -.tsd-signature.tsd-kind-icon:before { - background-position: 0 -153px; -} - -.tsd-kind-object-literal > .tsd-kind-icon:before { - background-position: 0px -17px; -} -.tsd-kind-object-literal.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -17px; -} -.tsd-kind-object-literal.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -17px; -} - -.tsd-kind-class > .tsd-kind-icon:before { - background-position: 0px -34px; -} -.tsd-kind-classes > .tsd-kind-icon:before { - background-position: 0px -34px; -} -.tsd-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -34px; -} -.tsd-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -34px; -} - -.tsd-kind-class.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -51px; -} -.tsd-kind-class.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -51px; -} - -.tsd-kind-interface > .tsd-kind-icon:before { - background-position: 0px -68px; -} -.tsd-kind-interfaces > .tsd-kind-icon:before { - background-position: 0px -68px; -} -.tsd-kind-interface.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -68px; -} -.tsd-kind-interface.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -68px; -} - -.tsd-kind-interface.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -85px; -} -.tsd-kind-interface.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -85px; -} - -.tsd-kind-namespace > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-namespace.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-namespace.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-module > .tsd-kind-icon:before { - background-position: 0px -102px; -} -.tsd-kind-module.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -102px; -} -.tsd-kind-module.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -102px; -} - -.tsd-kind-enum > .tsd-kind-icon:before { - background-position: 0px -119px; -} -.tsd-kind-enums > .tsd-kind-icon:before { - background-position: 0px -119px; -} -.tsd-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -119px; -} -.tsd-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -119px; -} - -.tsd-kind-enum-member > .tsd-kind-icon:before { - background-position: 0px -136px; -} -.tsd-kind-enum-member.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -136px; -} -.tsd-kind-enum-member.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -136px; -} - -.tsd-kind-signature > .tsd-kind-icon:before { - background-position: 0px -153px; -} -.tsd-kind-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -153px; -} -.tsd-kind-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -153px; -} - -.tsd-kind-type-alias > .tsd-kind-icon:before { - background-position: 0px -170px; -} -.tsd-kind-type-alias.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -170px; -} -.tsd-kind-type-alias.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -170px; -} - -.tsd-kind-type-alias.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: 0px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-protected > .tsd-kind-icon:before { - background-position: -17px -187px; -} -.tsd-kind-type-alias.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -34px -187px; -} - -.tsd-kind-variable > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-variable.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-variable.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-variable.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-variable.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-variable.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-property > .tsd-kind-icon:before { - background-position: -136px -0px; -} -.tsd-kind-property.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -0px; -} -.tsd-kind-property.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -0px; -} -.tsd-kind-property.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -0px; -} -.tsd-kind-property.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -0px; -} -.tsd-kind-property.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -0px; -} - -.tsd-kind-get-signature > .tsd-kind-icon:before { - background-position: -136px -17px; -} -.tsd-kind-get-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -17px; -} -.tsd-kind-get-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -17px; -} -.tsd-kind-get-signature.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -17px; -} - -.tsd-kind-set-signature > .tsd-kind-icon:before { - background-position: -136px -34px; -} -.tsd-kind-set-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -34px; -} -.tsd-kind-set-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -34px; -} -.tsd-kind-set-signature.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -34px; -} - -.tsd-kind-accessor > .tsd-kind-icon:before { - background-position: -136px -51px; -} -.tsd-kind-accessor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -51px; -} -.tsd-kind-accessor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -51px; -} -.tsd-kind-accessor.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -51px; -} - -.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-function.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-method.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -68px; -} -.tsd-kind-call-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -68px; -} -.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -68px; -} -.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -68px; -} - -.tsd-kind-function.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-function.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-method.tsd-has-type-parameter > .tsd-kind-icon:before { - background-position: -136px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-enum.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -85px; -} -.tsd-kind-method.tsd-has-type-parameter.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -85px; -} - -.tsd-kind-constructor > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-constructor-signature > .tsd-kind-icon:before { - background-position: -136px -102px; -} -.tsd-kind-constructor-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -102px; -} -.tsd-kind-constructor-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -102px; -} -.tsd-kind-constructor-signature.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -102px; -} - -.tsd-kind-index-signature > .tsd-kind-icon:before { - background-position: -136px -119px; -} -.tsd-kind-index-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -119px; -} -.tsd-kind-index-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -119px; -} -.tsd-kind-index-signature.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -119px; -} - -.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -136px; -} -.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -136px; -} -.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -136px; -} -.tsd-kind-event.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -136px; -} -.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -136px; -} -.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -136px; -} - -.tsd-is-static > .tsd-kind-icon:before { - background-position: -136px -153px; -} -.tsd-is-static.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -153px; -} -.tsd-is-static.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -102px -153px; -} -.tsd-is-static.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -153px; -} -.tsd-is-static.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -153px; -} -.tsd-is-static.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -153px; -} -.tsd-is-static.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -153px; -} - -.tsd-is-static.tsd-kind-function > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-function.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-method > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-method.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-call-signature > .tsd-kind-icon:before { - background-position: -136px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -68px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -85px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-class.tsd-is-private - > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-protected - > .tsd-kind-icon:before { - background-position: -187px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -170px; -} -.tsd-is-static.tsd-kind-call-signature.tsd-parent-kind-interface.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -221px -170px; -} - -.tsd-is-static.tsd-kind-event > .tsd-kind-icon:before { - background-position: -136px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-protected > .tsd-kind-icon:before { - background-position: -153px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class > .tsd-kind-icon:before { - background-position: -51px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -68px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected > .tsd-kind-icon:before { - background-position: -85px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-protected.tsd-is-inherited - > .tsd-kind-icon:before { - background-position: -102px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-class.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum > .tsd-kind-icon:before { - background-position: -170px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-protected > .tsd-kind-icon:before { - background-position: -187px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-enum.tsd-is-private > .tsd-kind-icon:before { - background-position: -119px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface > .tsd-kind-icon:before { - background-position: -204px -187px; -} -.tsd-is-static.tsd-kind-event.tsd-parent-kind-interface.tsd-is-inherited > .tsd-kind-icon:before { - background-position: -221px -187px; -} diff --git a/packages/layouts/src/layouts/TypeDoc/icons.png b/packages/layouts/src/layouts/TypeDoc/icons.png deleted file mode 100644 index 3836d5fe..00000000 Binary files a/packages/layouts/src/layouts/TypeDoc/icons.png and /dev/null differ diff --git a/packages/layouts/src/layouts/TypeDoc/icons@2x.png b/packages/layouts/src/layouts/TypeDoc/icons@2x.png deleted file mode 100644 index 5a209e2f..00000000 Binary files a/packages/layouts/src/layouts/TypeDoc/icons@2x.png and /dev/null differ diff --git a/packages/layouts/src/layouts/TypeDoc/index.tsx b/packages/layouts/src/layouts/TypeDoc/index.tsx deleted file mode 100644 index be10f2a1..00000000 --- a/packages/layouts/src/layouts/TypeDoc/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import classnames from 'clsx'; -import { HelpLinks, Hero } from '@jpmorganchase/mosaic-components'; -import { - AppHeader, - BackLink, - Breadcrumbs, - Footer, - PageNavigation -} from '@jpmorganchase/mosaic-site-components'; - -import { LayoutBase } from '../../LayoutBase'; -import { LayoutColumns } from '../../LayoutColumns'; -import type { LayoutProps } from '../../types'; -import styles from './styles.css'; -import { ExportsSidebar } from './ExportsSidebar'; - -const APIHero = ({ description, name, pageType, releaseDate, title, version }) => ( - -); - -export const TypeDoc: React.FC = ({ - BackLinkProps, - SidebarProps, - FooterProps, - meta, - children -}) => { - const Sidebar = ( - <> - {BackLinkProps && ( -
    - -
    - )} - - {SidebarProps?.helpLinks && } - - ); - const showHero = meta?.data?.pageType === 'root' || meta?.data?.pageType === 'index'; - const SecondarySidebar = meta?.data?.sidebarNavOptions ? ( - - ) : null; - return ( - }> - } - > -
    - - {showHero && meta?.data ? ( - - ) : null} - {children} -
    -
    -
    - ); -}; diff --git a/packages/layouts/src/layouts/TypeDoc/style.css b/packages/layouts/src/layouts/TypeDoc/style.css deleted file mode 100644 index 2199bf03..00000000 --- a/packages/layouts/src/layouts/TypeDoc/style.css +++ /dev/null @@ -1,689 +0,0 @@ -@import url('./icons.css'); -:root { - --color-ts: #9600ff; - --color-ts-interface: #647f1b; - --color-ts-enum: #937210; - --color-ts-class: #0672de; - --color-ts-private: #707070; - --color-link: var(--color-light-navigable-link-regular); - --color-panel-divider: var(--color-light-neutral-foreground-low); -} -.dark { - --color-ts: #9600ff; - --color-ts-interface: #647f1b; - --color-ts-enum: #937210; - --color-ts-class: #0672de; - --color-ts-private: #707070; - --color-link: var(--color-dark-navigable-link-regular); - --color-panel-divider: var(--color-dark-neutral-foreground-low); -} - -.typedoc body { - margin: 0; -} - -.typedoc h2 { - font-size: var(--fontSize-s140) !important; -} - -.typedoc h3 { - font-size: var(--fontSize-s110) !important; -} - -.typedoc h4 { - font-size: var(--fontSize-s80) !important; -} - -.typedoc-api-page .tsd-typography div:first-of-type h2 { - margin: 0; -} -.typedoc-api-page h4, -h5, -h6 { - margin: 0; -} -.typedoc.typedoc-modules-page a > h4, -.typedoc.typedoc-modules-page a > h5, -.typedoc.typedoc-modules-page a > h6 { - display: inline; - margin: 0; -} - -.typedoc .container { - margin: 0 auto; - margin-top: var(--space-vertical-x6); -} - -.typedoc .row { - display: flex; -} -.typedoc .row:after { - visibility: hidden; - display: block; - content: ''; - clear: both; - height: 0; -} - -.col-4, -.typedoc .col-8 { - width: 100%; - box-sizing: border-box; -} - -@media (min-width: 901px) and (max-width: 1024px) { - .typedoc html .col-content { - width: 72%; - } - .typedoc html .col-menu { - width: 28%; - } - .typedoc html .tsd-navigation { - padding-left: 10px; - } -} -@media (max-width: 900px) { - .typedoc html .col-content { - float: none; - width: 100%; - } - .typedoc html .col-menu { - position: fixed !important; - overflow: auto; - -webkit-overflow-scrolling: touch; - z-index: 1024; - top: 0 !important; - bottom: 0 !important; - left: auto !important; - right: 0 !important; - width: 100%; - padding: 20px 20px 0 0; - max-width: 450px; - visibility: hidden; - transform: translate(100%, 0); - } - .typedoc html .col-menu > *:last-child { - padding-bottom: 20px; - } -} - -.tsd-page-title { - padding: 0; -} -.tsd-page-title h2 { - margin: 0; -} - -#tsd-filter { - position: relative; - display: inline-block; - height: 40px; - vertical-align: bottom; -} -.no-filter #tsd-filter { - display: none; -} -#tsd-filter .tsd-filter-group { - display: inline-block; - height: 40px; - vertical-align: bottom; - white-space: nowrap; -} -#tsd-filter input { - display: none; -} -@media (max-width: 900px) { - #tsd-filter .tsd-filter-group { - display: block; - position: absolute; - top: 40px; - right: 20px; - height: auto; - visibility: hidden; - transform: translate(50%, 0); - } - .has-options #tsd-filter .tsd-filter-group { - visibility: visible; - } - .to-has-options #tsd-filter .tsd-filter-group { - animation: fade-in 0.2s; - } - .from-has-options #tsd-filter .tsd-filter-group { - animation: fade-out 0.2s; - } - #tsd-filter label, - #tsd-filter .tsd-select { - display: block; - padding-right: 20px; - } -} - -.tsd-hierarchy .target { - font-weight: bold; -} - -.tsd-panel.tsd-index-panel { - margin-top: var(--space-vertical-x6); - border: 1px solid var(--color-panel-divider); -} - -.tsd-index-panel h3 { -} -.tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; - -webkit-column-gap: 20px; - -moz-column-gap: 20px; - -ms-column-gap: 20px; - -o-column-gap: 20px; - column-gap: 20px; - padding: 0; - list-style: none; -} - -@media (min-width: 961px) and (max-width: 1280px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; - } -} - -@media (min-width: 601px) and (max-width: 960px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 2; - -moz-column-count: 2; - -ms-column-count: 2; - -o-column-count: 2; - column-count: 2; - } -} - -@media (max-width: 600px) { - .tsd-index-panel ul.tsd-index-list { - -webkit-column-count: 1; - -moz-column-count: 1; - -ms-column-count: 1; - -o-column-count: 1; - column-count: 1; - } -} -.tsd-index-panel ul.tsd-index-list li { - -webkit-page-break-inside: avoid; - -moz-page-break-inside: avoid; - -ms-page-break-inside: avoid; - -o-page-break-inside: avoid; - page-break-inside: avoid; - padding: 8px; -} - -.tsd-index-panel a[class^='tsd-kind'] { - color: var(--color-ts); -} -.tsd-index-panel .tsd-parent-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-parent-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-parent-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-kind-module a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-kind-interface a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-kind-enum a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-kind-class a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-is-private a { - color: var(--color-ts-private); -} - -.tsd-index-panel .tsd-parent-kind-interfaces a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-parent-kind-enums a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-parent-kind-classes a { - color: var(--color-ts-class); -} -.tsd-index-panel .tsd-kind-modules a { - color: var(--color-ts); -} -.tsd-index-panel .tsd-kind-interfaces a { - color: var(--color-ts-interface); -} -.tsd-index-panel .tsd-kind-enums a { - color: var(--color-ts-enum); -} -.tsd-index-panel .tsd-kind-classes a { - color: var(--color-ts-class); -} - -.tsd-flag { - display: inline-block; - padding: 1px 5px; - border-radius: 4px; - color: var(--color-dark-neutral-foreground-high); - background-color: var(--color-light-navigable-link-disabled); - text-indent: 0; - font-size: 14px; - font-weight: normal; - vertical-align: middle; -} - -.tsd-member .tsd-anchor + h3 { - margin-top: 0; - margin-bottom: 0; - border-bottom: none; -} -.tsd-member [data-tsd-kind] { - color: var(--color-ts); -} -.tsd-member [data-tsd-kind='Interface'] { - color: var(--color-ts-interface); -} -.tsd-member [data-tsd-kind='Enum'] { - color: var(--color-ts-enum); -} -.tsd-member [data-tsd-kind='Class'] { - color: var(--color-ts-class); -} -.tsd-member [data-tsd-kind='Private'] { - color: var(--color-ts-private); -} - -.tsd-panel { - padding-left: var(--space-horizontal-x4); - padding-right: var(--space-horizontal-x4); - border: 1px solid var(--color-panel-divider); - box-shadow: var(--shadow-light-elevation2); -} -.tsd-panel.tsd-member { - margin-top: var(--space-vertical-x4); - padding: var(--space-vertical-x4); -} -.tsd-panel.tsd-typography, -.tsd-panel.tsd-comment { - border: none; - box-shadow: none; -} -.tsd-panel.tsd-comment { - padding-left: 0; -} - -.tsd-panel:empty { - display: none; -} - -.tsd-panel > h2, -.tsd-panel > h3 { -} -.tsd-panel > h1.tsd-before-signature, -.tsd-panel > h2.tsd-before-signature, -.tsd-panel > h3.tsd-before-signature { - margin-bottom: 0; -} -.tsd-panel table { - display: block; - width: 100%; - overflow: auto; - margin-top: 10px; - word-break: normal; - word-break: keep-all; - border-collapse: collapse; -} -.tsd-panel table th { - font-weight: bold; -} -.tsd-panel table th, -.tsd-panel table td { - padding: 6px 13px; - border: 1px solid var(--color-panel-divider); -} -.tsd-panel table tr, -.tsd-panel table tr:nth-child(even), -.tsd-panel-group { -} - -.tsd-panel-group > h3 { - padding-right: 20px; -} - -.tsd-signature { - padding-left: var(--space-horizontal-x4); - padding-right: var(--space-horizontal-x4); - padding-top: var(--space-vertical-x4); - padding-bottom: var(--space-vertical-x4); - margin-top: var(--space-vertical-x4); - margin-bottom: var(--space-vertical-x4); - font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; - font-size: 14px; - overflow-x: auto; - border: 1px solid var(--color-panel-divider); -} - -.tsd-signature.tsd-kind-icon { - padding-left: 30px; -} -.tsd-signature.tsd-kind-icon:before { - top: 10px; - left: 10px; -} - -.tsd-panel > .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signature.tsd-kind-icon:before { - left: 20px; -} - -.tsd-signature-symbol { - font-weight: normal; -} - -.tsd-signature-type { - font-style: italic; - font-weight: normal; -} - -.tsd-signatures { - padding: 0; -} - -.tsd-signatures .tsd-signature { - margin: 0; - transition: background-color 0.1s; -} - -.tsd-signatures .tsd-signature.current, -.tsd-signatures.active > .tsd-signature { - cursor: pointer; -} -.tsd-panel > .tsd-signatures { - border-width: 1px 0; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon { - padding-left: 40px; -} -.tsd-panel > .tsd-signatures .tsd-signature.tsd-kind-icon:before { - left: 20px; -} - -.tsd-kind-icon { - overflow-wrap: normal; -} -.tsd-signature.tsd-kind-icon:before { - mask: unset; - -webkit-mask: none; - background-color: unset; -} - -ul.tsd-descriptions { - position: relative; - overflow: hidden; - padding: 0; - list-style: none; -} -ul.tsd-descriptions.active > .tsd-description { - display: none; -} -ul.tsd-descriptions.active > .tsd-description.current { - display: block; -} -ul.tsd-descriptions.active > .tsd-description.fade-in { - animation: fade-in-delayed 0.3s; -} -ul.tsd-descriptions.active > .tsd-description.fade-out { - animation: fade-out-delayed 0.3s; - position: absolute; - display: block; - top: 0; - left: 0; - right: 0; - opacity: 0; - visibility: hidden; -} -ul.tsd-descriptions h4, -ul.tsd-descriptions .tsd-index-panel h3, -.tsd-index-panel ul.tsd-descriptions h3 { - font-size: 16px; -} - -ul.tsd-parameters, -ul.tsd-type-parameters { -} -ul.tsd-parameters > li.tsd-parameter-signature, -ul.tsd-type-parameters > li.tsd-parameter-signature { - list-style: none; -} -ul.tsd-parameters h5, -ul.tsd-type-parameters h5 { - font-size: 16px; -} -ul.tsd-parameters .tsd-comment, -ul.tsd-type-parameters .tsd-comment { -} - -.tsd-comment-tags dt { - float: left; - padding: 1px 5px; - border-radius: 4px; - color: var(--color-light-neutral-foreground-high); - text-indent: 0; - font-size: 0.8em; - font-weight: normal; - border: 1px solid var(--color-light-neutral-foreground-mid); - margin-right: var(--space-horizontal-x2); -} - -.tsd-comment-tags dd { - display: block; -} - -.tsd-comment-tags li { - margin-left: var(--space-horizontal-x13); -} -.tsd-sources { - font-size: 14px; - margin: 0; -} -.tsd-sources a { - text-decoration: underline; - display: inline-block; -} -.tsd-sources ul, -.tsd-sources p { - margin: 0 !important; -} -.tsd-sources ul { - list-style: none; - padding: 0; -} - -/** Latest */ - -.typedoc dd { - margin: 0; -} - -.typedoc a { - display: inline; -} -.typedoc p a { - display: inline; -} - -.typedoc a.tsd-signature-type { - display: inline; -} - -.typedoc .lead > p { - margin-top: var(--space-vertical-x2); -} - -.typedoc .tsd-type-declaration { - margin-top: var(--space-vertical-x4); -} -.typedoc .tsd-type-declaration > ul { - margin-left: 0; - padding: 0; -} -.typedoc .tsd-parameters { - margin-top: 0; -} -.typedoc .tsd-parameters .tsd-parameter > div { - display: inline; -} -.typedoc .tsd-parameters .tsd-parameter > div a { - display: inline-block; -} -.typedoc .tsd-parameter, -.tsd-declaration { - padding: 0 var(--space-horizontal-x4); -} -.typedoc .tsd-type-declaration a { - font-style: italic; - text-decoration: none; -} - -.typedoc .tsd-type-parameters-title, -.tsd-parameters-title { - font-style: italic; - text-decoration: none; - margin-top: var(--space-vertical-x4); -} -.typedoc .tsd-type-returns-title, -.tsd-returns-title { - font-style: italic; - text-decoration: none; - margin-top: var(--space-vertical-x4); -} - -.typedoc .target { - display: inline-block; - margin-top: var(--space-vertical-x4); -} - -.typedoc footer { - padding-top: var(--space-vertical-x1); -} - -.typedoc footer:after { - content: ''; - display: table; -} -.typedoc footer .tsd-legend-group { - font-size: 0; -} -.typedoc footer .tsd-legend-group li { - padding: var(--space-vertical-x1); -} -.typedoc footer .tsd-legend { - display: inline-block; - width: 25%; - padding: var(--space-vertical-x2) 0 0 0; - font-size: 16px; - list-style: none; - vertical-align: top; -} - -.typedoc .tsd-parameters > .tsd-parameter { - margin-left: var(--space-vertical-x4); -} -.typedoc .tsd-parameter-signature > ul { - margin-left: 0; -} -.typedoc .tsd-index-section { - padding: 0; -} -.typedoc .tsd-index-section:first-of-type { - padding-top: var(--space-vertical-x6); -} - -.typedoc .tsd-index-section .tsd-index-list { - padding: 0; - margin: 0; -} -.typedoc .tsd-index-section ul:first-of-type { - margin-top: var(--space-vertical-x6); -} - -.typedoc .tsd-index-section .tsd-index-list a { - padding: 0; - margin: 0; -} -.typedoc .tsd-index-section h3 { - margin-top: var(--space-vertical-x6); -} -.typedoc .tsd-index-section:first-of-type h3 { - margin-top: 0; -} -.typedoc .tsd-panel.tsd-member h3 { - margin-top: 0; -} -.typedoc div.tsd-typography { - display: inline; -} -.typedoc :not(.tsd-member).tsd-panel:first-of-type { - margin-top: 0; -} - -.typedoc .tsd-index-group .tsd-panel.tsd-panel { - padding: 0 var(--space-horizontal-x4) var(--space-vertical-x4) var(--space-horizontal-x4); - margin-top: var(--space-vertical-x4); -} - -.typedoc .tsd-panel.tsd-type-parameters { - padding: 0 var(--space-horizontal-x4) var(--space-vertical-x4) var(--space-horizontal-x4); - margin-top: var(--space-vertical-x10); -} -.typedoc .tsd-panel.tsd-hierarchy { - padding: 0 var(--space-horizontal-x4) var(--space-vertical-x4) var(--space-horizontal-x4); - margin-top: var(--space-vertical-x10); - border: 1px solid var(--color-panel-divider); -} - -.typedoc.typedoc-index .tsd-page-title { - display: none; -} - -.typedoc .tsd-member a[data-tsd-kind] { - color: var(--color-ts); -} -.typedoc .tsd-member a[data-tsd-kind='Interface'] { - color: var(--color-ts-interface); -} - -.typedoc .tsd-index-panel .tsd-parent-kind-module a { - color: var(--color-ts); -} - -.typedoc a { - color: var(--color-link); - text-decoration: none; - display: inline; -} - -.typedoc a:hover { - text-decoration: none; -} -.typedoc li > div { - display: inline-block; -} - -.typedoc.typedoc-root-page .tsd-panel { - padding: 0; -} diff --git a/packages/layouts/src/layouts/TypeDoc/styles.css.ts b/packages/layouts/src/layouts/TypeDoc/styles.css.ts deleted file mode 100644 index c1f975c1..00000000 --- a/packages/layouts/src/layouts/TypeDoc/styles.css.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; - -export default { - columnWrapper: style([ - style({ - display: 'flex', - justifyContent: 'stretch', - width: '100%', - maxWidth: '1440px', - marginLeft: 'auto', - marginRight: 'auto' - }), - responsiveSprinkles({ - paddingLeft: ['x4', 'x6', 'x6', 'x6'], - paddingRight: ['x4', 'x6', 'x6', 'x6'] - }) - ]), - - contentBody: style([ - style({ - zIndex: 2, - flexGrow: 1, - flexShrink: 1, - // We're letting flexGrow do the width calc here, so as long as - // the width is smaller than the space the layout will be correct. - width: '1px' - }), - responsiveSprinkles({ - paddingTop: ['x10', 'x10', 'x20', 'x20'] - }) - ]), - - sidebarHeader: style({ flexShrink: 0 }), - - wrapper: style({ - width: '100%', - overflow: 'hidden', - position: 'relative' - }) -}; diff --git a/packages/layouts/src/layouts/TypeDoc/widgets.png b/packages/layouts/src/layouts/TypeDoc/widgets.png deleted file mode 100644 index c7380532..00000000 Binary files a/packages/layouts/src/layouts/TypeDoc/widgets.png and /dev/null differ diff --git a/packages/layouts/src/layouts/TypeDoc/widgets@2x.png b/packages/layouts/src/layouts/TypeDoc/widgets@2x.png deleted file mode 100644 index 4bbbd572..00000000 Binary files a/packages/layouts/src/layouts/TypeDoc/widgets@2x.png and /dev/null differ diff --git a/packages/layouts/src/layouts/index.ts b/packages/layouts/src/layouts/index.ts index 9017d167..2d935fbf 100644 --- a/packages/layouts/src/layouts/index.ts +++ b/packages/layouts/src/layouts/index.ts @@ -4,5 +4,3 @@ export * from './FullWidth'; export * from './Landing'; export * from './Newsletter'; export * from './Product'; -export * from './PythonDoc'; -export * from './TypeDoc'; diff --git a/packages/layouts/src/types/index.tsx b/packages/layouts/src/types/index.tsx index d9f3b45f..b0a5d00a 100644 --- a/packages/layouts/src/types/index.tsx +++ b/packages/layouts/src/types/index.tsx @@ -1,22 +1,23 @@ import type { ReactNode } from 'react'; -import type { Breadcrumb, LinkType } from '@jpmorganchase/mosaic-site-components'; +import type { LinkType } from '@jpmorganchase/mosaic-site-components'; import { FooterProps } from '@jpmorganchase/mosaic-site-components'; export type LayoutProps = { + AppHeaderComponent: ReactNode; + BreadcrumbsComponent: ReactNode; + FooterComponent: ReactNode; + DocPaginatorComponent: ReactNode; + SecondarySidebarComponent: ReactNode; + PrimarySidebarComponent: ReactNode; children?: ReactNode; className?: string; - ToCProps?: { - items: any; - }; SidebarProps?: Pick; NextPrevLinksProps?: { next?: LinkType; prev?: LinkType; }; - BreadcrumbsProps?: { breadcrumbs: typeof Breadcrumb[] }; BackLinkProps?: { label?: string; link: string }; - FooterProps?: any; layout?: string; meta?: { data: { [key: string]: any }; diff --git a/packages/loaders/README.md b/packages/loaders/README.md new file mode 100644 index 00000000..68657d24 --- /dev/null +++ b/packages/loaders/README.md @@ -0,0 +1,13 @@ +# Mosaic Loaders (Server) + +`@jpmorganchase/mosaic-loaders` contains Mosaic loaders for content served by Mosaic Core. + +## Installation + +`yarn add @jpmorganchase/mosaic-loaders` + +## Criteria + +The criteria for code within `@jpmorganchase/mosaic-loaders` is + +- Should only export server side code diff --git a/packages/loaders/package.json b/packages/loaders/package.json new file mode 100644 index 00000000..9d266778 --- /dev/null +++ b/packages/loaders/package.json @@ -0,0 +1,45 @@ +{ + "name": "@jpmorganchase/mosaic-loaders", + "description": "Mosaic - Loaders", + "version": "0.1.0-beta.89", + "author": "", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git@github.com:jpmorganchase/mosaic.git", + "directory": "packages/loaders" + }, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./index.css": "./dist/index.css", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "node": "./dist/index.js" + } + }, + "scripts": { + "build": "npm-run-all --parallel build:*", + "build:types": "tsc", + "build:components": "node ./scripts/bundle.mjs", + "clean": "npx del-cli 'dist/**' && find . -type d -empty -delete", + "lint": "eslint --ignore-pattern \"**/__tests__/**\"", + "dev": "node ./scripts/bundle.mjs watch" + }, + "devDependencies": { + "del-cli": "^4.0.1", + "esbuild": "0.23.1", + "esbuild-node-externals": "^1.0.2", + "fast-glob": "^3.2.7", + "typescript": "^5.2.2" + }, + "dependencies": { + "@jpmorganchase/mosaic-schemas": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", + "@types/node": "^18.15.3", + "gray-matter": "^4.0.3", + "zod": "^3.22.3" + } +} diff --git a/packages/loaders/scripts/bundle.mjs b/packages/loaders/scripts/bundle.mjs new file mode 100644 index 00000000..ca175553 --- /dev/null +++ b/packages/loaders/scripts/bundle.mjs @@ -0,0 +1,44 @@ +import glob from 'fast-glob'; +import esbuild from 'esbuild'; +import { nodeExternalsPlugin } from 'esbuild-node-externals'; + +const args = process.argv.slice(2); +const watchEnabled = args[0] === 'watch'; +const packageName = process.env.npm_package_name; + +try { + const context = await esbuild.context({ + entryPoints: glob.sync(['src/**/*.ts?(x)', 'src/*.ts?(x)'], { + ignore: ['**/__tests__', 'src/labs'] + }), + outdir: './dist', + outExtension: { '.js': '.mjs' }, + bundle: true, + sourcemap: false, + splitting: true, + minify: true, + format: 'esm', + target: ['es2022', 'node18'], + platform: 'node', + plugins: [nodeExternalsPlugin()] + }); + await context.rebuild(); + if (watchEnabled) { + await context.watch(); + } + await context.serve(); + context.dispose(); +} catch (e) { + if (e.errors && e.errors.length > 0) { + console.group(`!!!!!!! ${packageName} build errors !!!!!!!`); + console.error(e.errors); + console.groupEnd(); + } + + if (e.warnings && e.warnings.length > 0) { + console.group(`!!!!!!! ${packageName} build warnings !!!!!!!`); + console.error(e.warnings); + console.groupEnd(); + } + process.exit(1); +} diff --git a/packages/loaders/src/LoadPageError.ts b/packages/loaders/src/LoadPageError.ts new file mode 100644 index 00000000..b1d9f36c --- /dev/null +++ b/packages/loaders/src/LoadPageError.ts @@ -0,0 +1,7 @@ +export class LoadPageError extends Error { + statusCode: number; + constructor({ message, statusCode }: { message: string; statusCode: number }) { + super(message); + this.statusCode = statusCode; + } +} diff --git a/packages/loaders/src/index.ts b/packages/loaders/src/index.ts new file mode 100644 index 00000000..eda4c8b0 --- /dev/null +++ b/packages/loaders/src/index.ts @@ -0,0 +1,35 @@ +import path from 'path'; +import type { MosaicMode, SharedConfig } from '@jpmorganchase/mosaic-types'; + +import type { LoaderPage } from './types/index.js'; +import { loadActiveContent, loadActiveMosaicData } from './loadActiveContent'; +import { loadSnapshotFileContent, loadSnapshotFileMosaicData } from './loadSnapshotFileContent'; + +export { LoadPageError } from './LoadPageError'; +export * from './types/index.js'; + +export const loadMosaicData = async (url: string): Promise => { + const mode: MosaicMode = (process.env.MOSAIC_MODE || 'active') as MosaicMode; + + if (mode === 'snapshot-file') { + return loadSnapshotFileMosaicData(url); + } + + return loadActiveMosaicData(url); +}; + +export const loadSharedConfig = async (route: string): Promise => { + const sharedConfigUrl = path.posix.join(path.posix.dirname(route), 'shared-config.json'); + const { config } = await loadMosaicData<{ config: SharedConfig }>(sharedConfigUrl); + return config; +}; + +export const loadPage = async (route: string): Promise => { + const mode: MosaicMode = (process.env.MOSAIC_MODE || 'active') as MosaicMode; + + if (mode === 'snapshot-file') { + return loadSnapshotFileContent(route); + } + + return loadActiveContent(route); +}; diff --git a/packages/loaders/src/loadActiveContent.ts b/packages/loaders/src/loadActiveContent.ts new file mode 100644 index 00000000..f30490dc --- /dev/null +++ b/packages/loaders/src/loadActiveContent.ts @@ -0,0 +1,60 @@ +import matter from 'gray-matter'; +import type { SafeParseError } from 'zod'; +import { activeEnvSchema } from '@jpmorganchase/mosaic-schemas'; + +import { LoadPageError } from './LoadPageError'; +import type { LoaderPage } from './types/index.js'; + +const normalizePageUrl = (url: string): string => (/\/index$/.test(url) ? `${url}.mdx` : url); + +type ActiveModeUrlEnv = { + MOSAIC_ACTIVE_MODE_URL: string; +}; + +const getFSRootUrl = (): string => { + const env = activeEnvSchema.safeParse(process.env); + if (!env.success) { + const { error } = env as SafeParseError; + error.issues.forEach(issue => { + console.error( + `Missing process.env.${issue.path.join()} environment variable required to load pages` + ); + }); + throw new LoadPageError({ + message: `Environment variables missing to load pages`, + statusCode: 500 + }); + } + return env.data.MOSAIC_ACTIVE_MODE_URL; +}; + +export const loadActiveMosaicData = async (url: string): Promise => { + const fsRootUrl = getFSRootUrl(); + const dataUrl = new URL(url, fsRootUrl); + const response = await fetch(dataUrl); + + if (!response.ok) { + // This will activate the closest `error.js` Error Boundary + throw new Error(`Failed to fetch mosaic data @ ${dataUrl}`); + } + return response.json(); +}; + +export const loadActiveContent = async (route: string): Promise => { + const fsRootUrl = getFSRootUrl(); + const pageUrl = normalizePageUrl(`${fsRootUrl}${route}`); + const response = await fetch(pageUrl); + if (response.status === 302) { + const { redirect } = await response.json(); + return loadActiveContent(redirect); + } + if (response.ok) { + const source = await response.text(); + const { content, data } = matter(source); + return { source: content, data }; + } + throw new LoadPageError({ + message: `Could not load page : ${pageUrl} ${response.status}/${response.statusText}`, + statusCode: 404 + }); +}; diff --git a/packages/loaders/src/loadSnapshotFileContent.ts b/packages/loaders/src/loadSnapshotFileContent.ts new file mode 100644 index 00000000..e6044506 --- /dev/null +++ b/packages/loaders/src/loadSnapshotFileContent.ts @@ -0,0 +1,62 @@ +import path from 'path'; +import fs from 'node:fs/promises'; +import matter from 'gray-matter'; +import { snapshotFileEnvSchema } from '@jpmorganchase/mosaic-schemas'; + +import { LoadPageError } from './LoadPageError'; +import type { LoaderPage } from './types/index.js'; + +const normalizePageUrl = (url: string): string => (/\/index$/.test(url) ? `${url}.mdx` : url); + +const getFSRootUrl = (): string => { + const env = snapshotFileEnvSchema.safeParse(process.env); + if (!env.success) { + env.error.issues.forEach(issue => { + console.error( + `Missing process.env.${issue.path.join()} environment variable required to load pages` + ); + }); + throw new LoadPageError({ + message: `Environment variables missing to load pages`, + statusCode: 500 + }); + } + return env.data.MOSAIC_SNAPSHOT_DIR; +}; + +export const loadSnapshotFileMosaicData = async (url: string): Promise => { + const fsRootUrl = getFSRootUrl(); + const filePath = path.join(process.cwd(), fsRootUrl, url); + try { + const realPath = await fs.realpath(filePath); + + const source = await fs.readFile(realPath, 'utf-8'); + return JSON.parse(source); + } catch { + throw new Error(`Failed to fetch mosaic data @ ${url}`); + } +}; + +export const loadSnapshotFileContent = async (route: string): Promise => { + const fsRootUrl = getFSRootUrl(); + const pageUrl = normalizePageUrl(route); + const filePath = path.join(process.cwd(), fsRootUrl, pageUrl); + try { + let localPath = filePath; + if ((await fs.stat(filePath)).isDirectory()) { + localPath = path.join(localPath, 'index'); + } + const realPath = await fs.realpath(localPath); + const source = await fs.readFile(realPath, 'utf-8'); + const { content, data } = matter(source); + return { source: content, data }; + } catch (error) { + if (error instanceof Error) { + console.error(error.message); + } + throw new LoadPageError({ + message: `Could not read local file '${filePath}' for '${route}'`, + statusCode: 404 + }); + } +}; diff --git a/packages/loaders/src/types/index.ts b/packages/loaders/src/types/index.ts new file mode 100644 index 00000000..620ae9be --- /dev/null +++ b/packages/loaders/src/types/index.ts @@ -0,0 +1,37 @@ +import type { + Breadcrumb, + Navigation, + SearchConfig, + SearchIndex, + SharedConfig, + SidebarItem, + TableOfContentsItem +} from '@jpmorganchase/mosaic-types'; + +export type SiteState = { + /** Path described in breadcrumbs */ + breadcrumbs: Breadcrumb[]; + /** Page metadata description, used by search */ + description?: string; + /** Page route */ + route?: string; + /** Layout name that will define the page's anatomy */ + layout?: string; + navigation?: Navigation; + sharedConfig?: SharedConfig; + /** Page title */ + title?: string; + searchIndex?: SearchIndex; + searchConfig?: SearchConfig; + sidebarData: SidebarItem[]; + tableOfContents: TableOfContentsItem[]; +}; + +export type LoaderSource = string; +export type LoaderData = Partial; +export type LoaderPage = { + /** content source */ + source?: LoaderSource; + /** meta for content */ + data?: LoaderData; +}; diff --git a/packages/site-middleware/tsconfig.json b/packages/loaders/tsconfig.json similarity index 100% rename from packages/site-middleware/tsconfig.json rename to packages/loaders/tsconfig.json diff --git a/packages/mdx-components/README.md b/packages/mdx-components/README.md new file mode 100644 index 00000000..e377830f --- /dev/null +++ b/packages/mdx-components/README.md @@ -0,0 +1,18 @@ +# Mosaic MDX Component Library + +`@jpmorganchase/mosaic-components` contains re-usable MDX components that conform to the Mosaic Design language. + +All the Mosaic MDX components are wrapped with default spacing so they can be composed within a MDX page + +This package is intended to be used with a Mosaic site and should export "client side" components + +## Installation + +`yarn add @jpmorganchase/mosaic-components` + +## Criteria + +The criteria for a component within `@jpmorganchase/mosaic-components-client` is + +- Should only export React client-side components +- It can contain any Mosaic Store, NextJS or other site specific dependencies. diff --git a/packages/mdx-components/package.json b/packages/mdx-components/package.json new file mode 100644 index 00000000..b39dc10c --- /dev/null +++ b/packages/mdx-components/package.json @@ -0,0 +1,54 @@ +{ + "name": "@jpmorganchase/mosaic-mdx-components", + "description": "Mosaic - Markdown Components", + "version": "0.1.0-beta.89", + "author": "", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git@github.com:jpmorganchase/mosaic.git", + "directory": "packages/mdx-components" + }, + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "style": "./dist/index.css", + "exports": { + "./index.css": "./dist/index.css", + ".": { + "style": "./dist/index.css", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "node": "./dist/index.js" + } + }, + "scripts": { + "build": "npm-run-all --parallel build:*", + "build:types": "tsc", + "build:components": "node ../../scripts/bundle.mjs", + "clean": "npx del-cli 'dist/**' && find . -type d -empty -delete", + "lint": "eslint --ignore-pattern \"**/__tests__/**\"", + "doc": "node ../../scripts/updateDocs.js", + "dev": "node ../../scripts/bundle.mjs watch" + }, + "devDependencies": { + "react": "^18.3.0", + "del-cli": "^4.0.1", + "typescript": "^5.2.2" + }, + "dependencies": { + "@jpmorganchase/mosaic-components": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-open-api-component": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-site-components": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", + "@vanilla-extract/css": "^1.6.0", + "clsx": "^2.0.0", + "hoist-non-react-statics": "^3.3.2", + "next": "^14.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.46", + "react": "^18.3.0", + "react-dom": "^18.3.0" + } +} diff --git a/packages/components/src/Markdown/BlockQuote/index.css.ts b/packages/mdx-components/src/BlockQuote/index.css.ts similarity index 100% rename from packages/components/src/Markdown/BlockQuote/index.css.ts rename to packages/mdx-components/src/BlockQuote/index.css.ts diff --git a/packages/components/src/Markdown/BlockQuote/index.tsx b/packages/mdx-components/src/BlockQuote/index.tsx similarity index 100% rename from packages/components/src/Markdown/BlockQuote/index.tsx rename to packages/mdx-components/src/BlockQuote/index.tsx diff --git a/packages/components/src/Markdown/AnchorHeading/index.css.ts b/packages/mdx-components/src/Heading/AnchorHeading.css.ts similarity index 100% rename from packages/components/src/Markdown/AnchorHeading/index.css.ts rename to packages/mdx-components/src/Heading/AnchorHeading.css.ts diff --git a/packages/components/src/Markdown/AnchorHeading/index.tsx b/packages/mdx-components/src/Heading/AnchorHeading.tsx similarity index 86% rename from packages/components/src/Markdown/AnchorHeading/index.tsx rename to packages/mdx-components/src/Heading/AnchorHeading.tsx index a2364129..e1dacee8 100644 --- a/packages/components/src/Markdown/AnchorHeading/index.tsx +++ b/packages/mdx-components/src/Heading/AnchorHeading.tsx @@ -1,13 +1,12 @@ import React, { useEffect, useRef, useState } from 'react'; +import { usePathname } from 'next/navigation'; import classnames from 'clsx'; -import { Icon } from '../../Icon'; +import { Caption6, Icon, Link, LinkProps, TypographyProps } from '@jpmorganchase/mosaic-components'; -import { Link, LinkProps } from '../../Link'; -import { Caption6, TypographyProps } from '../../Typography'; -import styles from './index.css'; +import styles from './AnchorHeading.css'; export interface AnchorHeadingProps extends React.HTMLProps { - children: React.ReactNode[]; + children: React.ReactNode; Component: React.FC>; LinkProps?: LinkProps; } @@ -20,6 +19,7 @@ export const AnchorHeading: React.FC LinkProps: LinkPropsProp = {}, ...rest }) => { + const route = usePathname(); const timerRef = useRef(null); useEffect( () => () => { @@ -41,7 +41,7 @@ export const AnchorHeading: React.FC }; const handleMouseClick = event => { const anchor = event.currentTarget.getAttribute('href'); - navigator.clipboard.writeText(`${window.location.origin}${anchor}`); + navigator.clipboard.writeText(`${window.location.origin}${route}/${anchor}`); setCopied(true); timerRef.current = setTimeout(() => setCopied(false), 1000); event.preventDefault(); @@ -92,6 +92,3 @@ export const AnchorHeading: React.FC
    ); }; - -export const withAnchorHeading = Component => props => - ; diff --git a/packages/components/src/Markdown/__tests__/Heading.test.tsx b/packages/mdx-components/src/Heading/__tests__/Heading.test.tsx similarity index 79% rename from packages/components/src/Markdown/__tests__/Heading.test.tsx rename to packages/mdx-components/src/Heading/__tests__/Heading.test.tsx index bc9e8797..02d08818 100644 --- a/packages/components/src/Markdown/__tests__/Heading.test.tsx +++ b/packages/mdx-components/src/Heading/__tests__/Heading.test.tsx @@ -1,55 +1,54 @@ -import { describe, expect, test } from 'vitest'; -import React from 'react'; +import { describe, test, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; -import * as Heading from '../Heading'; +import { H0, H1, H2, H3, H4, H5, H6 } from '../index'; describe('GIVEN a Heading', () => { test('THEN a H0 can be rendered', () => { // arrange - render(H0); + render(H0); // assert expect(screen.getByText('H0')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); }); test('THEN a H1 can be rendered', () => { // arrange - render(H1); + render(

    H1

    ); // assert expect(screen.getByText('H1')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); }); test('THEN a H1 can be rendered', () => { // arrange - render(H2); + render(

    H2

    ); // assert expect(screen.getByText('H2')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument(); }); test('THEN a H3 can be rendered', () => { // arrange - render(H3); + render(

    H3

    ); // assert expect(screen.getByText('H3')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 3 })).toBeInTheDocument(); }); test('THEN a H4 can be rendered', () => { // arrange - render(H4); + render(

    H4

    ); // assert expect(screen.getByText('H4')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 4 })).toBeInTheDocument(); }); test('THEN a H5 can be rendered', () => { // arrange - render(H5); + render(
    H5
    ); // assert expect(screen.getByText('H5')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 5 })).toBeInTheDocument(); }); test('THEN a H6 can be rendered', () => { // arrange - render(H6); + render(
    H6
    ); // assert expect(screen.getByText('H6')).toBeInTheDocument(); expect(screen.getByRole('heading', { level: 6 })).toBeInTheDocument(); diff --git a/packages/components/src/Markdown/AnchorHeading/__tests__/index.test.tsx b/packages/mdx-components/src/Heading/__tests__/index.test.tsx similarity index 71% rename from packages/components/src/Markdown/AnchorHeading/__tests__/index.test.tsx rename to packages/mdx-components/src/Heading/__tests__/index.test.tsx index 603ac0dc..40b4b13c 100644 --- a/packages/components/src/Markdown/AnchorHeading/__tests__/index.test.tsx +++ b/packages/mdx-components/src/Heading/__tests__/index.test.tsx @@ -1,17 +1,14 @@ -import { describe, expect, test, vi } from 'vitest'; import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvents from '@testing-library/user-event'; -import { withAnchorHeading } from '../index'; -import * as Heading from '../../Heading'; +import { H1 } from '../index'; describe('GIVEN an AnchoredHeading', () => { describe('WHEN rendered', () => { test('THEN the Heading is rendered', () => { // arrange - const TestHeading = withAnchorHeading(Heading.H1); - render(Mosaic Heading); + render(

    Mosaic Heading

    ); // assert expect(screen.getByText('Mosaic Heading')).toBeInTheDocument(); }); @@ -21,18 +18,15 @@ describe('GIVEN an AnchoredHeading', () => { // arrange let clipboardData = ''; // initalizing clipboard data so it can be used in testing const mockClipboard = { - writeText: vi.fn(data => { + writeText: jest.fn(data => { clipboardData = data; }), - readText: vi.fn(() => clipboardData) + readText: jest.fn(() => clipboardData) }; - vi.stubGlobal('navigator', { clipboard: mockClipboard }); + (global.navigator as any).clipboard = mockClipboard; - const TestHeading = withAnchorHeading(Heading.H1); render( - - Mosaic Heading - +

    Mosaic Heading

    ); // assert expect(screen.queryByLabelText('copied anchor link to clipboard')).not.toBeInTheDocument(); diff --git a/packages/mdx-components/src/Heading/index.tsx b/packages/mdx-components/src/Heading/index.tsx new file mode 100644 index 00000000..2e58366c --- /dev/null +++ b/packages/mdx-components/src/Heading/index.tsx @@ -0,0 +1,34 @@ +import classnames from 'clsx'; +import { Typography } from '@jpmorganchase/mosaic-components'; +import { heading } from '@jpmorganchase/mosaic-theme'; +import { AnchorHeading } from './AnchorHeading'; + +const createHeading = + (variant, component) => + ({ children, className, ...props }) => + ( + + {children} + + ); + +const Heading0 = createHeading('heading0', 'h1'); +const Heading1 = createHeading('heading1', 'h1'); +const Heading2 = createHeading('heading2', 'h2'); +const Heading3 = createHeading('heading3', 'h3'); +const Heading4 = createHeading('heading4', 'h4'); +const Heading5 = createHeading('heading5', 'h5'); +const Heading6 = createHeading('heading6', 'h6'); + +export const H0 = props => ; +export const H1 = props => ; +export const H2 = props => ; +export const H3 = props => ; +export const H4 = props => ; +export const H5 = props => ; +export const H6 = props => ; diff --git a/packages/components/src/Markdown/InlineCode.tsx b/packages/mdx-components/src/InlineCode.tsx similarity index 100% rename from packages/components/src/Markdown/InlineCode.tsx rename to packages/mdx-components/src/InlineCode.tsx diff --git a/packages/components/src/Markdown/Link.tsx b/packages/mdx-components/src/Link.tsx similarity index 75% rename from packages/components/src/Markdown/Link.tsx rename to packages/mdx-components/src/Link.tsx index f10d1de2..1b2ad296 100644 --- a/packages/components/src/Markdown/Link.tsx +++ b/packages/mdx-components/src/Link.tsx @@ -1,6 +1,5 @@ import React from 'react'; - -import { Link as LinkComponent, LinkProps } from '../Link'; +import { Link as LinkComponent, LinkProps } from '@jpmorganchase/mosaic-components'; export interface MarkdownLinkProps extends LinkProps { href?: string; diff --git a/packages/components/src/Markdown/Table.tsx b/packages/mdx-components/src/Table.tsx similarity index 100% rename from packages/components/src/Markdown/Table.tsx rename to packages/mdx-components/src/Table.tsx diff --git a/packages/components/src/Markdown/Tbody.tsx b/packages/mdx-components/src/Tbody.tsx similarity index 100% rename from packages/components/src/Markdown/Tbody.tsx rename to packages/mdx-components/src/Tbody.tsx diff --git a/packages/components/src/Markdown/Td.tsx b/packages/mdx-components/src/Td.tsx similarity index 100% rename from packages/components/src/Markdown/Td.tsx rename to packages/mdx-components/src/Td.tsx diff --git a/packages/components/src/Markdown/Th.tsx b/packages/mdx-components/src/Th.tsx similarity index 100% rename from packages/components/src/Markdown/Th.tsx rename to packages/mdx-components/src/Th.tsx diff --git a/packages/components/src/Markdown/Thead.tsx b/packages/mdx-components/src/Thead.tsx similarity index 100% rename from packages/components/src/Markdown/Thead.tsx rename to packages/mdx-components/src/Thead.tsx diff --git a/packages/components/src/Markdown/ThematicBreak/index.css.ts b/packages/mdx-components/src/ThematicBreak/index.css.ts similarity index 100% rename from packages/components/src/Markdown/ThematicBreak/index.css.ts rename to packages/mdx-components/src/ThematicBreak/index.css.ts diff --git a/packages/components/src/Markdown/ThematicBreak/index.tsx b/packages/mdx-components/src/ThematicBreak/index.tsx similarity index 100% rename from packages/components/src/Markdown/ThematicBreak/index.tsx rename to packages/mdx-components/src/ThematicBreak/index.tsx diff --git a/packages/components/src/Markdown/Tr.tsx b/packages/mdx-components/src/Tr.tsx similarity index 100% rename from packages/components/src/Markdown/Tr.tsx rename to packages/mdx-components/src/Tr.tsx diff --git a/packages/mdx-components/src/components.ts b/packages/mdx-components/src/components.ts new file mode 100644 index 00000000..f7343d03 --- /dev/null +++ b/packages/mdx-components/src/components.ts @@ -0,0 +1,166 @@ +import { Ref } from 'react'; +import { + Accordion as MosaicAccordion, + AccordionProps, + Callout as MosaicCallout, + CalloutProps, + Card as MosaicCard, + CardProps, + Cards as MosaicCards, + CardsProps, + ComponentExample as MosaicComponentExample, + ComponentExampleProps, + DataTable as MosaicDataTable, + DataTableProps, + EditionTileLink as MosaicEditionTileLink, + EditionTileLinkProps, + Feature as MosaicFeature, + FeatureProps, + Features as MosaicFeatures, + FeaturesProps, + FilterDropdown as MosaicFilterDropdown, + FilterDropdownProps, + FilterNoResults as MosaicFilterNoResults, + FilterNoResultsProps, + FilterPillGroup as MosaicFilterPillGroup, + FilterPillGroupProps, + FilterResultCount as MosaicFilterResultCount, + FilterResultCountProps, + FilterSearch as MosaicFilterSearch, + FilterSearchProps, + FilterSortDropdown as MosaicFilterSortDropdown, + FilterSortDropdownProps, + FilterToolbar as MosaicFilterToolbar, + FilterToolbarProps, + FilterView as MosaicFilterView, + FilterViewProps, + Grid as MosaicGrid, + GridBase as MosaicGridBase, + GridBaseProps, + GridProps, + HelpLinks as MosaicHelpLinks, + HelpLinksProps, + Icon as MosaicIcon, + IconProps, + Impact as MosaicImpact, + ImpactProps, + Impacts as MosaicImpacts, + ImpactsProps, + Label as MosaicLabel, + LabelProps, + LinkBase as MosaicLinkBase, + LinkBaseProps, + LinkButton as MosaicLinkButton, + LinkButtonProps, + Links as MosaicLinks, + LinksProps, + LinkText as MosaicLinkText, + LinkTextProps, + ListItem as MosaicListItem, + ListItemProps, + OrderedList as MosaicOrderedList, + OrderedListProps, + SecondaryNavbar as MosaicSecondaryNavbar, + SecondaryNavbarProps, + Story as MosaicStory, + StoryProps, + Tabs as MosaicTabs, + TabsProps, + Tag as MosaicTag, + TagProps, + TileBase as MosaicTileBase, + TileBaseProps, + TileButton as MosaicTileButton, + TileButtonProps, + TileContent as MosaicTileContent, + TileContentLabel as MosaicTileContentLabel, + TileContentLabelProps, + TileContentProps, + TileLink as MosaicTileLink, + TileLinkProps, + Tiles as MosaicTiles, + TilesProps, + UnorderedList as MosaicUnorderedList, + UnOrderedListProps, + ViewStack as MosaicViewStack, + ViewStackProps +} from '@jpmorganchase/mosaic-components'; + +import { + OpenAPI as MosaicOpenAPI, + type OpenAPIProps +} from '@jpmorganchase/mosaic-open-api-component'; + +import { withMarkdownSpacing } from './withMarkdownSpacing'; + +export { + AudioPlayer, + AccordionDetails, + AccordionSection, + AccordionSummary, + Button, + EditionFilterView, + FeatureActions, + FeatureContent, + FeatureEyebrow, + FeatureTitle, + Hero, + Link, + SectionHeading, + StickyHeader, + Tab, + TabsBase, + PageFilterView, + VideoPlayer, + View +} from '@jpmorganchase/mosaic-components'; +export * from './markdownElements'; +export * from './typography'; + +export const Accordion = withMarkdownSpacing(MosaicAccordion); +export const Callout = withMarkdownSpacing(MosaicCallout); +export const Card = withMarkdownSpacing(MosaicCard); +export const Cards = withMarkdownSpacing(MosaicCards); +export const ComponentExample = withMarkdownSpacing(MosaicComponentExample); +export const DataTable = withMarkdownSpacing & { ref?: Ref }>( + MosaicDataTable +); +export const EditionTileLink = withMarkdownSpacing(MosaicEditionTileLink); +export const Feature = withMarkdownSpacing(MosaicFeature); +export const Features = withMarkdownSpacing(MosaicFeatures); +export const FilterView = withMarkdownSpacing>(MosaicFilterView); +export const FilterDropdown = withMarkdownSpacing(MosaicFilterDropdown); +export const FilterToolbar = withMarkdownSpacing(MosaicFilterToolbar); +export const FilterNoResults = withMarkdownSpacing(MosaicFilterNoResults); +export const FilterPillGroup = withMarkdownSpacing(MosaicFilterPillGroup); +export const FilterSortDropdown = + withMarkdownSpacing(MosaicFilterSortDropdown); +export const FilterSearch = withMarkdownSpacing(MosaicFilterSearch); +export const FilterResultCount = + withMarkdownSpacing(MosaicFilterResultCount); +export const Grid = withMarkdownSpacing(MosaicGrid); +export const GridBase = withMarkdownSpacing(MosaicGridBase); +export const HelpLinks = withMarkdownSpacing(MosaicHelpLinks); +export const Icon = withMarkdownSpacing(MosaicIcon, 'regular', true); +export const Impact = withMarkdownSpacing(MosaicImpact); +export const Impacts = withMarkdownSpacing(MosaicImpacts); +export const Label = withMarkdownSpacing(MosaicLabel); +export const LinkBase = withMarkdownSpacing(MosaicLinkBase); +export const LinkButton = withMarkdownSpacing(MosaicLinkButton); +export const LinkText = withMarkdownSpacing(MosaicLinkText); +export const Links = withMarkdownSpacing(MosaicLinks); +export const ListItem = withMarkdownSpacing(MosaicListItem); +export const OpenAPI = withMarkdownSpacing(MosaicOpenAPI); +export const OrderedList = withMarkdownSpacing(MosaicOrderedList); +export const Tag = withMarkdownSpacing(MosaicTag); +export const SecondaryNavbar = withMarkdownSpacing(MosaicSecondaryNavbar); +export const Story = withMarkdownSpacing(MosaicStory); +export const Tabs = withMarkdownSpacing(MosaicTabs); +export const Tiles = withMarkdownSpacing(MosaicTiles); +export const TileBase = withMarkdownSpacing(MosaicTileBase); +export const TileButton = withMarkdownSpacing(MosaicTileButton); +export const TileContent = withMarkdownSpacing(MosaicTileContent); +export const TileContentLabel = withMarkdownSpacing(MosaicTileContentLabel); +export const TileLink = withMarkdownSpacing(MosaicTileLink); +export const UnorderedList = withMarkdownSpacing(MosaicUnorderedList); +export const ViewStack = withMarkdownSpacing>(MosaicViewStack); diff --git a/packages/mdx-components/src/index.tsx b/packages/mdx-components/src/index.tsx new file mode 100644 index 00000000..021480a9 --- /dev/null +++ b/packages/mdx-components/src/index.tsx @@ -0,0 +1,3 @@ +'use client'; + +export * from './components'; diff --git a/packages/mdx-components/src/markdownElements.tsx b/packages/mdx-components/src/markdownElements.tsx new file mode 100644 index 00000000..c2c71371 --- /dev/null +++ b/packages/mdx-components/src/markdownElements.tsx @@ -0,0 +1,58 @@ +import { emphasis, link, paragraph } from '@jpmorganchase/mosaic-theme'; +import { + ListItem, + OrderedList, + UnorderedList, + withStyledTypography +} from '@jpmorganchase/mosaic-components'; +import { Image as MosaicImage, ImageProps } from '@jpmorganchase/mosaic-site-components'; + +import { BlockQuote } from './BlockQuote'; +import { InlineCode } from './InlineCode'; +import * as Heading from './Heading'; +import { Link } from './Link'; +import { Table as MosaicTable } from './Table'; +import { Tbody as MosaicTbody } from './Tbody'; +import { Thead as MosaicThead } from './Thead'; +import { Th as MosaicTh } from './Th'; +import { Td as MosaicTd } from './Td'; +import { Tr as MosaicTr } from './Tr'; +import { ThematicBreak } from './ThematicBreak'; +import { withMarkdownSpacing } from './withMarkdownSpacing'; + +export const a = withMarkdownSpacing(Link, link({ context: 'markdown', variant: 'document' })); +export const blockquote = withMarkdownSpacing(BlockQuote); +export const ol = withMarkdownSpacing(OrderedList); +export const ul = withMarkdownSpacing(UnorderedList); +export const li = withMarkdownSpacing(ListItem, 'none'); +export const hr = ThematicBreak; +export const h1 = Heading.H1; +export const h2 = Heading.H2; +export const h3 = Heading.H3; +export const h4 = Heading.H4; +export const h5 = Heading.H5; +export const h6 = Heading.H6; +export const img = withMarkdownSpacing(MosaicImage); +export const Image = img; +export const p = withStyledTypography(paragraph({ variant: 'paragraph2', context: 'markdown' })); +export const inlineCode = withMarkdownSpacing(InlineCode, 'none'); +export const table = withMarkdownSpacing(MosaicTable); +export const Table = table; +export const tbody = withMarkdownSpacing(MosaicTbody, 'none'); +export const Tbody = tbody; +export const thead = withMarkdownSpacing(MosaicThead, 'none'); +export const Thead = thead; +export const th = withMarkdownSpacing(MosaicTh, 'none'); +export const Th = th; +export const td = withMarkdownSpacing(MosaicTd, 'none'); +export const Td = MosaicTd; +export const tr = withMarkdownSpacing(MosaicTr, 'none'); +export const Tr = tr; +export const em = withStyledTypography( + emphasis({ variant: 'regular', context: 'markdown' }), + 'span' +); +export const strong = withStyledTypography( + emphasis({ variant: 'strong', context: 'markdown' }), + 'span' +); diff --git a/packages/mdx-components/src/typography.tsx b/packages/mdx-components/src/typography.tsx new file mode 100644 index 00000000..ca0ec054 --- /dev/null +++ b/packages/mdx-components/src/typography.tsx @@ -0,0 +1,55 @@ +import { + action, + amount, + caption, + eyebrow, + paragraph, + subtitle, + watermark +} from '@jpmorganchase/mosaic-theme'; +import { withStyledTypography } from '@jpmorganchase/mosaic-components'; +import * as markdownElements from './markdownElements'; + +export const Action1 = withStyledTypography(action({ variant: 'action1', context: 'markdown' })); +export const Action2 = withStyledTypography(action({ variant: 'action2', context: 'markdown' })); +export const Action3 = withStyledTypography(action({ variant: 'action3', context: 'markdown' })); +export const Action4 = withStyledTypography(action({ variant: 'action4', context: 'markdown' })); +export const Action5 = withStyledTypography(action({ variant: 'action5', context: 'markdown' })); +export const Action6 = withStyledTypography(action({ variant: 'action6', context: 'markdown' })); +export const Action7 = withStyledTypography(action({ variant: 'action7', context: 'markdown' })); +export const Action8 = withStyledTypography(action({ variant: 'action8', context: 'markdown' })); +export const Caption1 = withStyledTypography(caption({ variant: 'caption1', context: 'markdown' })); +export const Caption2 = withStyledTypography(caption({ variant: 'caption2', context: 'markdown' })); +export const Caption3 = withStyledTypography(caption({ variant: 'caption3', context: 'markdown' })); +export const Caption4 = withStyledTypography(caption({ variant: 'caption4', context: 'markdown' })); +export const Caption5 = withStyledTypography(caption({ variant: 'caption5', context: 'markdown' })); +export const Caption6 = withStyledTypography(caption({ variant: 'caption6', context: 'markdown' })); +export const Hr = markdownElements.hr; +export * from './Heading'; +export const P1 = markdownElements.p; +export const P2 = withStyledTypography(paragraph({ variant: 'paragraph2', context: 'markdown' })); +export const P3 = withStyledTypography(paragraph({ variant: 'paragraph3', context: 'markdown' })); +export const P4 = withStyledTypography(paragraph({ variant: 'paragraph4', context: 'markdown' })); +export const P5 = withStyledTypography(paragraph({ variant: 'paragraph5', context: 'markdown' })); +export const P6 = withStyledTypography(paragraph({ variant: 'paragraph6', context: 'markdown' })); +export const Subtitle1 = withStyledTypography( + subtitle({ variant: 'subtitle1', context: 'markdown' }) +); +export const Subtitle2 = withStyledTypography( + subtitle({ variant: 'subtitle2', context: 'markdown' }) +); +export const Subtitle3 = withStyledTypography( + subtitle({ variant: 'subtitle3', context: 'markdown' }) +); +export const Subtitle4 = withStyledTypography( + subtitle({ variant: 'subtitle4', context: 'markdown' }) +); +export const Subtitle5 = withStyledTypography( + subtitle({ variant: 'subtitle5', context: 'markdown' }) +); +export const Subtitle6 = withStyledTypography( + subtitle({ variant: 'subtitle6', context: 'markdown' }) +); +export const Amount = withStyledTypography(amount({ context: 'markdown' })); +export const Eyebrow = withStyledTypography(eyebrow({ context: 'markdown' })); +export const Watermark = withStyledTypography(watermark({ context: 'markdown' })); diff --git a/packages/components/src/Markdown/withMarkdownSpacing/index.tsx b/packages/mdx-components/src/withMarkdownSpacing/index.tsx similarity index 100% rename from packages/components/src/Markdown/withMarkdownSpacing/index.tsx rename to packages/mdx-components/src/withMarkdownSpacing/index.tsx diff --git a/packages/mdx-components/src/withMarkdownSpacing/styles.css.ts b/packages/mdx-components/src/withMarkdownSpacing/styles.css.ts new file mode 100644 index 00000000..e5ee7008 --- /dev/null +++ b/packages/mdx-components/src/withMarkdownSpacing/styles.css.ts @@ -0,0 +1,7 @@ +import { responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; + +export default { + none: responsiveSprinkles({ marginTop: 'none' }), + regular: responsiveSprinkles({ marginTop: 'x6' }), + inline: responsiveSprinkles({ paddingLeft: 'x1' }) +}; diff --git a/packages/mdx-components/tsconfig.json b/packages/mdx-components/tsconfig.json new file mode 100644 index 00000000..a179de40 --- /dev/null +++ b/packages/mdx-components/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.bundle.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/open-api-component/package.json b/packages/open-api-component/package.json index 5bc1ea88..c6211a53 100644 --- a/packages/open-api-component/package.json +++ b/packages/open-api-component/package.json @@ -39,23 +39,13 @@ "typescript": "^5.0.0" }, "dependencies": { - "@jpmorganchase/mosaic-components": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", "@vanilla-extract/css": "^1.6.0", - "@vanilla-extract/sprinkles": "^1.3.0", - "@vanilla-extract/recipes": "^0.2.1", "@types/swagger-ui-react": "^4.18.0", - "swagger-ui-react": "^5.0.0", - "clsx": "^2.0.0", - "deepmerge": "^2.0.1", - "lodash-es": "^4.17.21", - "react-markdown": "^9.0.0", - "use-memo-one": "^1.1.1", - "warning": "^3.0.0" + "swagger-ui-react": "^5.0.0" }, "peerDependencies": { "@types/react": "^18.3.12", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/plugins/package.json b/packages/plugins/package.json index b976e39a..d64b5e9b 100644 --- a/packages/plugins/package.json +++ b/packages/plugins/package.json @@ -41,9 +41,6 @@ "dependencies": { "@apidevtools/json-schema-ref-parser": "^10.1.0", "@jpmorganchase/mosaic-schemas": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-serialisers": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-source-git-repo": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-source-local-folder": "^0.1.0-beta.89", "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", "@types/github-slugger": "^1.3.0", "check-links": "^2.0.0", @@ -61,12 +58,9 @@ "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", - "rxjs": "^7.5.5", "unified": "^11.0.0", "unist-util-visit": "^5.0.0", "mdast-util-directive": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "uuid": "^7.0.3", - "vfile-reporter": "^7.0.5" + "mdast-util-to-string": "^4.0.0" } } diff --git a/packages/schemas/src/ActiveEnvSchema.ts b/packages/schemas/src/ActiveEnvSchema.ts new file mode 100644 index 00000000..dd8e99cf --- /dev/null +++ b/packages/schemas/src/ActiveEnvSchema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod'; + +export const activeEnvSchema = z.object({ + MOSAIC_ACTIVE_MODE_URL: z.string() +}); + +export type ActiveEnvSchema = z.infer; diff --git a/packages/schemas/src/index.ts b/packages/schemas/src/index.ts index 9c8991a1..7c45ffa4 100644 --- a/packages/schemas/src/index.ts +++ b/packages/schemas/src/index.ts @@ -4,6 +4,7 @@ export * from './fileExtensionSchema.js'; export * from './sidebarSortConfigSchema.js'; export * from './validate.js'; +export * from './ActiveEnvSchema.js'; export * from './SnapshotFileEnvSchema.js'; export * from './MosaicConfigSchema.js'; export * from './PluginModuleSchema.js'; diff --git a/packages/serialisers/package.json b/packages/serialisers/package.json index 287ef76c..a569a253 100644 --- a/packages/serialisers/package.json +++ b/packages/serialisers/package.json @@ -46,11 +46,9 @@ "url": "git@github.com:jpmorganchase/mosaic.git", "directory": "packages/serialisers" }, - "devDependencies": { - "rxjs": "^7.5.5" - }, "dependencies": { "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", - "gray-matter": "^4.0.3" + "gray-matter": "^4.0.3", + "memfs": "^3.4.12" } } diff --git a/packages/site-components-next/package.json b/packages/site-components-next/package.json new file mode 100644 index 00000000..5b682eeb --- /dev/null +++ b/packages/site-components-next/package.json @@ -0,0 +1,53 @@ +{ + "name": "@jpmorganchase/mosaic-site-components-next", + "version": "0.1.0-beta.89", + "license": "Apache-2.0", + "description": "Mosaic - Site components NEXT", + "repository": { + "type": "git", + "url": "git@github.com:jpmorganchase/mosaic.git", + "directory": "packages/site-components" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "node": "./dist/index.js" + } + }, + "scripts": { + "build": "npm-run-all --parallel build:*", + "build:bundle": "node ../../scripts/bundle.mjs", + "build:types": "tsc", + "clean": "npx del-cli 'dist/**' && find . -type d -empty -delete", + "lint": "eslint --ignore-pattern \"**/__tests__/**\"", + "dev": "node ../../scripts/bundle.mjs watch" + }, + "devDependencies": { + "del-cli": "^4.0.1", + "typescript": "^5.2.2" + }, + "dependencies": { + "@jpmorganchase/mosaic-mdx-components": "0.1.0-beta.89", + "@jpmorganchase/mosaic-components": "0.1.0-beta.89", + "@jpmorganchase/mosaic-site-components": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-loaders": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", + "@types/mdx": "^2.0.0", + "clsx": "^2.0.0", + "rehype-pretty-code": "0.13.0", + "rehype-slug": "^6.0.0", + "remark-gfm": "^4.0.0", + "server-only": "^0.0.1", + "shiki": "^1.1.1", + "next-mdx-remote": "^5.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.46", + "next-auth": "^4.22.1", + "react": "^18.3.0", + "react-dom": "^18.3.0" + } +} diff --git a/packages/site-components-next/src/AppHeader/index.tsx b/packages/site-components-next/src/AppHeader/index.tsx new file mode 100644 index 00000000..30292588 --- /dev/null +++ b/packages/site-components-next/src/AppHeader/index.tsx @@ -0,0 +1,117 @@ +import { Suspense } from 'react'; +import { loadSharedConfig } from '@jpmorganchase/mosaic-loaders'; +import { AppHeader as UI } from '@jpmorganchase/mosaic-site-components'; +import { TabsLinkItem, TabsMenu, TabsMenuButtonItem } from '@jpmorganchase/mosaic-components'; +import { SearchInput } from '../SearchInput'; + +/** + * [[`MenuItemType`]] defines the type of App header Menu items + */ +export type MenuItemType = 'menu' | 'link'; + +function isMenu(menu: MenuLinkItem | MenuLinksItem): menu is MenuLinksItem { + return (menu as MenuLinksItem).links !== undefined; +} +function isMenuLink(menu: MenuLinkItem | MenuLinksItem): menu is MenuLinkItem { + return (menu as MenuLinkItem).link !== undefined; +} + +/** + * [[`Menu`]] defines App Header menus + */ +export type Menu = MenuLinksItem | MenuLinkItem; + +/** + * [[`MenuLinksItem`]] defines a menu of links + */ +export interface MenuLinksItem { + /** Collection of link options */ + links: MenuLinkItem[]; + /** Title of MenuLinksItem */ + title: string; + /** Type of MenuLinksItem */ + type: 'menu'; +} + +/** + * [[`MenuLinksItem`]] defines a menu of links and includes a type + */ +export interface AppHeaderMenuLinksItem { + /** Collection of link options */ + links: MenuLinkItem[]; + /** Title of Tab */ + title: string; + /** Type of Tab */ + type: 'menu'; +} + +/** + * [[`MenuLinkItem`]] define a menu link + */ +export interface MenuLinkItem { + /** URL linked by Tab */ + link: string; + /** Title of Tab */ + title?: string; + /** Type of MenuLinkItem */ + type: 'link'; +} + +/** + * [[`MenuLinkItem`]] define a menu link and include a type + */ +export interface AppHeaderMenuLinkItem { + /** URL linked by Tab */ + link: string; + /** Title of Tab */ + title?: string; + /** Type of Tab */ + type: 'link'; +} + +export type AppHeaderMenu = Array; + +function createTabsMenu(menu: AppHeaderMenu): TabsMenu { + const tabsMenu = menu.reduce<(TabsMenuButtonItem | TabsLinkItem)[]>((result, menuItem) => { + if (menu && isMenu(menuItem)) { + const tabsLinksItem: TabsMenuButtonItem = { + title: menuItem.title, + type: 'menu', + links: menuItem.links.map(({ title, link }: MenuLinkItem) => ({ + type: 'link', + title, + link + })), + onSelect: () => undefined + }; + return [...result, tabsLinksItem]; + } else if (menuItem && isMenuLink(menuItem)) { + const tabsLinkItem: TabsLinkItem = { + title: menuItem.title, + type: 'link', + link: menuItem.link + }; + return [...result, tabsLinkItem]; + } + console.error('Unknown Menu item passed to createTabsMenu, ignoring', menu); + return result; + }, []); + return tabsMenu; +} + +export async function AppHeader({ path }: { path: string }) { + const sharedConfig = await loadSharedConfig(path); + const headerConfig = sharedConfig?.header; + const { homeLink, logo, menu: menuItems = [], title } = headerConfig || {}; + + const tabsMenu = createTabsMenu(menuItems); + + return ( + + Loading Search
    }> + {/* @ts-expect-error Server Component */} + + + + ); +} diff --git a/packages/site-components-next/src/Breadcrumbs/index.tsx b/packages/site-components-next/src/Breadcrumbs/index.tsx new file mode 100644 index 00000000..40078e71 --- /dev/null +++ b/packages/site-components-next/src/Breadcrumbs/index.tsx @@ -0,0 +1,8 @@ +import { loadPage } from '@jpmorganchase/mosaic-loaders'; +import { Breadcrumbs as UI } from '@jpmorganchase/mosaic-site-components'; + +export async function Breadcrumbs({ path, loader }: { path: string; loader: typeof loadPage }) { + const { data } = await loader(path); + const breadcrumbs = data?.breadcrumbs || []; + return 2} />; +} diff --git a/packages/site-components-next/src/DocPaginator/index.tsx b/packages/site-components-next/src/DocPaginator/index.tsx new file mode 100644 index 00000000..ca29c333 --- /dev/null +++ b/packages/site-components-next/src/DocPaginator/index.tsx @@ -0,0 +1,16 @@ +import { loadPage } from '@jpmorganchase/mosaic-loaders'; +import { DocPaginator as UI } from '@jpmorganchase/mosaic-site-components'; + +export async function DocPaginator({ + path, + loader, + linkSuffix = 'Page' +}: { + path: string; + loader: typeof loadPage; + linkSuffix: string; +}) { + const { data } = await loader(path); + const { next, prev } = data?.navigation || {}; + return ; +} diff --git a/packages/site-components-next/src/EditionFilterView/index.tsx b/packages/site-components-next/src/EditionFilterView/index.tsx new file mode 100644 index 00000000..14df8fff --- /dev/null +++ b/packages/site-components-next/src/EditionFilterView/index.tsx @@ -0,0 +1,17 @@ +import type { EditionFilterViewProps } from '@jpmorganchase/mosaic-components'; +import { EditionFilterView as UI } from '@jpmorganchase/mosaic-mdx-components'; +import { MDXContent } from '../MDXContent'; +import { mdxElements } from '../mdx/elements'; + +export async function EditionFilterView({ view, ...restProps }: EditionFilterViewProps) { + const formattedView = view.map(item => { + const formattedDescription = ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + /** @ts-ignore Server Component */ + + ); + + return item.formattedDescription ? { ...item, formattedDescription } : item; + }); + return ; +} diff --git a/packages/site-components-next/src/Footer/index.tsx b/packages/site-components-next/src/Footer/index.tsx new file mode 100644 index 00000000..e501fa7a --- /dev/null +++ b/packages/site-components-next/src/Footer/index.tsx @@ -0,0 +1,8 @@ +import { loadSharedConfig } from '@jpmorganchase/mosaic-loaders'; +import { Footer as UI } from '@jpmorganchase/mosaic-site-components'; + +export async function Footer({ path }: { path: string }) { + const sharedConfig = await loadSharedConfig(path); + const footerProps = sharedConfig?.footer; + return ; +} diff --git a/packages/site-components-next/src/MDXContent/index.tsx b/packages/site-components-next/src/MDXContent/index.tsx new file mode 100644 index 00000000..43eb2fd1 --- /dev/null +++ b/packages/site-components-next/src/MDXContent/index.tsx @@ -0,0 +1,12 @@ +import { CompileOptions, compile } from '../actions/compile'; + +export type MDXContentProps = CompileOptions; + +export async function MDXContent(props: MDXContentProps) { + if (props.source === undefined) { + return null; + } + + const content = await compile(props); + return content; +} diff --git a/packages/site-components-next/src/SearchInput/index.tsx b/packages/site-components-next/src/SearchInput/index.tsx new file mode 100644 index 00000000..551ab07b --- /dev/null +++ b/packages/site-components-next/src/SearchInput/index.tsx @@ -0,0 +1,13 @@ +import { ReactNode } from 'react'; +import { SearchInput as UI } from '@jpmorganchase/mosaic-site-components'; +import { loadMosaicData } from '@jpmorganchase/mosaic-loaders'; +import type { SearchConfig, SearchIndex } from '@jpmorganchase/mosaic-types'; + +export async function SearchInput(): Promise { + const [searchConfig, searchIndex] = await Promise.all([ + loadMosaicData('search-config.json'), + loadMosaicData('search-data.json') + ]); + + return ; +} diff --git a/packages/site-components-next/src/Sidebar/index.tsx b/packages/site-components-next/src/Sidebar/index.tsx new file mode 100644 index 00000000..4d5339c1 --- /dev/null +++ b/packages/site-components-next/src/Sidebar/index.tsx @@ -0,0 +1,18 @@ +import type { loadPage } from '@jpmorganchase/mosaic-loaders'; +import { PageNavigation as UI } from '@jpmorganchase/mosaic-site-components'; +import type { Breadcrumb } from '@jpmorganchase/mosaic-types'; + +function getIds(breadcrumbs: Breadcrumb[]) { + return new Set(breadcrumbs.map(({ id }) => id.substr(0, id.lastIndexOf('.')))); +} + +export async function Sidebar({ path, loader }: { path: string; loader: typeof loadPage }) { + const { data } = await loader(path); + const props = { + menu: data?.sidebarData || [], + selectedNodeId: data?.route, + selectedGroupIds: getIds(data?.breadcrumbs || []) + }; + + return ; +} diff --git a/packages/site-components-next/src/TOC/index.tsx b/packages/site-components-next/src/TOC/index.tsx new file mode 100644 index 00000000..8765aa57 --- /dev/null +++ b/packages/site-components-next/src/TOC/index.tsx @@ -0,0 +1,16 @@ +import { loadPage } from '@jpmorganchase/mosaic-loaders'; +import { TableOfContents as UI, type TOCItem } from '@jpmorganchase/mosaic-site-components'; + +export async function TableOfContents({ + path, + loader, + items +}: { + path: string; + loader: typeof loadPage; + items?: TOCItem[]; +}) { + const { data } = await loader(path); + const tableOfContents = data?.tableOfContents || []; + return ; +} diff --git a/packages/site-components-next/src/actions/compile.ts b/packages/site-components-next/src/actions/compile.ts new file mode 100644 index 00000000..9e5fd8f6 --- /dev/null +++ b/packages/site-components-next/src/actions/compile.ts @@ -0,0 +1,49 @@ +import { compileMDX } from 'next-mdx-remote/rsc'; +import remarkGfm from 'remark-gfm'; +import rehypeSlug from 'rehype-slug'; +import rehypePrettyCode, { type Options } from 'rehype-pretty-code'; +import type { MDXComponents } from 'mdx/types'; +import type { SiteState } from '@jpmorganchase/mosaic-loaders'; + +const defaultLangPrettyCodeOptions: Partial = { + theme: { dark: 'night-owl', light: 'light-plus' }, + defaultLang: 'js', + keepBackground: false +}; + +export type CompileOptions = { + source: string; + components: MDXComponents; + data?: Partial; + rehypePlugins?: any[]; + remarkPlugins?: any[]; + parseFrontmatter?: boolean; + prettyCodeOptions?: Options; +}; + +export async function compile({ + source, + data = {}, + components, + rehypePlugins = [], + remarkPlugins = [], + parseFrontmatter = false, + prettyCodeOptions = defaultLangPrettyCodeOptions +}: CompileOptions) { + const { content } = await compileMDX({ + source, + components, + options: { + scope: { meta: data }, + mdxOptions: { + rehypePlugins: [rehypeSlug, [rehypePrettyCode, prettyCodeOptions], ...rehypePlugins], + remarkPlugins: [remarkGfm, ...remarkPlugins] + }, + parseFrontmatter + } + }); + + return content; +} + +export type CompileAction = typeof compile; diff --git a/packages/site-components-next/src/actions/preview.ts b/packages/site-components-next/src/actions/preview.ts new file mode 100644 index 00000000..2aabb44d --- /dev/null +++ b/packages/site-components-next/src/actions/preview.ts @@ -0,0 +1,37 @@ +import { SiteState } from '@jpmorganchase/mosaic-loaders'; +import { compile, type CompileOptions } from './compile'; +import { mdxComponents } from '../mdx'; + +export type PreviewActionOptions = { + source: string; + data?: Partial; + components?: CompileOptions['components']; +}; + +/** + * + * This is a wrapper around the compile function that handles errors. + * + * When previewing content in the editor we expect errors + * so want to show them in toasts and not redirect to the main error page + */ +export async function preview({ + components = mdxComponents, + ...restOptions +}: PreviewActionOptions) { + try { + const result = await compile({ ...restOptions, components }); + return { result }; + } catch (e) { + if (e instanceof Error) { + return { + result: undefined, + error: e.message + }; + } + + throw e; + } +} + +export type PreviewAction = typeof preview; diff --git a/packages/site-components-next/src/index.ts b/packages/site-components-next/src/index.ts new file mode 100644 index 00000000..d4ef8940 --- /dev/null +++ b/packages/site-components-next/src/index.ts @@ -0,0 +1,14 @@ +import 'server-only'; +import { compile, CompileAction } from './actions/compile'; +import { preview, PreviewAction } from './actions/preview'; + +export * from './AppHeader'; +export * from './Breadcrumbs'; +export * from './DocPaginator'; +export * from './Footer'; +export * from './Sidebar'; +export * from './TOC'; +export * from './mdx'; +export * from './MDXContent'; + +export { compile, type CompileAction, preview, type PreviewAction }; diff --git a/packages/site-components-next/src/mdx/elements.ts b/packages/site-components-next/src/mdx/elements.ts new file mode 100644 index 00000000..bc6f8573 --- /dev/null +++ b/packages/site-components-next/src/mdx/elements.ts @@ -0,0 +1,34 @@ +import { + a, + em, + h1, + h2, + h3, + h4, + h5, + h6, + hr, + li, + ol, + p, + ul, + inlineCode +} from '@jpmorganchase/mosaic-mdx-components'; +import type { MDXComponents } from 'mdx/types'; + +export const mdxElements = { + a, + em, + h1, + h2, + h3, + h4, + h5, + h6, + hr, + li, + ol, + p, + ul, + code: inlineCode +} as unknown as MDXComponents; diff --git a/packages/site-components-next/src/mdx/index.tsx b/packages/site-components-next/src/mdx/index.tsx new file mode 100644 index 00000000..940ea448 --- /dev/null +++ b/packages/site-components-next/src/mdx/index.tsx @@ -0,0 +1,239 @@ +import type { MDXComponents } from 'mdx/types'; + +import { + Accordion, + AccordionDetails, + AccordionSection, + AccordionSummary, + Action1, + Action2, + Action3, + Action4, + Action5, + Action6, + Action7, + Action8, + Amount, + AudioPlayer, + Button, + blockquote, + Callout, + Caption1, + Caption2, + Caption3, + Caption4, + Caption5, + Caption6, + Card, + Cards, + ComponentExample, + DataTable, + EditionTileLink, + Eyebrow, + Feature, + FeatureActions, + FeatureContent, + FeatureEyebrow, + FeatureTitle, + Features, + FilterView, + FilterDropdown, + FilterToolbar, + FilterNoResults, + FilterPillGroup, + FilterSortDropdown, + FilterSearch, + FilterResultCount, + Grid, + GridBase, + H0, + H1, + H2, + H3, + H4, + H5, + H6, + HelpLinks, + Hero, + inlineCode, + Icon, + Impact, + Impacts, + Label, + Link, + LinkBase, + LinkButton, + LinkText, + Links, + ListItem, + OpenAPI, + OrderedList, + P1, + P2, + P3, + P4, + P5, + P6, + PageFilterView, + SecondaryNavbar, + SectionHeading, + strong, + StickyHeader, + Story, + Subtitle1, + Subtitle2, + Subtitle3, + Subtitle4, + Subtitle5, + Subtitle6, + table, + Table, + Tag, + Tabs, + Tab, + TabsBase, + tbody, + Tbody, + td, + Td, + thead, + Thead, + th, + Th, + Tiles, + TileBase, + TileButton, + TileContent, + TileContentLabel, + TileLink, + tr, + Tr, + UnorderedList, + View, + ViewStack, + VideoPlayer, + Watermark +} from '@jpmorganchase/mosaic-mdx-components'; + +import { EditionFilterView } from '../EditionFilterView'; + +import { mdxElements } from './elements'; + +export const mdxComponents = { + ...mdxElements, + Accordion, + AccordionDetails, + AccordionSection, + AccordionSummary, + Action1, + Action2, + Action3, + Action4, + Action5, + Action6, + Action7, + Action8, + Amount, + AudioPlayer, + Button, + blockquote, + Callout, + Caption1, + Caption2, + Caption3, + Caption4, + Caption5, + Caption6, + Card, + Cards, + ComponentExample, + DataTable, + Emphasis: mdxElements.em, + EditionFilterView, + EditionTileLink, + Eyebrow, + Feature, + FeatureActions, + FeatureContent, + FeatureEyebrow, + FeatureTitle, + Features, + FilterView, + FilterDropdown, + FilterToolbar, + FilterNoResults, + FilterPillGroup, + FilterSortDropdown, + FilterSearch, + FilterResultCount, + Grid, + GridBase, + H0, + H1, + H2, + H3, + H4, + H5, + H6, + HelpLinks, + Hero, + inlineCode, + Icon, + Impact, + Impacts, + Label, + Link, + LinkBase, + LinkButton, + LinkText, + Links, + ListItem, + OpenAPI, + OrderedList, + P1, + P2, + P3, + P4, + P5, + P6, + PageFilterView, + SecondaryNavbar, + SectionHeading, + strong, + Strong: strong, + StickyHeader, + Story, + Subtitle1, + Subtitle2, + Subtitle3, + Subtitle4, + Subtitle5, + Subtitle6, + table, + Table, + Tag, + Tabs, + Tab, + TabsBase, + tbody, + Tbody, + td, + Td, + thead, + Thead, + th, + Th, + Tiles, + TileBase, + TileButton, + TileContent, + TileContentLabel, + TileLink, + tr, + Tr, + UnorderedList, + View, + VideoPlayer, + ViewStack, + Watermark +} as unknown as MDXComponents; diff --git a/packages/site-components-next/tsconfig.json b/packages/site-components-next/tsconfig.json new file mode 100644 index 00000000..a179de40 --- /dev/null +++ b/packages/site-components-next/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.bundle.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/site-components/package.json b/packages/site-components/package.json index 642ffc76..7cb5b211 100644 --- a/packages/site-components/package.json +++ b/packages/site-components/package.json @@ -33,8 +33,6 @@ "dist" ], "devDependencies": { - "@types/styled-components": "^5.1.26", - "@vanilla-extract/esbuild-plugin": "^2.3.11", "del-cli": "^4.0.1", "next-router-mock": "^0.9.13", "typescript": "^5.0.0" @@ -42,39 +40,21 @@ "dependencies": { "@jpmorganchase/mosaic-components": "^0.1.0-beta.89", "@jpmorganchase/mosaic-content-editor-plugin": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-labs-components": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-open-api-component": "^0.1.0-beta.89", - "@jpmorganchase/mosaic-site-middleware": "^0.1.0-beta.89", "@jpmorganchase/mosaic-store": "^0.1.0-beta.89", "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0", - "@salt-ds/lab": "1.0.0-alpha.50", - "@types/mdast": "^3.0.0", + "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", + "@salt-ds/core": "^1.37.1", + "@salt-ds/lab": "1.0.0-alpha.54", "@vanilla-extract/css": "^1.6.0", - "@vanilla-extract/recipes": "^0.2.1", - "@vanilla-extract/sprinkles": "^1.3.0", - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", "clsx": "^2.0.0", - "deepmerge": "^4.2.2", "fuse.js": "^6.6.2", - "https-proxy-agent": "^5.0.1", - "jwt-decode": "^3.1.2", "lodash-es": "^4.17.21", - "next": "^14.0.0", - "next-mdx-remote": "^5.0.0", - "node-cookie": "^2.1.2", - "react-error-boundary": "^4.0.11", - "rehype-slug": "^6.0.0", - "swr": "^2.1.2", - "unified": "^11.0.0", - "unist-util-visit": "^5.0.0", - "warning": "^3.0.0" + "next": "^14.0.0" }, "peerDependencies": { "@types/react": "^18.3.12", "next-auth": "^4.22.1", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/site-components/src/404.tsx b/packages/site-components/src/404.tsx index c2e9e36b..8f6cd3cf 100644 --- a/packages/site-components/src/404.tsx +++ b/packages/site-components/src/404.tsx @@ -1,12 +1,9 @@ -import { Hero } from '@jpmorganchase/mosaic-components'; +import React from 'react'; +import { Hero, HeroProps } from '@jpmorganchase/mosaic-components'; -export function Page404() { - return ( - - ); -} +export const Page404: React.FC = ({ + children, + description = "Sorry, looks like something's wrong here.", + title = 'Page Not Found', + ...rest +}) => ; diff --git a/packages/site-components/src/500.tsx b/packages/site-components/src/500.tsx index 1a93a283..88c1aa63 100644 --- a/packages/site-components/src/500.tsx +++ b/packages/site-components/src/500.tsx @@ -1,12 +1,13 @@ -import { Hero } from '@jpmorganchase/mosaic-components'; +import React from 'react'; +import { Hero, HeroProps } from '@jpmorganchase/mosaic-components'; -export function Page500() { - return ( - - ); -} +export const Page500: React.FC = ({ + children, + description = 'A 500 error occurred.', + title = 'Whoops! something went wrong', + ...rest +}) => ( + + {children} + +); diff --git a/packages/site-components/src/AppHeader/AppHeader.tsx b/packages/site-components/src/AppHeader/AppHeader.tsx index 4a1d9128..2b4b78e8 100644 --- a/packages/site-components/src/AppHeader/AppHeader.tsx +++ b/packages/site-components/src/AppHeader/AppHeader.tsx @@ -1,9 +1,8 @@ -import { FC } from 'react'; +import { FC, ReactNode } from 'react'; import { Text } from '@salt-ds/core'; import { Logo, LogoImage } from '@salt-ds/lab'; import { useBreakpoint, Link } from '@jpmorganchase/mosaic-components'; import type { TabsMenu } from '@jpmorganchase/mosaic-components'; -import { useRoute } from '@jpmorganchase/mosaic-store'; import { AppHeaderControls } from '../AppHeaderControls'; import { AppHeaderDrawer } from '../AppHeaderDrawer'; @@ -11,6 +10,7 @@ import { AppHeaderTabs } from '../AppHeaderTabs'; import styles from './styles.css'; export type AppHeaderProps = { + children?: ReactNode; homeLink?: string; logo?: string; menu?: TabsMenu; @@ -31,9 +31,8 @@ const createDrawerMenu = menu => return [...result, parsedItem]; }, []); -export const AppHeader: FC = ({ homeLink, logo, menu = [], title }) => { +export const AppHeader: FC = ({ children, homeLink, logo, menu = [], title }) => { const breakpoint = useBreakpoint(); - const { route } = useRoute(); const showDrawer = breakpoint === 'mobile' || breakpoint === 'tablet'; return ( @@ -50,8 +49,8 @@ export const AppHeader: FC = ({ homeLink, logo, menu = [], title )} )} - {!showDrawer && } - + {!showDrawer && } + {children} ); diff --git a/packages/site-components/src/AppHeader/index.ts b/packages/site-components/src/AppHeader/index.ts index dc1ce3b8..139cb8b9 100644 --- a/packages/site-components/src/AppHeader/index.ts +++ b/packages/site-components/src/AppHeader/index.ts @@ -1,5 +1 @@ -import { withAppHeaderAdapter } from './withAppHeaderAdapter'; -import { AppHeader as OriginalAppHeader } from './AppHeader'; - -export { withAppHeaderAdapter } from './withAppHeaderAdapter'; -export const AppHeader = withAppHeaderAdapter(OriginalAppHeader); +export * from './AppHeader'; diff --git a/packages/site-components/src/AppHeader/withAppHeaderAdapter.tsx b/packages/site-components/src/AppHeader/withAppHeaderAdapter.tsx deleted file mode 100644 index 909a9a77..00000000 --- a/packages/site-components/src/AppHeader/withAppHeaderAdapter.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { TabsLinkItem, TabsMenu, TabsMenuButtonItem } from '@jpmorganchase/mosaic-components'; -import { TabMenuItemType } from '@jpmorganchase/mosaic-components'; -import type { - MenuLinkItem, - AppHeaderMenuLinksItem, - AppHeaderMenuLinkItem -} from '@jpmorganchase/mosaic-store'; -import { useAppHeader, AppHeaderMenu, MenuItemType } from '@jpmorganchase/mosaic-store'; - -function createTabsMenu(menu: AppHeaderMenu): TabsMenu { - const tabsMenu = menu.reduce<(TabsMenuButtonItem | TabsLinkItem)[]>((result, menuItem) => { - const menu = menuItem as AppHeaderMenuLinksItem; - const link = menuItem as AppHeaderMenuLinkItem; - if (menu && menu.type === MenuItemType.MENU) { - const tabsLinksItem: TabsMenuButtonItem = { - title: menu.title, - type: TabMenuItemType.MENU, - links: menu.links.map(({ title, link }: MenuLinkItem) => ({ - type: TabMenuItemType.LINK, - title, - link - })), - onSelect: () => undefined - }; - return [...result, tabsLinksItem]; - } else if (link && link.type === MenuItemType.LINK) { - const tabsLinkItem: TabsLinkItem = { - title: link.title, - type: TabMenuItemType.LINK, - link: link.link - }; - return [...result, tabsLinkItem]; - } - console.error('Unknown Menu item passed to createTabsMenu, ignoring', menu); - return result; - }, []); - return tabsMenu; -} - -export const withAppHeaderAdapter = Component => () => { - const headerConfig = useAppHeader(); - const { homeLink, logo, menu: menuItems = [], title } = headerConfig || {}; - const tabsMenu = createTabsMenu(menuItems); - return ; -}; diff --git a/packages/site-components/src/AppHeaderControls/index.tsx b/packages/site-components/src/AppHeaderControls/index.tsx index 17844f04..4000484a 100644 --- a/packages/site-components/src/AppHeaderControls/index.tsx +++ b/packages/site-components/src/AppHeaderControls/index.tsx @@ -1,12 +1,11 @@ -import React from 'react'; +import { ReactNode } from 'react'; import { Icon, Link, Button } from '@jpmorganchase/mosaic-components'; import { Menu, MenuTrigger, MenuPanel, MenuItem } from '@salt-ds/core'; import { useContentEditor, EditorControls } from '@jpmorganchase/mosaic-content-editor-plugin'; -import { useColorMode, useSearchIndex, useStoreActions } from '@jpmorganchase/mosaic-store'; +import { useColorMode } from '@jpmorganchase/mosaic-store'; import { useSession } from 'next-auth/react'; import { UserProfile } from '../UserProfile'; -import { SearchInput } from '../SearchInput'; import styles from './styles.css'; type ActionMenuItem = { @@ -19,15 +18,13 @@ function toUpperFirst(str) { return `${str.charAt(0).toUpperCase()}${str.slice(1)}`; } -export const AppHeaderControls: React.FC = () => { - const colorMode = useColorMode(); - const { setColorMode } = useStoreActions(); +export const AppHeaderControls: React.FC<{ children?: ReactNode }> = ({ children }) => { + const { colorMode, setColorMode } = useColorMode(); const { data: session } = useSession(); const isLoginEnabled = process.env.NEXT_PUBLIC_ENABLE_LOGIN === 'true' || false; const isLoggedIn = session != null; const { pageState, startEditing, stopEditing } = useContentEditor(); - const { searchEnabled } = useSearchIndex(); const inverseColorMode = colorMode === 'dark' ? 'light' : 'dark'; let actionMenuOptions: ActionMenuItem[] = [ @@ -63,7 +60,7 @@ export const AppHeaderControls: React.FC = () => { return (
    {isLoginEnabled && } - {searchEnabled && } + {children} {isLoginEnabled && (
    {isLoggedIn ? ( diff --git a/packages/site-components/src/AppHeaderTabs/index.tsx b/packages/site-components/src/AppHeaderTabs/index.tsx index 4e007b43..d3444801 100644 --- a/packages/site-components/src/AppHeaderTabs/index.tsx +++ b/packages/site-components/src/AppHeaderTabs/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { useRouter } from 'next/router'; -import { hasProtocol, TabsBase, TabMenuItemType } from '@jpmorganchase/mosaic-components'; +import { useRouter, usePathname } from 'next/navigation'; +import { hasProtocol, TabsBase } from '@jpmorganchase/mosaic-components'; import type { TabsMenu, TabsMenuButtonItem, TabsLinkItem } from '@jpmorganchase/mosaic-components'; import { useWindowResize, Size } from './useWindowResize'; @@ -12,7 +12,7 @@ function resolveSelectedIndex(menu, itemPath) { let longestMatch = 0; for (let i = 0; i < menu.length; i++) { const item: TabsMenuButtonItem | TabsLinkItem = menu[i]; - if (item.type === TabMenuItemType.MENU) { + if (item.type === 'menu') { // eslint-disable-next-line no-restricted-syntax for (const { link: subLink } of item.links) { // If menu link matches the current route - we can return this index @@ -31,11 +31,15 @@ function resolveSelectedIndex(menu, itemPath) { if (item.link === itemPath) { return i; } + + // remove "/index" from the end of the item link + const indexTrimmedLinkPath = item.link.slice(0, -'/index'.length); + // If current item is contained within part of route (e.g. /case-studies when route is /case-studies/item) // and the link will be the longest matching one we've seen so far (longest means closest match) - if (itemPath.startsWith(item.link) && item.link.length > longestMatch) { + if (itemPath.startsWith(indexTrimmedLinkPath) && indexTrimmedLinkPath.length > longestMatch) { selectedIndex = i; - longestMatch = item.link.length; + longestMatch = indexTrimmedLinkPath.length; } } } @@ -44,6 +48,7 @@ function resolveSelectedIndex(menu, itemPath) { export function AppHeaderTabs({ menu = [] }: { menu: TabsMenu }) { const router = useRouter(); + const pathname = usePathname(); const size: Size = useWindowResize(); const getSelectedTabIndex = React.useCallback( itemPath => resolveSelectedIndex(menu, itemPath), @@ -57,23 +62,12 @@ export function AppHeaderTabs({ menu = [] }: { menu: TabsMenu }) { setSelectionIndex(currentSelection); }; - const handleRouteChangeComplete = (newRoute: string) => updateSelection(newRoute); - - useEffect(() => { - router.events.on('routeChangeComplete', handleRouteChangeComplete); - - return () => { - router.events.off('routeChangeComplete', handleRouteChangeComplete); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - useEffect(() => { - if (router.asPath && size?.width) { - updateSelection(router.asPath); + if (pathname && size?.width) { + updateSelection(pathname); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router, size]); + }, [pathname, size]); const handleMenuSelect = (_event, sourceItem) => { const { link } = sourceItem as TabsLinkItem; @@ -84,7 +78,7 @@ export function AppHeaderTabs({ menu = [] }: { menu: TabsMenu }) { } }; const linkedMenu = menu.map(menuItem => { - if (menuItem.type === TabMenuItemType.MENU) { + if (menuItem.type === 'menu') { return { ...menuItem, onSelect: handleMenuSelect }; } return menuItem; diff --git a/packages/site-components/src/BaseUrlProvider.tsx b/packages/site-components/src/BaseUrlProvider.tsx deleted file mode 100644 index 05c68cde..00000000 --- a/packages/site-components/src/BaseUrlProvider.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createContext, Context, useContext } from 'react'; -import { hasProtocol } from '@jpmorganchase/mosaic-components'; -import { useRoute } from '@jpmorganchase/mosaic-store'; - -// The pathname of the current route. We don't get this from router, as we want the full -// path including the /index on index pages. -export type BaseUrlValue = string; - -export const BaseUrlContext: Context = createContext('/'); -export const BaseUrlProvider = ({ children }) => { - const { route = '/' } = useRoute(); - return {children}; -}; - -function resolveRelativeUrl(href: string, baseRoute: string) { - if (hasProtocol(href) || href.startsWith('#') || !href.startsWith('.')) { - return href; - } - if (!baseRoute) { - throw new Error('Cannot resolve relative url as base route is undefined'); - } - const url = new URL('http://jpmorgan.com'); - const anchorMatches = href.match(/(.*)#(.*)/); - if (anchorMatches && anchorMatches[2]) { - // Anchors will get encoded to %23 if set on URL - url.pathname = `${baseRoute}/../${anchorMatches[1]}`; - return `${url.pathname}#${anchorMatches[2]}`; - } - url.pathname = `${baseRoute}/../${href}`; - return url.pathname; -} - -export const useBaseRoute = () => useContext(BaseUrlContext); - -export const useResolveRelativeUrl = href => { - const baseRoute = useContext(BaseUrlContext); - - return resolveRelativeUrl(href, baseRoute); -}; diff --git a/packages/site-components/src/Body.tsx b/packages/site-components/src/Body.tsx deleted file mode 100644 index 64efc5f8..00000000 --- a/packages/site-components/src/Body.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { useEffect } from 'react'; -import { MDXRemote } from 'next-mdx-remote'; -import { ErrorBoundary, useErrorBoundary } from 'react-error-boundary'; -import { useContentEditor, Editor } from '@jpmorganchase/mosaic-content-editor-plugin'; -import { useSession } from 'next-auth/react'; -import { useRouter } from 'next/router'; - -import { createMDXScope } from './utils/createMDXScope'; -import { Page500 } from './500'; -import { Page404 } from './404'; - -const DefaultFallBackComponent = ({ error: { message: errorMessage = 'unknown' } }) => { - const router = useRouter(); - const { resetBoundary } = useErrorBoundary(); - - useEffect(() => { - const handleRouteChange = () => { - resetBoundary(); - }; - - router.events.on('routeChangeComplete', handleRouteChange); - - return () => { - router.events.off('routeChangeComplete', handleRouteChange); - }; - }, [router, resetBoundary]); - console.error('An un-handled error created a 500 message'); - console.error(errorMessage); - return ; -}; - -function MDXRemoteWithErrorBoundary({ components, source, meta = {} }) { - return ( - - - - ); -} - -export function Body({ components = {}, type, ...props }) { - const { pageState } = useContentEditor(); - const { data: session } = useSession(); - - if (props.show404) { - return ; - } - if (props.show500) { - return ; - } - - if (pageState !== 'VIEW' && session !== null && type === 'mdx') { - return ( - - ); - } - - if (type === 'mdx') { - return ( -
    - -
    - ); - } - // If file is JSON, we expect it to have a `content` attr - if (type === 'json') { - return
    {props.content}
    ; - } - return
    Unsupported file type
    ; -} diff --git a/packages/site-components/src/Breadcrumbs/Breadcrumbs.tsx b/packages/site-components/src/Breadcrumbs/Breadcrumbs.tsx index f3dcfbeb..e1f7a0d3 100644 --- a/packages/site-components/src/Breadcrumbs/Breadcrumbs.tsx +++ b/packages/site-components/src/Breadcrumbs/Breadcrumbs.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useRouter } from 'next/router'; +import { useRouter } from 'next/navigation'; import { Menu, MenuItem, MenuPanel, MenuTrigger } from '@salt-ds/core'; import { Button, Icon } from '@jpmorganchase/mosaic-components'; diff --git a/packages/site-components/src/Breadcrumbs/index.ts b/packages/site-components/src/Breadcrumbs/index.ts index f9c87f55..e434f4cb 100644 --- a/packages/site-components/src/Breadcrumbs/index.ts +++ b/packages/site-components/src/Breadcrumbs/index.ts @@ -1,7 +1,2 @@ -import { withBreadcrumbsAdapter } from './withBreadcrumbsAdapter'; -import { Breadcrumbs as OriginalBreadcrumbs } from './Breadcrumbs'; - +export * from './Breadcrumbs'; export type { Breadcrumb } from './Breadcrumb'; - -export { withBreadcrumbsAdapter } from './withBreadcrumbsAdapter'; -export const Breadcrumbs = withBreadcrumbsAdapter(OriginalBreadcrumbs); diff --git a/packages/site-components/src/Breadcrumbs/withBreadcrumbsAdapter.tsx b/packages/site-components/src/Breadcrumbs/withBreadcrumbsAdapter.tsx deleted file mode 100644 index eda75f52..00000000 --- a/packages/site-components/src/Breadcrumbs/withBreadcrumbsAdapter.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useBreadcrumbs } from '@jpmorganchase/mosaic-store'; - -export const withBreadcrumbsAdapter = Component => () => { - const { breadcrumbs, enabled } = useBreadcrumbs(); - return ; -}; diff --git a/packages/site-components/src/DocPaginator/DocPaginator.tsx b/packages/site-components/src/DocPaginator/DocPaginator.tsx index 2c03a513..896569a2 100644 --- a/packages/site-components/src/DocPaginator/DocPaginator.tsx +++ b/packages/site-components/src/DocPaginator/DocPaginator.tsx @@ -1,12 +1,10 @@ -import React, { useEffect } from 'react'; -import { Link, P6, P3, TileBase, Icon } from '@jpmorganchase/mosaic-components'; +import React from 'react'; import classnames from 'clsx'; -import { useRouter } from 'next/router'; +import { Link, P6, P3, TileBase, Icon } from '@jpmorganchase/mosaic-components'; +import type { NavigationLink } from '@jpmorganchase/mosaic-types'; import styles from './styles.css'; -import { NavigationLink } from '@jpmorganchase/mosaic-store'; - export interface DocPaginatorProps { /** Link label suffix */ linkSuffix: string; @@ -17,29 +15,6 @@ export interface DocPaginatorProps { } export const DocPaginator: React.FC = ({ linkSuffix, next, prev }) => { - const router = useRouter(); - - const handleRouteChangeComplete = () => { - setTimeout(() => { - if (window.pageYOffset > 0) { - window.scroll({ - top: 0, - left: 0, - behavior: 'smooth' - }); - } - }, 300); - }; - - useEffect(() => { - router.events.on('routeChangeComplete', handleRouteChangeComplete); - - return () => { - router.events.off('routeChangeComplete', handleRouteChangeComplete); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - return (
    diff --git a/packages/site-components/src/DocPaginator/__tests__/DocPaginator.test.tsx b/packages/site-components/src/DocPaginator/__tests__/DocPaginator.test.tsx index b38641ef..dcc33885 100644 --- a/packages/site-components/src/DocPaginator/__tests__/DocPaginator.test.tsx +++ b/packages/site-components/src/DocPaginator/__tests__/DocPaginator.test.tsx @@ -1,19 +1,19 @@ import { describe, expect, test } from 'vitest'; -import React, { PropsWithChildren } from 'react'; +import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvents from '@testing-library/user-event'; import mockRouter from 'next-router-mock'; import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider/next-13.5'; import NextLink from 'next/link'; -import { LinkProvider } from '@jpmorganchase/mosaic-components'; +import { StoreProvider, StoreProviderProps } from '@jpmorganchase/mosaic-store'; import { DocPaginator } from '../DocPaginator'; describe('GIVEN a DocPaginator', () => { - const wrapper: PropsWithChildren = ({ children }) => ( - + const wrapper: StoreProviderProps = ({ children }) => ( + {children} - + ); test('can change to the next page', async () => { diff --git a/packages/site-components/src/DocPaginator/index.ts b/packages/site-components/src/DocPaginator/index.ts index 78fe6cd0..6c790afa 100644 --- a/packages/site-components/src/DocPaginator/index.ts +++ b/packages/site-components/src/DocPaginator/index.ts @@ -1,5 +1 @@ -import { withNavigationAdapter } from './withNavigationAdapter'; -import { DocPaginator as OriginalDocPaginator } from './DocPaginator'; - -export { withNavigationAdapter } from './withNavigationAdapter'; -export const DocPaginator = withNavigationAdapter(OriginalDocPaginator); +export * from './DocPaginator'; diff --git a/packages/site-components/src/DocPaginator/withNavigationAdapter.tsx b/packages/site-components/src/DocPaginator/withNavigationAdapter.tsx deleted file mode 100644 index 9254fe9a..00000000 --- a/packages/site-components/src/DocPaginator/withNavigationAdapter.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useNavigation } from '@jpmorganchase/mosaic-store'; - -export const withNavigationAdapter = - Component => - ({ linkSuffix = 'Page' }) => { - const { next, prev } = useNavigation(); - return ; - }; diff --git a/packages/site-components/src/Document.tsx b/packages/site-components/src/Document.tsx deleted file mode 100644 index 60b206bd..00000000 --- a/packages/site-components/src/Document.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable eslint-comments/disable-enable-pair */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import NextDocument, { Html, Head, NextScript, Main } from 'next/document'; - -export type { DocumentProps } from 'next/document'; - -export class Document extends NextDocument { - render() { - return ( - - - -
    - - - - ); - } -} diff --git a/packages/site-components/src/Drawer/index.tsx b/packages/site-components/src/Drawer/index.tsx index 56874ffd..44c78b51 100644 --- a/packages/site-components/src/Drawer/index.tsx +++ b/packages/site-components/src/Drawer/index.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import classNames from 'clsx'; -import { useRoute } from '@jpmorganchase/mosaic-store'; import { LayerLayout } from '@salt-ds/lab'; import styles from './styles.css'; import { Button, Icon, useOutsideClick } from '@jpmorganchase/mosaic-components'; @@ -25,27 +24,21 @@ export function Drawer({ children, TriggerElement, side }: DrawerProps) { const triggerRef = useRef(null); const rootRef = useOutsideClick(triggerRef, () => setOpen(false)); - const { route } = useRoute(); - const handleNavigationToggle = () => { setOpen(!open); }; - - const [isClient, setIsClient] = useState(false); + const [portalElement, setPortalElement] = useState(null); useEffect(() => { - // Close drawer whenever a page loads - setOpen(false); - setIsClient(true); - }, [route]); + setPortalElement(document.querySelector('[data-mosaic-id="portal-root"]')); + }, []); - const portalRoot = isClient ? document.querySelector('[data-mosaic-id="portal-root"]') : null; return ( <>
    - {portalRoot + {portalElement ? createPortal(
    , - portalRoot + portalElement ) : null} diff --git a/packages/site-components/src/Footer/index.ts b/packages/site-components/src/Footer/index.ts index f6a7f6cd..ddcc5a9c 100644 --- a/packages/site-components/src/Footer/index.ts +++ b/packages/site-components/src/Footer/index.ts @@ -1,6 +1 @@ -import { withFooterAdapter } from './withFooterAdapter'; -import { Footer as OriginalFooter } from './Footer'; - -export type { FooterProps } from './Footer'; - -export const Footer = withFooterAdapter(OriginalFooter); +export * from './Footer'; diff --git a/packages/site-components/src/Footer/withFooterAdapter.tsx b/packages/site-components/src/Footer/withFooterAdapter.tsx deleted file mode 100644 index 08214589..00000000 --- a/packages/site-components/src/Footer/withFooterAdapter.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { useFooter } from '@jpmorganchase/mosaic-store'; - -export const withFooterAdapter = Component => () => { - const props = useFooter(); - return ; -}; diff --git a/packages/site-components/src/Home/__tests__/index.test.tsx b/packages/site-components/src/Home/__tests__/index.test.tsx deleted file mode 100644 index eac0c541..00000000 --- a/packages/site-components/src/Home/__tests__/index.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import React from 'react'; -import { render } from '@testing-library/react'; - -import { Home } from '../index'; - -describe('GIVEN a Home component', () => { - test('THEN it renders children', () => { - const { getByText } = render( - - TEST - - ); - expect(getByText('TEST')).toBeDefined(); - }); -}); diff --git a/packages/site-components/src/Home/index.tsx b/packages/site-components/src/Home/index.tsx deleted file mode 100644 index 3075ada1..00000000 --- a/packages/site-components/src/Home/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import classnames from 'clsx'; -import { useImageComponent } from '@jpmorganchase/mosaic-components'; - -import styles from './styles.css'; - -export type HomeHeroProps = { - /** The child components representing Home's Hero content */ - children?: React.ReactNode; - /** Additional class name for root class override */ - className?: string; -}; -export const HomeHero: React.FC = ({ children, className, ...rest }) => ( -
    - {children} -
    -); - -export type HomeSectionProps = { - /** The child components representing a section within the Home component */ - children?: React.ReactNode; - /** Additional class name for root class override */ - className?: string; -}; -export const HomeSection: React.FC = ({ children, className, ...rest }) => ( -
    - {children} -
    -); - -function BackgroundImages() { - const ImageComponent = useImageComponent(); - - return ( - <> -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - - ); -} - -export type HomeProps = { - /** The children components of the Section component */ - children?: React.ReactNode; - /** Additional class name for root class override */ - className?: string; -}; - -export interface HomeComposition { - /** The initial hero section of the Home component */ - Hero?: typeof HomeHero; - /** A content section for the Home component */ - Section?: typeof HomeSection; -} - -export const Home: React.FC & HomeComposition = ({ children, className, ...rest }) => ( -
    - - {children} -
    -); - -Home.Hero = HomeHero; -Home.Section = HomeSection; diff --git a/packages/site-components/src/Home/styles.css.ts b/packages/site-components/src/Home/styles.css.ts deleted file mode 100644 index d7be48d7..00000000 --- a/packages/site-components/src/Home/styles.css.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { responsiveSprinkles } from '@jpmorganchase/mosaic-theme'; - -export default { - section: responsiveSprinkles({ - marginTop: ['x30', 'x30', 'x30', 'x30'] - }), - main: responsiveSprinkles({ - marginBottom: ['x30', 'x30', 'x30', 'x30'] - }), - backgroundImage: style([ - responsiveSprinkles({ - display: ['none', 'none', 'inherit', 'inherit'] - }), - { - zIndex: -1, - pointerEvents: 'none', - userSelect: 'none', - position: 'absolute' - } - ]), - backgroundImage1: style({ - opacity: '0.25', - top: '702px', - left: '1251px', - width: '254px', - height: '193px' - }), - backgroundImage2: style({ - opacity: '0.5', - top: '772px', - left: '1121px', - width: '385px', - height: '214px' - }), - backgroundImage3: style({ - opacity: '0.15', - top: '1259px', - left: '1153px', - width: '629px', - height: '1024px' - }), - backgroundImage4: style({ - opacity: '0.15', - top: '2367px', - left: '1041px', - width: '750px', - height: '813px' - }), - backgroundImage5: style({ - opacity: '0.5', - top: '5100px', - left: '-51px', - width: '385px', - height: '214px' - }) -}; diff --git a/packages/site-components/src/Image/index.tsx b/packages/site-components/src/Image/index.tsx index 397c78a5..56128f46 100644 --- a/packages/site-components/src/Image/index.tsx +++ b/packages/site-components/src/Image/index.tsx @@ -2,7 +2,6 @@ import { FC, forwardRef, Ref } from 'react'; import NextImage, { type ImageProps as NextImageProps } from 'next/image'; import classnames from 'clsx'; -import { useResolveRelativeUrl } from '../BaseUrlProvider'; import styles from './styles.css'; export type ImageProps = Omit & { @@ -31,7 +30,6 @@ export const Image: FC = forwardRef( }, ref: Ref ) => { - const resolvedSrc = useResolveRelativeUrl(src); return (
    {fill || (width && height) ? ( @@ -41,16 +39,12 @@ export const Image: FC = forwardRef( {...rest} height={height} fill={fill} - src={src.match(/^(http[s]?:)?\/{1,2}/) === null ? resolvedSrc : src} + src={src} width={width} unoptimized={unoptimized} /> ) : ( - {alt} + {alt} )}
    ); diff --git a/packages/site-components/src/Link.tsx b/packages/site-components/src/Link.tsx index 008b3fe1..41bd3e49 100644 --- a/packages/site-components/src/Link.tsx +++ b/packages/site-components/src/Link.tsx @@ -1,7 +1,24 @@ import NextLink from 'next/link'; import React, { Ref } from 'react'; +import { hasProtocol } from '@jpmorganchase/mosaic-components'; -import { useResolveRelativeUrl } from './BaseUrlProvider'; +function resolveRelativeUrl(href: string, baseRoute: string) { + if (hasProtocol(href) || href.startsWith('#') || !href.startsWith('.')) { + return href; + } + if (!baseRoute) { + throw new Error('Cannot resolve relative url as base route is undefined'); + } + const url = new URL('http://jpmorgan.com'); + const anchorMatches = href.match(/(.*)#(.*)/); + if (anchorMatches && anchorMatches[2]) { + // Anchors will get encoded to %23 if set on URL + url.pathname = `${baseRoute}/../${anchorMatches[1]}`; + return `${url.pathname}#${anchorMatches[2]}`; + } + url.pathname = `${baseRoute}/../${href}`; + return url.pathname; +} interface LinkProps extends React.AnchorHTMLAttributes { href: string; @@ -11,7 +28,7 @@ export type LinkType = { link: string; text: string }; export const Link = React.forwardRef( ({ href = '', ...props }: LinkProps, ref: Ref) => { - const url = useResolveRelativeUrl(href); + const url = resolveRelativeUrl(href, '/'); return ; } diff --git a/packages/site-components/src/Metadata.tsx b/packages/site-components/src/Metadata.tsx deleted file mode 100644 index bb4b2548..00000000 --- a/packages/site-components/src/Metadata.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { ElementType } from 'react'; -import { useMeta } from '@jpmorganchase/mosaic-store'; -import type { MetaSlice } from '@jpmorganchase/mosaic-store'; -import { useSession } from 'next-auth/react'; - -export interface HTMLMeta extends MetaSlice {} - -export type MetadataProps = { - Component: ElementType; -}; - -export const Metadata: React.FC = ({ Component = 'head' }) => { - const { meta } = useMeta(); - const { data: session } = useSession(); - return ( - - {meta.title && {meta.title}} - {meta.description && } - {session?.user?.image && } - {Array.isArray(meta.breadcrumbs) && meta.breadcrumbs.length > 0 && ( - - )} - - ); -}; diff --git a/packages/site-components/src/PageNavigation.tsx b/packages/site-components/src/PageNavigation.tsx index 2290ea92..194aabc4 100644 --- a/packages/site-components/src/PageNavigation.tsx +++ b/packages/site-components/src/PageNavigation.tsx @@ -1,8 +1,45 @@ -import { useSidebar } from '@jpmorganchase/mosaic-store'; import { VerticalNavigation } from './VerticalNavigation'; -export const PageNavigation = () => { - const { menu, selectedNodeId, selectedGroupIds } = useSidebar(); +export interface SidebarNode { + /** The id of the item */ + id: string; + /** Node kind */ + kind: 'data'; + /** The name of the item */ + name: string; + /** Sidebar data */ + data: { + /** The level of the menu item from the sidebar root */ + level: number; + /** The link of the item */ + link: string; + }; + /** Whether the item is hidden */ + hidden: boolean; +} + +export interface SidebarGroup { + /** The id of the item */ + id: string; + /** Node kind */ + kind: 'group'; + /** Child nodes */ + childNodes: SidebarItem[]; + /** The name of the item */ + name: string; + /** Whether the item is hidden */ + hidden: boolean; +} + +export type SidebarItem = SidebarNode | SidebarGroup; + +export type PageNavigationProps = { + menu: SidebarItem[]; + selectedNodeId?: string; + selectedGroupIds?: Set; +}; + +export const PageNavigation = ({ menu, selectedNodeId, selectedGroupIds }: PageNavigationProps) => { return ( ([]); const [listVisibility, setListVisibility] = useState(false); @@ -55,6 +59,10 @@ export function SearchInput() { }; }, []); + if (!searchConfig || !searchIndex) { + return null; + } + return (
    fetch(url).then(res => res.json()); - - const { searchIndex: fallbackIndex, searchConfig } = useSearchIndex(); - - const { data, error, isLoading: searchIsLoading } = useSWR('/search-data.json', fetcher); - - const searchIndex = searchIsLoading || error ? fallbackIndex : data; - - return { searchIndex, searchConfig }; -} diff --git a/packages/site-components/src/SessionProvider.tsx b/packages/site-components/src/SessionProvider.tsx new file mode 100644 index 00000000..d0ed7ae6 --- /dev/null +++ b/packages/site-components/src/SessionProvider.tsx @@ -0,0 +1,5 @@ +import { SessionProvider as NextSessionProvider } from 'next-auth/react'; + +export function SessionProvider({ children }) { + return {children}; +} diff --git a/packages/site-components/src/TableOfContents/TableOfContents.tsx b/packages/site-components/src/TableOfContents/TableOfContents.tsx index 6f62e2fe..91409be8 100644 --- a/packages/site-components/src/TableOfContents/TableOfContents.tsx +++ b/packages/site-components/src/TableOfContents/TableOfContents.tsx @@ -6,14 +6,14 @@ import { TableOfContentsItem } from './TableOfContentsItem'; import { mostRecentScrollPoint, setupHeadingState, setupSelectedHeadingState } from './utils'; import styles from './styles.css'; -export type Item = { level: number; id: string; text: string }; -export interface CurrentItem extends Item { +export type TOCItem = { level: number; id: string; text: string }; +export interface CurrentItem extends TOCItem { current: boolean; } const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect; export type TableOfContentsProps = { - items?: Item[]; + items?: TOCItem[]; }; export const TableOfContents: React.FC = ({ items }) => { diff --git a/packages/site-components/src/TableOfContents/TableOfContentsItem.tsx b/packages/site-components/src/TableOfContents/TableOfContentsItem.tsx index fe91e959..33ecc337 100644 --- a/packages/site-components/src/TableOfContents/TableOfContentsItem.tsx +++ b/packages/site-components/src/TableOfContents/TableOfContentsItem.tsx @@ -1,9 +1,9 @@ import { NavigationItem } from '@salt-ds/core'; import { stripMarkdownLinks } from './utils'; -import type { Item } from './TableOfContents'; +import type { TOCItem } from './TableOfContents'; -export function TableOfContentsItem({ item, current }: { item: Item; current?: string }) { +export function TableOfContentsItem({ item, current }: { item: TOCItem; current?: string }) { const selected = item.id === current; const handleItemClick = e => { diff --git a/packages/site-components/src/TableOfContents/index.ts b/packages/site-components/src/TableOfContents/index.ts index a56b90de..715a3a23 100644 --- a/packages/site-components/src/TableOfContents/index.ts +++ b/packages/site-components/src/TableOfContents/index.ts @@ -1,8 +1 @@ -import React from 'react'; -import { withTableOfContentsAdapter } from './withTableOfContentsAdapter'; -import { TableOfContents as OriginalTableOfContents } from './TableOfContents'; -import type { TableOfContentsProps } from './TableOfContents'; - -export { withTableOfContentsAdapter } from './withTableOfContentsAdapter'; -export const TableOfContents: React.FC = - withTableOfContentsAdapter(OriginalTableOfContents); +export * from './TableOfContents'; diff --git a/packages/site-components/src/TableOfContents/withTableOfContentsAdapter.tsx b/packages/site-components/src/TableOfContents/withTableOfContentsAdapter.tsx deleted file mode 100644 index 217b8c63..00000000 --- a/packages/site-components/src/TableOfContents/withTableOfContentsAdapter.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import { useTableOfContents } from '@jpmorganchase/mosaic-store'; -import { TableOfContentsProps } from './TableOfContents'; - -export const withTableOfContentsAdapter = - (Component): React.FC => - ({ items }) => { - const { tableOfContents } = useTableOfContents(); - return ; - }; diff --git a/packages/components/src/ThemeProvider.tsx b/packages/site-components/src/ThemeProvider.tsx similarity index 52% rename from packages/components/src/ThemeProvider.tsx rename to packages/site-components/src/ThemeProvider.tsx index 9aa07d18..0d0aa8c8 100644 --- a/packages/components/src/ThemeProvider.tsx +++ b/packages/site-components/src/ThemeProvider.tsx @@ -1,45 +1,31 @@ -import { useColorMode } from '@jpmorganchase/mosaic-store'; -import { ssrClassName } from '@jpmorganchase/mosaic-theme'; +import { ReactNode } from 'react'; import { SaltProvider, UNSTABLE_SaltProviderNext } from '@salt-ds/core'; -import { type ReactNode, useEffect, useState } from 'react'; +import { useColorMode } from '@jpmorganchase/mosaic-store'; import classnames from 'clsx'; -const useHasHydrated = () => { - const [hasHydrated, setHasHydrated] = useState(false); - - useEffect(() => { - setHasHydrated(true); - }, []); - - return hasHydrated; -}; - interface ThemeProviderProps { - /** Applies to `SaltProvider` `theme` prop */ - themeClassName?: string; className?: string; children?: ReactNode; + /** Applies to `SaltProvider` `theme` prop */ + themeClassName?: string; /** Enables Salt theme next */ themeNext?: boolean; } export function ThemeProvider({ - themeClassName, className, - themeNext, - children + children, + themeClassName, + themeNext }: ThemeProviderProps) { - const hasHydrated = useHasHydrated(); - const colorMode = useColorMode(); - - const ssrClassname = hasHydrated ? undefined : ssrClassName; + const { colorMode } = useColorMode(); const ChosenSaltProvider = themeNext ? UNSTABLE_SaltProviderNext : SaltProvider; return ( - -
    + +
    {children}
    diff --git a/packages/site-components/src/VerticalNavigation.tsx b/packages/site-components/src/VerticalNavigation.tsx index d8c09495..9b99f52a 100644 --- a/packages/site-components/src/VerticalNavigation.tsx +++ b/packages/site-components/src/VerticalNavigation.tsx @@ -1,7 +1,7 @@ import React, { MouseEventHandler, useState } from 'react'; import { StackLayout, NavigationItem, NavigationItemProps } from '@salt-ds/core'; import { Link } from '@jpmorganchase/mosaic-components'; -import { SidebarItem, SidebarNode } from '@jpmorganchase/mosaic-store'; +import type { SidebarItem, SidebarNode } from '@jpmorganchase/mosaic-types'; export type VerticalNavigationProps = { /** Selected item groups ids to expand */ @@ -40,11 +40,12 @@ const renderNavigationItem = ( } const childNodes = isGroup ? item.childNodes : undefined; const isExpanded = isGroup ? expandedGroupIds.has(id) : false; - const containsSelectedNode = selectedGroupIds.has(id); + const containsSelectedNode = isGroup && selectedGroupIds.has(id); const isActive = selectedNodeId === id || ((!isExpanded || singlePageInGroup) && (containsSelectedNode || selectedNodeId?.includes(item.id))); + const shouldRenderAsParent = !link; const handleExpand: MouseEventHandler = event => { event.stopPropagation(); diff --git a/packages/site-components/src/__tests__/BaseUrlProvider.test.tsx b/packages/site-components/src/__tests__/BaseUrlProvider.test.tsx deleted file mode 100644 index 676121a6..00000000 --- a/packages/site-components/src/__tests__/BaseUrlProvider.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, expect, test, vi } from 'vitest'; -import React from 'react'; -import { renderHook } from '@testing-library/react'; - -import { BaseUrlProvider, useResolveRelativeUrl } from '../BaseUrlProvider'; - -vi.mock('@jpmorganchase/mosaic-store', () => ({ - useRoute: vi.fn(() => ({ route: '/a/b/c' })) -})); - -const ProviderWrapper: React.FC = ({ children }) => ( - {children} -); - -describe('GIVEN useResolveRelativeUrl', () => { - test('returns a relative path resolved with the base path', () => { - const { result } = renderHook(() => useResolveRelativeUrl('./c/d/e'), { - wrapper: ProviderWrapper - }); - expect(result.current).toEqual('/a/b/c/d/e'); - }); - test('returns a relative path+anchor resolved with the base path', () => { - const { result } = renderHook(() => useResolveRelativeUrl('./c/d/e#anchor'), { - wrapper: ProviderWrapper - }); - expect(result.current).toEqual('/a/b/c/d/e#anchor'); - }); - test('returns the original path for external URLs', () => { - const { result } = renderHook(() => useResolveRelativeUrl('http://jpmorgan.com'), { - wrapper: ProviderWrapper - }); - expect(result.current).toEqual('http://jpmorgan.com'); - }); - test('returns the original path for local anchors', () => { - const { result } = renderHook(() => useResolveRelativeUrl('#anchor'), { - wrapper: ProviderWrapper - }); - expect(result.current).toEqual('#anchor'); - }); -}); diff --git a/packages/site-components/src/index.tsx b/packages/site-components/src/index.tsx index 349069b3..68738fe1 100644 --- a/packages/site-components/src/index.tsx +++ b/packages/site-components/src/index.tsx @@ -1,25 +1,24 @@ +'use client'; + export * from './AppHeader'; export * from './AppHeaderControls'; export * from './AppHeaderDrawer'; export * from './AppHeaderTabs'; export * from './BackLink'; export * from './Breadcrumbs'; -export * from './BaseUrlProvider'; -export * from './Body'; -export * from './Document'; export * from './DocPaginator'; export * from './Drawer'; export * from './Footer'; export * from './HTMLView'; export * from './Link'; export * from './Image'; -export * from './Metadata'; export * from './PageNavigation'; +export * from './SearchInput'; +export * from './SessionProvider'; export * from './Sidebar'; export * from './TableOfContents'; +export * from './ThemeProvider'; export * from './UserProfile'; export * from './VerticalNavigation'; export * from './404'; export * from './500'; - -export { default as components } from './mdx'; diff --git a/packages/site-components/src/mdx.tsx b/packages/site-components/src/mdx.tsx deleted file mode 100644 index 7d2ab204..00000000 --- a/packages/site-components/src/mdx.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Pre, getMarkdownComponents, withMarkdownSpacing } from '@jpmorganchase/mosaic-components'; -import type { PreProps } from '@jpmorganchase/mosaic-components'; -import { getLabMarkdownComponents } from '@jpmorganchase/mosaic-labs-components'; -import { OpenAPI } from '@jpmorganchase/mosaic-open-api-component'; -import type { OpenAPIProps } from '@jpmorganchase/mosaic-open-api-component'; - -import { Home } from './Home'; -import { Image } from './Image'; -import type { ImageProps } from './Image'; - -const components = { - ...getMarkdownComponents(), - Labs: getLabMarkdownComponents(), - Home, - img: withMarkdownSpacing(Image), - pre: withMarkdownSpacing(props =>
    ),
    -  OpenAPI: withMarkdownSpacing(OpenAPI)
    -};
    -
    -export default components;
    diff --git a/packages/site-middleware/CHANGELOG.md b/packages/site-middleware/CHANGELOG.md
    deleted file mode 100644
    index c801b7e9..00000000
    --- a/packages/site-middleware/CHANGELOG.md
    +++ /dev/null
    @@ -1,858 +0,0 @@
    -# @jpmorganchase/mosaic-site-middleware
    -
    -## 0.1.0-beta.89
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.89
    -- @jpmorganchase/mosaic-store@0.1.0-beta.89
    -- @jpmorganchase/mosaic-types@0.1.0-beta.89
    -
    -## 0.1.0-beta.88
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.88
    -- @jpmorganchase/mosaic-store@0.1.0-beta.88
    -- @jpmorganchase/mosaic-types@0.1.0-beta.88
    -
    -## 0.1.0-beta.87
    -
    -### Patch Changes
    -
    -- Updated dependencies [14942195]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.87
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.87
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.87
    -
    -## 0.1.0-beta.86
    -
    -### Patch Changes
    -
    -- Updated dependencies [9124a6c0]
    -- Updated dependencies [b8361977]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.86
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.86
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.86
    -
    -## 0.1.0-beta.85
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.85
    -- @jpmorganchase/mosaic-store@0.1.0-beta.85
    -- @jpmorganchase/mosaic-types@0.1.0-beta.85
    -
    -## 0.1.0-beta.84
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.84
    -- @jpmorganchase/mosaic-store@0.1.0-beta.84
    -- @jpmorganchase/mosaic-types@0.1.0-beta.84
    -
    -## 0.1.0-beta.83
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.83
    -- @jpmorganchase/mosaic-store@0.1.0-beta.83
    -- @jpmorganchase/mosaic-types@0.1.0-beta.83
    -
    -## 0.1.0-beta.82
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.82
    -- @jpmorganchase/mosaic-store@0.1.0-beta.82
    -- @jpmorganchase/mosaic-types@0.1.0-beta.82
    -
    -## 0.1.0-beta.81
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.81
    -- @jpmorganchase/mosaic-store@0.1.0-beta.81
    -- @jpmorganchase/mosaic-types@0.1.0-beta.81
    -
    -## 0.1.0-beta.80
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.80
    -- @jpmorganchase/mosaic-store@0.1.0-beta.80
    -- @jpmorganchase/mosaic-types@0.1.0-beta.80
    -
    -## 0.1.0-beta.79
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.79
    -- @jpmorganchase/mosaic-store@0.1.0-beta.79
    -- @jpmorganchase/mosaic-types@0.1.0-beta.79
    -
    -## 0.1.0-beta.78
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.78
    -- @jpmorganchase/mosaic-store@0.1.0-beta.78
    -- @jpmorganchase/mosaic-types@0.1.0-beta.78
    -
    -## 0.1.0-beta.77
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.77
    -- @jpmorganchase/mosaic-store@0.1.0-beta.77
    -- @jpmorganchase/mosaic-types@0.1.0-beta.77
    -
    -## 0.1.0-beta.76
    -
    -### Patch Changes
    -
    -- Updated dependencies [727fcd54]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.76
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.76
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.76
    -
    -## 0.1.0-beta.75
    -
    -### Patch Changes
    -
    -- fd6d715d: - Tactical fix to remove `react-pro-sidebar` and replace with Salt's `NavigationItem`. We will remove this local `NavigationItem` once Salt merges it's own PR.
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.75
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.75
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.75
    -
    -## 0.1.0-beta.74
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.74
    -- @jpmorganchase/mosaic-store@0.1.0-beta.74
    -- @jpmorganchase/mosaic-types@0.1.0-beta.74
    -
    -## 0.1.0-beta.73
    -
    -### Patch Changes
    -
    -- de00c017: Added the files package.json field to all of the packages to prevent unnecessary files being published.
    -- Updated dependencies [de00c017]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.73
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.73
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.73
    -
    -## 0.1.0-beta.72
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.72
    -- @jpmorganchase/mosaic-store@0.1.0-beta.72
    -- @jpmorganchase/mosaic-types@0.1.0-beta.72
    -
    -## 0.1.0-beta.71
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.71
    -- @jpmorganchase/mosaic-store@0.1.0-beta.71
    -- @jpmorganchase/mosaic-types@0.1.0-beta.71
    -
    -## 0.1.0-beta.70
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.70
    -- @jpmorganchase/mosaic-store@0.1.0-beta.70
    -- @jpmorganchase/mosaic-types@0.1.0-beta.70
    -
    -## 0.1.0-beta.69
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.69
    -- @jpmorganchase/mosaic-store@0.1.0-beta.69
    -- @jpmorganchase/mosaic-types@0.1.0-beta.69
    -
    -## 0.1.0-beta.68
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.68
    -- @jpmorganchase/mosaic-store@0.1.0-beta.68
    -- @jpmorganchase/mosaic-types@0.1.0-beta.68
    -
    -## 0.1.0-beta.67
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.67
    -- @jpmorganchase/mosaic-store@0.1.0-beta.67
    -- @jpmorganchase/mosaic-types@0.1.0-beta.67
    -
    -## 0.1.0-beta.66
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.66
    -- @jpmorganchase/mosaic-store@0.1.0-beta.66
    -- @jpmorganchase/mosaic-types@0.1.0-beta.66
    -
    -## 0.1.0-beta.65
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.65
    -- @jpmorganchase/mosaic-store@0.1.0-beta.65
    -- @jpmorganchase/mosaic-types@0.1.0-beta.65
    -
    -## 0.1.0-beta.64
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.64
    -- @jpmorganchase/mosaic-store@0.1.0-beta.64
    -- @jpmorganchase/mosaic-types@0.1.0-beta.64
    -
    -## 0.1.0-beta.63
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.63
    -- @jpmorganchase/mosaic-store@0.1.0-beta.63
    -- @jpmorganchase/mosaic-types@0.1.0-beta.63
    -
    -## 0.1.0-beta.62
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.62
    -- @jpmorganchase/mosaic-store@0.1.0-beta.62
    -- @jpmorganchase/mosaic-types@0.1.0-beta.62
    -
    -## 0.1.0-beta.61
    -
    -### Patch Changes
    -
    -- cec89401: add `pluginTimeout` (20 secs) to fastify to prevent loading timeout
    -- Updated dependencies [cec89401]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.61
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.61
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.61
    -
    -## 0.1.0-beta.60
    -
    -### Patch Changes
    -
    -- a3da0830: New Readme Source
    -
    -  This source pulls a single Readme.md from a remote Source repo.
    -  Typically used for third-party repos which exist already or don't want to
    -  create a full document hierachy and use `@jpmorganchase/mosaic-source-git-repo`.
    -  By pulling a single page, we can add metadata to that page via the source's config.
    -  It's also more performant as we do not need to pull a whole source repo.
    -
    -- Updated dependencies [a3da0830]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.60
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.60
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.60
    -
    -## 0.1.0-beta.59
    -
    -### Patch Changes
    -
    -- e3f4b7c3: Fixed routing breaking when URL parameters are present.
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.59
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.59
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.59
    -
    -## 0.1.0-beta.58
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.58
    -- @jpmorganchase/mosaic-store@0.1.0-beta.58
    -- @jpmorganchase/mosaic-types@0.1.0-beta.58
    -
    -## 0.1.0-beta.57
    -
    -### Patch Changes
    -
    -- d214d112: Add catch-all default exports for
    -
    -  - `@jpmorganchase/mosaic-store`
    -  - `@jpmorganchase/mosaic-theme`
    -  - `@jpmorganchase/mosaic-workflows`
    -
    -  This resolves an issue when running tests from an external repo which depends on these packages
    -
    -- Updated dependencies [d214d112]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.57
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.57
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.57
    -
    -## 0.1.0-beta.56
    -
    -### Patch Changes
    -
    -- 6d30e29f: Add new Storybook source
    -
    -  Storybook stories can be extracted from Storybook and embedded into Mosaic pages.
    -
    -  The stories are extracted based on a configured filter or matching tags.
    -
    -  With a page created for each Story, the author can create a dynamic index of matching stories.
    -
    -  eg. An index of patterns which match a specific tag
    -
    -- Updated dependencies [6d30e29f]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.56
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.56
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.56
    -
    -## 0.1.0-beta.55
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.55
    -- @jpmorganchase/mosaic-store@0.1.0-beta.55
    -- @jpmorganchase/mosaic-types@0.1.0-beta.55
    -
    -## 0.1.0-beta.54
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.54
    -- @jpmorganchase/mosaic-store@0.1.0-beta.54
    -- @jpmorganchase/mosaic-types@0.1.0-beta.54
    -
    -## 0.1.0-beta.53
    -
    -### Patch Changes
    -
    -- Updated dependencies [d7098baa]
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.53
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.53
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.53
    -
    -## 0.1.0-beta.52
    -
    -### Patch Changes
    -
    -- Updated dependencies [9ad7418c]
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.52
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.52
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.52
    -
    -## 0.1.0-beta.51
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.51
    -- @jpmorganchase/mosaic-store@0.1.0-beta.51
    -- @jpmorganchase/mosaic-types@0.1.0-beta.51
    -
    -## 0.1.0-beta.50
    -
    -### Patch Changes
    -
    -- Updated dependencies [2f015976]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.50
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.50
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.50
    -
    -## 0.1.0-beta.49
    -
    -### Patch Changes
    -
    -- Updated dependencies [425b5a00]
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.49
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.49
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.49
    -
    -## 0.1.0-beta.48
    -
    -### Patch Changes
    -
    -- Updated dependencies [0eca1d6e]
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.48
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.48
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.48
    -
    -## 0.1.0-beta.47
    -
    -### Patch Changes
    -
    -- Updated dependencies [19b4e49a]
    -- Updated dependencies [6caa661a]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.47
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.47
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.47
    -
    -## 0.1.0-beta.46
    -
    -### Patch Changes
    -
    -- Updated dependencies [32e86cfd]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.46
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.46
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.46
    -
    -## 0.1.0-beta.45
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.45
    -- @jpmorganchase/mosaic-store@0.1.0-beta.45
    -- @jpmorganchase/mosaic-types@0.1.0-beta.45
    -
    -## 0.1.0-beta.44
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.44
    -- @jpmorganchase/mosaic-store@0.1.0-beta.44
    -- @jpmorganchase/mosaic-types@0.1.0-beta.44
    -
    -## 0.1.0-beta.43
    -
    -### Patch Changes
    -
    -- Updated dependencies [d3b8b3a]
    -- Updated dependencies [0ced179]
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.43
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.43
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.43
    -
    -## 0.1.0-beta.42
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.42
    -- @jpmorganchase/mosaic-store@0.1.0-beta.42
    -- @jpmorganchase/mosaic-types@0.1.0-beta.42
    -
    -## 0.1.0-beta.41
    -
    -### Patch Changes
    -
    -- Updated dependencies [898c9ad]
    -- Updated dependencies [5cd5a87]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.41
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.41
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.41
    -
    -## 0.1.0-beta.40
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.40
    -- @jpmorganchase/mosaic-store@0.1.0-beta.40
    -- @jpmorganchase/mosaic-types@0.1.0-beta.40
    -
    -## 0.1.0-beta.39
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.39
    -- @jpmorganchase/mosaic-store@0.1.0-beta.39
    -- @jpmorganchase/mosaic-types@0.1.0-beta.39
    -
    -## 0.1.0-beta.38
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.38
    -- @jpmorganchase/mosaic-store@0.1.0-beta.38
    -- @jpmorganchase/mosaic-types@0.1.0-beta.38
    -
    -## 0.1.0-beta.37
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.37
    -- @jpmorganchase/mosaic-store@0.1.0-beta.37
    -- @jpmorganchase/mosaic-types@0.1.0-beta.37
    -
    -## 0.1.0-beta.36
    -
    -### Patch Changes
    -
    -- 03464ad: fix: remove env variable check in `withSession` middleware
    -
    -  The `NEXTAUTH_SECRET` env variable is not always required. It is possible to pass the secret as an option to `next-auth`
    -
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.36
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.36
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.36
    -
    -## 0.1.0-beta.35
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.35
    -- @jpmorganchase/mosaic-store@0.1.0-beta.35
    -- @jpmorganchase/mosaic-types@0.1.0-beta.35
    -
    -## 0.1.0-beta.34
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.34
    -- @jpmorganchase/mosaic-store@0.1.0-beta.34
    -- @jpmorganchase/mosaic-types@0.1.0-beta.34
    -
    -## 0.1.0-beta.33
    -
    -### Patch Changes
    -
    -- @jpmorganchase/mosaic-schemas@0.1.0-beta.33
    -- @jpmorganchase/mosaic-store@0.1.0-beta.33
    -- @jpmorganchase/mosaic-types@0.1.0-beta.33
    -
    -## 0.1.0-beta.32
    -
    -### Patch Changes
    -
    -- 7ed1ee7: Upgrade to latest version of NextJs (v13.4.1)
    -- 8c854fd: - Upgrade `next-auth` version
    -  - Update `withSession` mosaic middleware to use `next-auth`
    -- Updated dependencies [a56eadb]
    -- Updated dependencies [8c854fd]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.32
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.32
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.32
    -
    -## 0.1.0-beta.31
    -
    -### Patch Changes
    -
    -- b609fd0: Added support for containerization of the site and cli as well as providing config for kubernetes development (skaffold)
    -
    -  ## @jpmorganchase/mosaic-cli
    -
    -  The cli package has been updated to support containerization.
    -
    -- Updated dependencies [b609fd0]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.31
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.31
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.31
    -
    -## 0.1.0-beta.30
    -
    -### Patch Changes
    -
    -- 18ef436: The git repo source no longer generates a double slash between the repo host and repo path.
    -- Updated dependencies [18ef436]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.30
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.30
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.30
    -
    -## 0.1.0-beta.29
    -
    -### Minor Changes
    -
    -- c78deb4: Flatten Sidebar
    -  Search Optimisation
    -  Public Assets Plugin
    -  TOC Indentation
    -
    -### Patch Changes
    -
    -- Updated dependencies [c78deb4]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.29
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.29
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.29
    -
    -## 0.1.0-beta.28
    -
    -### Patch Changes
    -
    -- 27ac914: 1. Layout improvements. 2. Fix: if a user hits a url that lands on a directory, the index file within that directory is resolved as the content. 3. Fix: improve Table of Contents component highlighting.
    -- Updated dependencies [27ac914]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.28
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.28
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.28
    -
    -## 0.1.0-beta.27
    -
    -### Patch Changes
    -
    -- b465413: Improvements to vercel deployments
    -- Updated dependencies [b465413]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.27
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.27
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.27
    -
    -## 0.1.0-beta.26
    -
    -### Minor Changes
    -
    -- 531c87a: ## Mosaic Theme
    -
    -  The theme variables are now globally scoped and prefixed with `mosaic`.
    -
    -  ## BrokenLinksPlugin
    -
    -  The `BrokenLinksPlugin` uses a running instance of mosaic to verify that all links in the source pages are alive.
    -
    -  If mosaic is running behind a corporate proxy, the `proxyEndpoint` option is required to fetch external URLs.
    -
    -  Configuration:
    -
    -  ```json
    -   {
    -        modulePath: '@jpmorganchase/mosaic-plugins/BrokenLinksPlugin',
    -        priority: -1,
    -        // Exclude this plugin in builds
    -        runTimeOnly: true,
    -        options: {
    -          baseUrl: process.env.MOSAIC_ACTIVE_MODE_URL || 'http://localhost:8080',
    -          proxyEndpoint: 'http://some-proxy-url'
    -        }
    -      }
    -  ```
    -
    -  ## Next/Prev button
    -
    -  The next and prev buttons are visible again on pages that have a layout that uses these buttons.
    -
    -### Patch Changes
    -
    -- Updated dependencies [531c87a]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.26
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.26
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.26
    -
    -## 0.1.0-beta.25
    -
    -### Minor Changes
    -
    -- 93e9b07: The theme contract provided by the `@jpmorganchase/mosaic-theme` package now uses locally scoped variable names via a Vanilla Extract [Theme Contract](https://vanilla-extract.style/documentation/api/create-theme-contract/). Previously the theme variables were globally scoped resulting in conflicts with other design systems.
    -
    -### Patch Changes
    -
    -- a36219c: - Next/Prev page buttons were not appearing at the bottom of a page sequence
    -  - Removed redundant snapshot page api
    -- Updated dependencies [a36219c]
    -- Updated dependencies [93e9b07]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.25
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.25
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.25
    -
    -## 0.1.0-beta.24
    -
    -### Patch Changes
    -
    -- 049d9af: 1. Pip Salt version 2. Issues 155, make 500 error more specific 3. button and sidebar styles 4. search opt-out
    -- Updated dependencies [049d9af]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.24
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.24
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.24
    -
    -## 0.1.0-beta.23
    -
    -### Patch Changes
    -
    -- 513d45f: Sidebar behavior and styling changes.
    -  Add Client-side search feature.
    -  Relax node engine requirements.
    -  Removal of patches from the site package.
    -  Update site generator templates.
    -- Updated dependencies [513d45f]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.23
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.23
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.23
    -
    -## 0.1.0-beta.22
    -
    -### Patch Changes
    -
    -- be89e4f: fix markdown tables and update generator's Salt patches
    -
    -  - Salt patches in generator were out of sync with Mosaic repo
    -  - Markdown now support github flavoured markdown, such as Tables
    -
    -- Updated dependencies [be89e4f]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.22
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.22
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.22
    -
    -## 0.1.0-beta.21
    -
    -### Patch Changes
    -
    -- f75fd5e: fix sidebar which was generated after `beforeSend` had completed
    -- Updated dependencies [f75fd5e]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.21
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.21
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.21
    -
    -## 0.1.0-beta.20
    -
    -### Patch Changes
    -
    -- 9c7b8ff: pip to beta.20
    -- Updated dependencies [9c7b8ff]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.20
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.20
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.20
    -
    -## 0.1.0-beta.19
    -
    -### Patch Changes
    -
    -- ad06d4c: ensure spinner is removed after page has loaded
    -- Updated dependencies [ad06d4c]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.19
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.19
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.19
    -
    -## 0.1.0-beta.18
    -
    -### Patch Changes
    -
    -- 066efed: Update docs with quick-start guide
    -
    -  Sample docs now include a 'quick-start' guide to onboarding to AWS.
    -
    -  Also
    -
    -  - generator default directory is the current directory
    -  - after generating a site, it will run `yarn` in the created directory. This simplifies the generator call to just `yarn mosaic-create-site`
    -
    -- Updated dependencies [066efed]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.18
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.18
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.18
    -
    -## 0.1.0-beta.17
    -
    -### Patch Changes
    -
    -- b2f6d52: Fix `pre` block code block rendering
    -- Updated dependencies [b2f6d52]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.17
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.17
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.17
    -
    -## 0.1.0-beta.16
    -
    -### Patch Changes
    -
    -- 3a5c88a: add missing `@types/node` dependency for generator
    -- Updated dependencies [3a5c88a]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.16
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.16
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.16
    -
    -## 0.1.0-beta.15
    -
    -### Patch Changes
    -
    -- aaaf255: initial release of HTTP Source package.
    -
    -  An HTTP source accepts a collection of endpoints and a path to a transformer module.
    -  The response from fetching is transformed and merged together into 1 single collection of pages.
    -  Should 1 of the endpoints request fail then it will have no impact on the other requests.
    -
    -- Updated dependencies [aaaf255]
    -  - @jpmorganchase/mosaic-schemas@0.1.0-beta.15
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.15
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.15
    -
    -## 0.1.0-beta.14
    -
    -### Patch Changes
    -
    -- dde3b5a: Feature release
    -
    -  - Enhanced generators now have defaults.
    -    With one command (`yarn mosaic-create-site create`) it will generate a fully working site with both local and remote sources
    -  - Fix an issue where we could not clone from the master branch of git repos
    -  - Migrate to Next 13 image
    -
    -- Updated dependencies [dde3b5a]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.14
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.14
    -
    -## 0.1.0-beta.13
    -
    -### Patch Changes
    -
    -- d4da1df: incremental improvements
    -
    -  - move colormode into store
    -  - ensure breadcrumbs and sidebar data is only added to frontmatter for pages which use a layout that has breadcrumbs or a sidebar
    -  - improve changeset so it can work standalone without a monorepo
    -  - resolev json5 vulnerability
    -
    -- Updated dependencies [d4da1df]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.13
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.13
    -
    -## 0.1.0-beta.12
    -
    -### Patch Changes
    -
    -- 9ec358b: Upgrade React to version 18 and NextJs to version 13
    -- 3eb35bf: initial work to enable generators to run outside of the repo
    -- Updated dependencies [9ec358b]
    -- Updated dependencies [3eb35bf]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.12
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.12
    -
    -## 0.1.0-beta.11
    -
    -### Patch Changes
    -
    -- 146a4bb: fix SSR Initial render
    -
    -  removed Salt Core patch
    -  added Salt Labs patch (Cascading Menu) to support SSR
    -
    -- fc5d7b5: Generators can now interactively add sources
    -
    -  Previously we were making local edits to our own site or examples to implement new features.
    -  What we wanted was the ability to create a local rig, A local rig can be used for development purposes,
    -  without touching our site code.
    -
    -  We have added Mosaic repo commands, to enable us to generates local rigs (`yarn gen:rig`) and deploy our own
    -  tech docs via snapshot (`yarn gen`)
    -
    -  To generate a site+snapshot from sources defined in `mosaic-generators.js`, run `yarn gen`
    -  To generate a dynamic site from sources defined in `mosaic-generators.js`, run `yarn gen:site`
    -  To generate a rig `yarn gen:rig`
    -
    -  Equally these changes can be used to generate sites in other repos via the `mosaic-create-site` command.
    -
    -  `yarn mosaic-create-site init` will create a `mosaic.generators.js`.
    -
    -  Configure the `mosaic.generators.js` with your generator and sources, then run.
    -
    -  `yarn mosaic-create-site create -i -o path/to/my-site`
    -
    -  When this command is run, it will present an interactive menu of generators and output the site to `path/to/my-site`.
    -
    -- Updated dependencies [fc5d7b5]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.11
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.11
    -
    -## 0.1.0-beta.10
    -
    -### Patch Changes
    -
    -- af2f579: Converted repo to ESM and Salt DS nomenclature
    -
    -  - Switch UITK nomenclature to Salt DS
    -    Upgraded to first stable version of Salt DS version 1.0.0
    -  - CommonJS code switched to ESM and upgraded to Node 16
    -  - Removed example-nextjs-ssr package as un-required, can be replaced by documentation
    -  - Sites can now generate immutable snapshots of content that loads content like a SGS (statically generated site)
    -    Snapshots can be used as a serverless solution when deployed to Vercel.
    -  - New Middleware package `@jpmorganchase/mosaic-site-middleware`
    -
    -- Updated dependencies [af2f579]
    -  - @jpmorganchase/mosaic-store@0.1.0-beta.10
    -  - @jpmorganchase/mosaic-types@0.1.0-beta.10
    diff --git a/packages/site-middleware/README.md b/packages/site-middleware/README.md
    deleted file mode 100644
    index 84a50eed..00000000
    --- a/packages/site-middleware/README.md
    +++ /dev/null
    @@ -1,14 +0,0 @@
    -# Mosaic Site Components Library
    -
    -`@jpmorganchase/mosaic-site-middleware` contains site specific middleware functions.
    -
    -Middleware functions are server-side only functions. They cannot be run on the client.
    -
    -## Features
    -
    -1. Middleware functions.
    -2. Middleware Preset
    -
    -## Installation
    -
    -`yarn add @jpmorganchase/mosaic-site-components`
    diff --git a/packages/site-middleware/package.json b/packages/site-middleware/package.json
    deleted file mode 100644
    index 3977014c..00000000
    --- a/packages/site-middleware/package.json
    +++ /dev/null
    @@ -1,67 +0,0 @@
    -{
    -  "name": "@jpmorganchase/mosaic-site-middleware",
    -  "version": "0.1.0-beta.89",
    -  "license": "Apache-2.0",
    -  "description": "Mosaic - Site middleware",
    -  "repository": {
    -    "type": "git",
    -    "url": "git@github.com:jpmorganchase/mosaic.git",
    -    "directory": "packages/site-middleware"
    -  },
    -  "main": "./dist/index.js",
    -  "types": "./dist/index.d.ts",
    -  "exports": {
    -    "./index.css": "./dist/index.css",
    -    ".": {
    -      "style": "./dist/index.css",
    -      "types": "./dist/index.d.ts",
    -      "import": "./dist/index.js",
    -      "node": "./dist/index.js",
    -      "default": "./dist/index.js"
    -    }
    -  },
    -  "files": [
    -    "dist"
    -  ],
    -  "scripts": {
    -    "build": "tsc",
    -    "clean": "rm -fr dist",
    -    "lint": "eslint --ignore-pattern \"**/__tests__/**\""
    -  },
    -  "devDependencies": {
    -    "aws-sdk-client-mock": "^2.0.1",
    -    "vitest-fetch-mock": "^0.3.0",
    -    "mock-fs": "^5.0.0",
    -    "typescript": "^5.0.0"
    -  },
    -  "dependencies": {
    -    "@aws-sdk/client-s3": "^3.645.0",
    -    "@jpmorganchase/mosaic-schemas": "^0.1.0-beta.89",
    -    "@jpmorganchase/mosaic-store": "^0.1.0-beta.89",
    -    "@jpmorganchase/mosaic-types": "^0.1.0-beta.89",
    -    "@types/mdast": "^3.0.0",
    -    "acorn": "^8.0.0",
    -    "acorn-jsx": "^5.0.0",
    -    "deepmerge": "^4.2.2",
    -    "hast-util-properties-to-mdx-jsx-attributes": "^1.0.0",
    -    "jwt-decode": "^3.1.2",
    -    "lodash-es": "^4.17.21",
    -    "mdast-util-mdx": "^3.0.0",
    -    "mdast-util-mdx-jsx": "^3.0.0",
    -    "mdast-util-from-markdown": "^2.0.0",
    -    "micromark-extension-mdxjs": "^3.0.0",
    -    "next": "^14.0.0",
    -    "next-mdx-remote": "^5.0.0",
    -    "node-cookie": "^2.1.2",
    -    "react-error-boundary": "^3.1.4",
    -    "remark-gfm": "^4.0.0",
    -    "rehype-slug": "^6.0.0",
    -    "unified": "^11.0.0",
    -    "unist-util-visit": "^5.0.0",
    -    "unist-util-visit-parents": "^6.0.0",
    -    "warning": "^3.0.0"
    -  },
    -  "peerDependencies": {
    -    "next-auth": "^4.22.1"
    -  }
    -}
    diff --git a/packages/site-middleware/src/MiddlewareError.ts b/packages/site-middleware/src/MiddlewareError.ts
    deleted file mode 100644
    index 64c25796..00000000
    --- a/packages/site-middleware/src/MiddlewareError.ts
    +++ /dev/null
    @@ -1,26 +0,0 @@
    -export default class MiddlewareError extends Error {
    -  /** Response code */
    -  public status: number;
    -
    -  /** Related route location */
    -  public location: string | undefined;
    -
    -  /** Array of errors */
    -  public errors: string[];
    -
    -  /** Props to pass back */
    -  public props: { show404?: boolean; show500?: boolean };
    -
    -  constructor(
    -    status: MiddlewareError['status'],
    -    location: MiddlewareError['location'],
    -    errors: MiddlewareError['errors'] = [],
    -    props: MiddlewareError['props'] = {}
    -  ) {
    -    super(`${status} error`);
    -    this.status = status;
    -    this.location = location;
    -    this.errors = errors;
    -    this.props = props;
    -  }
    -}
    diff --git a/packages/site-middleware/src/__tests__/compileMdx.test.ts b/packages/site-middleware/src/__tests__/compileMdx.test.ts
    deleted file mode 100644
    index 81d23af7..00000000
    --- a/packages/site-middleware/src/__tests__/compileMdx.test.ts
    +++ /dev/null
    @@ -1,19 +0,0 @@
    -import { describe, expect, it } from 'vitest';
    -import { compileMDX } from '../compileMdx.js';
    -
    -describe('compileMdx', () => {
    -  it('should handle codeblocks', async () => {
    -    const result = await compileMDX('```jsx live\n
    I will be live editable!
    \n```'); - expect(result.compiledSource).toContain(`{ - live: true, - children: _jsxDEV(_components.code, { - className: "language-jsx", - children: "
    I will be live editable!
    \\n" - }, undefined, false, { - fileName: "", - lineNumber: 1, - columnNumber: 1 - }, this) - }`); - }); -}); diff --git a/packages/site-middleware/src/__tests__/withMDXContent.test.ts b/packages/site-middleware/src/__tests__/withMDXContent.test.ts deleted file mode 100644 index 432286bd..00000000 --- a/packages/site-middleware/src/__tests__/withMDXContent.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { describe, expect, test, vi, beforeAll, afterAll } from 'vitest'; -import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { AwsStub, mockClient } from 'aws-sdk-client-mock'; -import { sdkStreamMixin } from '@smithy/util-stream'; -import { Readable } from 'stream'; -import createFetchMock from 'vitest-fetch-mock'; - -const mockFs = require('mock-fs'); - -import { withMDXContent } from '../withMDXContent'; - -const fetchMock = createFetchMock(vi); - -vi.mock('../compileMdx.js', () => ({ - compileMDX: async (value: string) => Promise.resolve(value) -})); - -declare var process: { - env: { - MOSAIC_S3_BUCKET?: string; - MOSAIC_S3_REGION?: string; - MOSAIC_S3_ACCESS_KEY_ID?: string; - MOSAIC_S3_SECRET_ACCESS_KEY?: string; - MOSAIC_SNAPSHOT_DIR?: string; - }; -}; - -describe('GIVEN withMDXContent', () => { - describe('WHEN snapshot-s3 Mosaic mode is set', () => { - let savedEnv = process.env; - let s3ClientMock: AwsStub<{}, { $metadata: {} }>; - beforeAll(() => { - process.env = { - ...process.env, - MOSAIC_S3_BUCKET: 'some-bucket', - MOSAIC_S3_REGION: 'some-region', - MOSAIC_S3_ACCESS_KEY_ID: 'some-access-key', - MOSAIC_S3_SECRET_ACCESS_KEY: 'some-secret-key' - }; - s3ClientMock = mockClient(S3Client); - const stream = new Readable(); - stream.push('my content'); - stream.push(null); // end of stream - const contentStream = sdkStreamMixin(stream); - s3ClientMock.on(GetObjectCommand).resolves({ Body: contentStream }); - }); - afterAll(() => { - process.env = savedEnv; - s3ClientMock.reset(); - }); - test('THEN a snapshot can be loaded from an S3 bucket', async () => { - // arrange - const content = await withMDXContent({ - resolvedUrl: '/mynamespace/mypage.mdx', - res: { getHeader: () => 'snapshot-s3' } - }); - // assert - expect(content).toEqual({ props: { raw: 'my content', source: 'my content', type: 'mdx' } }); - }); - }); - - describe('WHEN snapshot-file Mosaic mode is set', () => { - let savedEnv = process.env; - beforeAll(() => { - process.env = { ...process.env, MOSAIC_SNAPSHOT_DIR: '/some/snapshots' }; - mockFs({ - 'some/snapshots/mynamespace/mydir': { - 'mypage.mdx': 'my content' - } - }); - }); - afterAll(() => { - mockFs.restore(); - process.env = savedEnv; - }); - - test('THEN a snapshot can be loaded from a local file', async () => { - // arrange - const content = await withMDXContent({ - resolvedUrl: '/mynamespace/mydir/mypage.mdx', - res: { getHeader: () => 'snapshot-file' } - }); - expect(content).toEqual({ props: { raw: 'my content', source: 'my content', type: 'mdx' } }); - }); - }); - - describe('WHEN dynamic Mosaic mode is set', () => { - beforeAll(() => { - fetchMock.enableMocks(); - fetchMock.mockOnce('my content'); - }); - afterAll(() => { - fetchMock.disableMocks(); - }); - test('THEN content is fetched from the data source', async () => { - // arrange - const content = await withMDXContent({ - resolvedUrl: '/mynamespace/mypage.mdx', - res: { getHeader: () => '/dynamic' } - }); - // assert - expect(content).toEqual({ props: { raw: 'my content', source: 'my content', type: 'mdx' } }); - }); - }); -}); diff --git a/packages/site-middleware/src/__tests__/withSearchIndex.test.ts b/packages/site-middleware/src/__tests__/withSearchIndex.test.ts deleted file mode 100644 index 5da3cd2a..00000000 --- a/packages/site-middleware/src/__tests__/withSearchIndex.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { describe, expect, beforeAll, afterAll, test, afterEach, beforeEach, vi } from 'vitest'; -import { GetObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { AwsStub, mockClient } from 'aws-sdk-client-mock'; -import { sdkStreamMixin } from '@smithy/util-stream'; -import { Readable } from 'stream'; -import createFetchMock from 'vitest-fetch-mock'; -const mockFs = require('mock-fs'); - -import { withSearchIndex } from '../withSearchIndex'; - -declare var process: { - env: { - MOSAIC_S3_BUCKET?: string; - MOSAIC_S3_REGION?: string; - MOSAIC_S3_ACCESS_KEY_ID?: string; - MOSAIC_S3_SECRET_ACCESS_KEY?: string; - MOSAIC_SNAPSHOT_DIR?: string; - }; -}; - -const fetchMock = createFetchMock(vi); - -describe('GIVEN withSearchIndex', () => { - describe('WHEN snapshot-s3 Mosaic mode is set', () => { - let savedEnv = process.env; - let s3ClientMock: AwsStub<{}, { $metadata: {} }>; - beforeAll(() => { - process.env = { - ...process.env, - MOSAIC_S3_BUCKET: 'some-bucket', - MOSAIC_S3_REGION: 'some-region', - MOSAIC_S3_ACCESS_KEY_ID: 'some-access-key', - MOSAIC_S3_SECRET_ACCESS_KEY: 'some-secret-key' - }; - s3ClientMock = mockClient(S3Client); - const indexStream = new Readable(); - indexStream.push('{ "someValue": true }'); - indexStream.push(null); // end of stream - const indexContentStream = sdkStreamMixin(indexStream); - s3ClientMock - .on(GetObjectCommand, { - Bucket: 'some-bucket', - Key: 'search-data-condensed.json' - }) - .resolves({ Body: indexContentStream }); - s3ClientMock - .on(HeadObjectCommand, { - Bucket: 'some-bucket', - Key: 'search-data-condensed.json' - }) - .resolvesOnce({ $metadata: { httpStatusCode: 200 } }) - .resolves({ $metadata: { httpStatusCode: 404 } }); - const configStream = new Readable(); - configStream.push('{ "someConfigValue": true }'); - configStream.push(null); // end of stream - const configContentStream = sdkStreamMixin(configStream); - s3ClientMock - .on(GetObjectCommand, { - Bucket: 'some-bucket', - Key: 'search-config.json' - }) - .resolves({ Body: configContentStream }); - s3ClientMock - .on(HeadObjectCommand, { - Bucket: 'some-bucket', - Key: 'search-config.json' - }) - .resolvesOnce({ $metadata: { httpStatusCode: 200 } }) - .resolves({ $metadata: { httpStatusCode: 404 } }); - }); - afterAll(() => { - process.env = savedEnv; - s3ClientMock.reset(); - }); - test('THEN search-index can be loaded from an S3 bucket', async () => { - // arrange - const content = await withSearchIndex({ - resolvedUrl: '/mynamespace/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? '/mynamespace' : 'snapshot-s3') - } - }); - // assert - expect(content).toEqual({ - props: { searchConfig: { someConfigValue: true }, searchIndex: { someValue: true } } - }); - }); - test('THEN does not throw for a non-existent search-index', async () => { - // arrange - const content = await withSearchIndex({ - resolvedUrl: '/non-existent/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? '/mynamespace' : 'snapshot-s3') - } - }); - // assert - expect(content).toEqual({ props: {} }); - }); - }); - - describe('WHEN snapshot-file Mosaic mode is set', () => { - let savedEnv = process.env; - beforeEach(() => { - process.env = { MOSAIC_SNAPSHOT_DIR: '/some/snapshots' }; - }); - afterEach(() => { - process.env = savedEnv; - }); - test('THEN reads search-index from a local file', async () => { - // arrange - mockFs({ - 'some/snapshots/': { - 'search-data-condensed.json': '{ "someValue": true }', - 'search-config.json': '{ "someConfigValue": true }' - } - }); - const content = await withSearchIndex({ - resolvedUrl: '/mynamespace/mydir/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? '/mydomain' : 'snapshot-file') - } - }); - expect(content).toEqual({ - props: { searchConfig: { someConfigValue: true }, searchIndex: { someValue: true } } - }); - mockFs.restore(); - }); - test('THEN does not throw for a non-existent search-index', async () => { - // arrange - const content = await withSearchIndex({ - resolvedUrl: '/mynamespace/non-existent/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? '/mydomain' : 'snapshot-file') - } - }); - console.log({ content }); - expect(content).toEqual({ props: {} }); - }); - }); - - describe('WHEN active Mosaic mode is set', () => { - beforeAll(() => { - fetchMock.enableMocks(); - fetchMock.mockResponses( - [JSON.stringify({ someValue: true }), { status: 200 }], - [JSON.stringify({ someConfigValue: true }), { status: 200 }], - ['', { status: 404 }], - ['', { status: 404 }] - ); - }); - afterAll(() => { - fetchMock.disableMocks(); - }); - test('THEN search-index is fetched from the data source', async () => { - // arrange - const content = await withSearchIndex({ - resolvedUrl: '/mynamespace/mypage.mdx', - res: { getHeader: name => (name === 'X-Mosaic-Content-Url' ? '/mydomain' : 'active') } - }); - // assert - expect(content).toEqual({ - props: { searchConfig: { someConfigValue: true }, searchIndex: { someValue: true } } - }); - }); - test('THEN does not throw for a non-existent search-index', async () => { - // arrange - const content = await withSearchIndex({ - resolvedUrl: '/mynamespace/non-existent/mypage.mdx', - res: { getHeader: name => (name === 'X-Mosaic-Content-Url' ? '/mydomain' : 'active') } - }); - // assert - expect(content).toEqual({ props: {} }); - }); - }); -}); diff --git a/packages/site-middleware/src/__tests__/withSharedConfig.test.ts b/packages/site-middleware/src/__tests__/withSharedConfig.test.ts deleted file mode 100644 index 0175683b..00000000 --- a/packages/site-middleware/src/__tests__/withSharedConfig.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { describe, test, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest'; -import { GetObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { AwsStub, mockClient } from 'aws-sdk-client-mock'; -import { sdkStreamMixin } from '@smithy/util-stream'; -import { Readable } from 'stream'; -import createFetchMock from 'vitest-fetch-mock'; -import { vol, fs } from 'memfs'; - -import { withSharedConfig } from '../withSharedConfig'; - -const fetchMock = createFetchMock(vi); - -declare var process: { - env: { - MOSAIC_S3_BUCKET?: string; - MOSAIC_S3_REGION?: string; - MOSAIC_S3_ACCESS_KEY_ID?: string; - MOSAIC_S3_SECRET_ACCESS_KEY?: string; - MOSAIC_SNAPSHOT_DIR?: string; - }; -}; - -describe('GIVEN withSharedConfig', () => { - describe('WHEN snapshot-s3 Mosaic mode is set', () => { - let savedEnv = process.env; - let s3ClientMock: AwsStub<{}, { $metadata: {} }>; - beforeAll(() => { - process.env = { - ...process.env, - MOSAIC_S3_BUCKET: 'some-bucket', - MOSAIC_S3_REGION: 'some-region', - MOSAIC_S3_ACCESS_KEY_ID: 'some-access-key', - MOSAIC_S3_SECRET_ACCESS_KEY: 'some-secret-key' - }; - s3ClientMock = mockClient(S3Client); - const stream = new Readable(); - stream.push('{"config": { "someValue": true }}'); - stream.push(null); // end of stream - const contentStream = sdkStreamMixin(stream); - s3ClientMock - .on(GetObjectCommand, { - Bucket: 'some-bucket', - Key: 'mynamespace/shared-config.json' - }) - .resolves({ Body: contentStream }); - s3ClientMock - .on(HeadObjectCommand, { - Bucket: 'some-bucket', - Key: 'mynamespace/shared-config.json' - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - s3ClientMock - .on(HeadObjectCommand, { - Bucket: 'some-bucket', - Key: 'non-existent/shared-config.json' - }) - .resolves({ $metadata: { httpStatusCode: 404 } }); - }); - afterAll(() => { - process.env = savedEnv; - s3ClientMock.reset(); - }); - test('THEN shared-config can be loaded from an S3 bucket', async () => { - // arrange - const content = await withSharedConfig({ - resolvedUrl: '/mynamespace/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? 'http://mydomain' : 'snapshot-s3') - } - }); - // assert - expect(content).toEqual({ props: { sharedConfig: { someValue: true } } }); - }); - test('THEN does not throw for a non-existent shared-config', async () => { - // arrange - const content = await withSharedConfig({ - resolvedUrl: '/non-existent/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? 'http://mydomain' : 'snapshot-s3') - } - }); - // assert - expect(content).toEqual({ props: {} }); - }); - }); - - describe('WHEN snapshot-file Mosaic mode is set', () => { - let savedEnv = process.env; - beforeEach(() => { - process.env = { MOSAIC_SNAPSHOT_DIR: '/some/snapshots' }; - vol.fromNestedJSON({ - 'some/snapshots/mynamespace/mydir': { - 'shared-config.json': '{"config": { "someValue": true }}' - } - }); - vi.mock('fs', () => ({ default: fs })); - }); - afterEach(() => { - vol.reset(); - process.env = savedEnv; - }); - - test('THEN reads shared-config from a local file', async () => { - // arrange - const content = await withSharedConfig({ - resolvedUrl: '/mynamespace/mydir/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? 'http://mydomain' : 'snapshot-file') - } - }); - expect(content).toEqual({ props: { sharedConfig: { someValue: true } } }); - }); - test('THEN does not throw for a non-existent shared-config', async () => { - // arrange - const content = await withSharedConfig({ - resolvedUrl: '/mynamespace/non-existent/mypage.mdx', - res: { - getHeader: name => (name === 'X-Mosaic-Content-Url' ? 'http://mydomain' : 'snapshot-file') - } - }); - expect(content).toEqual({ props: {} }); - }); - }); - - describe('WHEN active Mosaic mode is set', () => { - beforeAll(() => { - fetchMock.enableMocks(); - fetchMock.mockResponses( - [JSON.stringify({ config: { someValue: true } }), { status: 200 }], - ['', { status: 404 }] - ); - }); - afterAll(() => { - fetchMock.disableMocks(); - }); - test('THEN shared-config is fetched from the data source', async () => { - // arrange - const content = await withSharedConfig({ - resolvedUrl: '/mynamespace/mypage.mdx', - res: { getHeader: name => (name === 'X-Mosaic-Content-Url' ? 'http://mydomain' : 'active') } - }); - // assert - expect(content).toEqual({ props: { sharedConfig: { someValue: true } } }); - }); - test('THEN does not throw for a non-existent shared-config', async () => { - // arrange - const content = await withSharedConfig({ - resolvedUrl: '/mynamespace/non-existent/mypage.mdx', - res: { getHeader: name => (name === 'X-Mosaic-Content-Url' ? 'http://mydomain' : 'active') } - }); - // assert - expect(content).toEqual({ props: {} }); - }); - }); -}); diff --git a/packages/site-middleware/src/compileMdx.ts b/packages/site-middleware/src/compileMdx.ts deleted file mode 100644 index e3fd093e..00000000 --- a/packages/site-middleware/src/compileMdx.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { serialize } from 'next-mdx-remote/serialize'; -import remarkGfm from 'remark-gfm'; -import rehypeSlug from 'rehype-slug'; -import { codeBlocks } from './plugins/codeBlocks.js'; - -export async function compileMDX( - content, - parseFrontmatter = true, - rehypePlugins = [], - remarkPlugins = [] -) { - const mdxSource = await serialize(content, { - mdxOptions: { - rehypePlugins: [codeBlocks, rehypeSlug, ...remarkPlugins], - remarkPlugins: [remarkGfm, ...rehypePlugins] - }, - parseFrontmatter - }); - return mdxSource; -} diff --git a/packages/site-middleware/src/createMiddlewareRunner.ts b/packages/site-middleware/src/createMiddlewareRunner.ts deleted file mode 100644 index 7716f957..00000000 --- a/packages/site-middleware/src/createMiddlewareRunner.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { GetServerSidePropsContext } from 'next'; -import deepmerge from 'deepmerge'; - -import type { MosaicAppProps } from './middlewarePresets.js'; -import MiddlewareError from './MiddlewareError.js'; - -const overwriteMerge = (_, sourceArray) => sourceArray; - -/** Props returned after running Middleware */ -export type MiddlewareResult = Awaited>; - -/** Options to be passed to Middleware */ -export type MosaicMiddlewareOptions = Record; - -/** Middleware callbacks return props to be added to the page */ -export type MosaicMiddleware = ( - /** Context provides information on the current page */ - context: GetServerSidePropsContext, - /** Middleware specific options */ - options?: TOptions, - /** Previous result created by plugins */ - lastState?: Record -) => Promise>; - -/** Middleware with a configuration object specifying options */ -export type MosaicMiddlewareWithConfig = [ - MosaicMiddleware, - TConfig -]; - -/** Function to run all provided middleware */ -export type MosaicMiddlewareRunner = ( - context: GetServerSidePropsContext, - options: MosaicMiddlewareOptions -) => Promise>; - -/** - * Creates a middleware runner from the collection of provided middleware - * @param initialState initial state - * @param middleware a collection of middleware to be executed in order to get the page props - * @returns a middleware runner - */ -export function createMiddlewareRunner( - initialState: Record, - middleware: Array | MosaicMiddlewareWithConfig> -): MosaicMiddlewareRunner { - return async function middlewareRunner(context, options) { - let result: MiddlewareResult = initialState; - const errors: Error[] = []; - // eslint-disable-next-line no-restricted-syntax - for (const fnOrConfig of middleware) { - let fn: MosaicMiddleware; - let finalOptions = options; - try { - if (typeof fnOrConfig === 'function') { - fn = fnOrConfig; - } else { - fn = fnOrConfig[0]; - finalOptions = { ...options, ...fnOrConfig[1] }; - } - // eslint-disable-next-line no-await-in-loop - const nextState = await fn(context, finalOptions, result); - result = deepmerge>(result, nextState, { - arrayMerge: overwriteMerge - }); - } catch (error) { - if (error instanceof Error) { - errors.push(error); - } else { - const unexpectedError = new MiddlewareError(500, undefined, [String(error)], { - show500: true - }); - errors.push(unexpectedError); - } - } - } - if (result.redirect) { - return { redirect: result.redirect }; - } - const show404 = errors.some(error => { - if (error instanceof MiddlewareError) { - return error.props?.show404; - } - return false; - }); - const show500 = - !show404 && - errors.some(error => { - if (error instanceof MiddlewareError) { - return error.props?.show500; - } - return true; - }); - - if (show500) { - console.error('An un-expected error(s) was thrown which caused the 500 page to appear'); - errors.forEach(console.error); - } - - if (show404 || show500) { - context.res.setHeader(`X-Mosaic-${show404 ? '404' : '500'}`, 'true'); - } - const props: TProps = { ...result.props, show404, show500 } as TProps; - return { props }; - }; -} diff --git a/packages/site-middleware/src/index.ts b/packages/site-middleware/src/index.ts deleted file mode 100644 index 79134514..00000000 --- a/packages/site-middleware/src/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './compileMdx.js'; -export * from './createMiddlewareRunner.js'; -export * from './MiddlewareError.js'; -export * from './middlewarePresets.js'; -export * from './withMDXContent.js'; -export * from './withMosaicMode.js'; -export * from './withSearchIndex.js'; -export * from './withSession.js'; -export * from './withSharedConfig.js'; diff --git a/packages/site-middleware/src/loaders/__tests__/createS3Loader.test.ts b/packages/site-middleware/src/loaders/__tests__/createS3Loader.test.ts deleted file mode 100644 index 97e16acc..00000000 --- a/packages/site-middleware/src/loaders/__tests__/createS3Loader.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { describe, test, expect, beforeAll, afterAll } from 'vitest'; -import { GetObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { AwsStub, mockClient } from 'aws-sdk-client-mock'; -import { sdkStreamMixin } from '@smithy/util-stream'; -import { Readable } from 'stream'; - -import { createS3Loader } from '../index.js'; - -describe('GIVEN createS3Loader', () => { - let savedEnv = process.env; - let s3ClientMock: AwsStub<{}, { $metadata: {} }>; - beforeAll(() => { - process.env = { - ...process.env, - MOSAIC_S3_BUCKET: 'some-bucket', - MOSAIC_S3_REGION: 'some-region', - MOSAIC_S3_ACCESS_KEY_ID: 'some-access-key', - MOSAIC_S3_SECRET_ACCESS_KEY: 'some-secret-key' - }; - s3ClientMock = mockClient(S3Client); - const stream = new Readable(); - stream.push('some-content'); - stream.push(null); // end of stream - const contentStream = sdkStreamMixin(stream); - s3ClientMock.on(GetObjectCommand).resolves({ Body: contentStream }); - s3ClientMock - .on(HeadObjectCommand, { - Bucket: 'some-bucket', - Key: 'some-key' - }) - .resolves({ $metadata: { httpStatusCode: 200 } }); - s3ClientMock - .on(HeadObjectCommand, { - Bucket: 'some-bucket', - Key: 'non-existent-key' - }) - .resolves({ $metadata: { httpStatusCode: 404 } }); - }); - afterAll(() => { - process.env = savedEnv; - s3ClientMock.reset(); - }); - test('THEN the S3 loader can load keys from buckets', async () => { - // arrange - const s3Client = createS3Loader('some-bucket', 'some-access-key', 'some-secret-key'); - // assert - expect(await s3Client.loadKey('some-bucket', 'some-key')).toEqual('some-content'); - }); - test('THEN the S3 loader can check keys exist in buckets', async () => { - // arrange - const s3Client = createS3Loader('some-bucket', 'some-access-key', 'some-secret-key'); - // assert - expect(await s3Client.keyExists('some-bucket', 'some-key')).toEqual(true); - expect(await s3Client.keyExists('some-bucket', 'non-existent-key')).toEqual(false); - }); -}); diff --git a/packages/site-middleware/src/loaders/__tests__/getS3Config.test.ts b/packages/site-middleware/src/loaders/__tests__/getS3Config.test.ts deleted file mode 100644 index 122ae4de..00000000 --- a/packages/site-middleware/src/loaders/__tests__/getS3Config.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, beforeAll, test, expect, afterAll } from 'vitest'; -import { getSnapshotS3Config } from '../index.js'; -describe('GIVEN getS3Config', () => { - describe('WHEN valid config is defined', () => { - let savedEnv = process.env; - beforeAll(() => { - process.env = { - ...process.env, - MOSAIC_S3_BUCKET: 'some-bucket', - MOSAIC_S3_REGION: 'some-region', - MOSAIC_S3_ACCESS_KEY_ID: 'some-access-key', - MOSAIC_S3_SECRET_ACCESS_KEY: 'some-secret-key' - }; - }); - afterAll(() => { - process.env = savedEnv; - }); - test('THEN it returns the S3 config', async () => { - // arrange - const config = getSnapshotS3Config('some-key'); - // assert - expect(config).toEqual({ - bucket: 'some-bucket', - region: 'some-region', - accessKeyId: 'some-access-key', - secretAccessKey: 'some-secret-key' - }); - }); - }); - describe('WHEN invalid config is defined', () => { - let savedEnv = process.env; - beforeAll(() => { - process.env = {}; - }); - afterAll(() => { - process.env = savedEnv; - }); - test('THEN it throws an error', () => { - // assert - expect(() => getSnapshotS3Config('some-key')).toThrow( - /Environment variables missing for loading of S3 content for key some-key/ - ); - }); - }); -}); diff --git a/packages/site-middleware/src/loaders/__tests__/loadLocalFile.test.ts b/packages/site-middleware/src/loaders/__tests__/loadLocalFile.test.ts deleted file mode 100644 index 6b99bb0b..00000000 --- a/packages/site-middleware/src/loaders/__tests__/loadLocalFile.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { describe, beforeEach, afterEach, test, expect, vi } from 'vitest'; -import { vol, fs } from 'memfs'; -import { loadLocalFile } from '../index.js'; - -vi.mock('fs', () => ({ - default: fs -})); - -describe('GIVEN loadLocalFile', () => { - beforeEach(() => { - vol.fromNestedJSON({ - 'some/snapshots/mynamespace/mydir': 'some-content', - 'some/snapshots/mynamespace/dir/index': 'directory index content' - }); - }); - afterEach(() => { - vol.reset(); - }); - - test('THEN it can read from a local file', async () => { - // arrange - const content = await loadLocalFile('some/snapshots/mynamespace/mydir'); - // assert - expect(content).toEqual('some-content'); - }); - test('THEN it throws and error when the local file does not exist', async () => { - // assert - await expect(loadLocalFile('some/non-existent/mynamespace/mydir')).rejects.toThrow( - "ENOENT: no such file or directory, stat 'some/non-existent/mynamespace/mydir'" - ); - }); - test('THEN it loads the index file if a directory is requested', async () => { - // arrange - const content = await loadLocalFile('some/snapshots/mynamespace/dir'); - // assert - expect(content).toEqual('directory index content'); - }); -}); diff --git a/packages/site-middleware/src/loaders/createS3Loader.ts b/packages/site-middleware/src/loaders/createS3Loader.ts deleted file mode 100644 index b372709a..00000000 --- a/packages/site-middleware/src/loaders/createS3Loader.ts +++ /dev/null @@ -1,61 +0,0 @@ -import md5 from 'md5'; -import { GetObjectCommand, HeadObjectCommand, S3Client } from '@aws-sdk/client-s3'; -import { MetadataBearer } from '@aws-sdk/types'; - -export function createClient(region, accessKeyId, secretAccessKey): S3Client { - return new S3Client({ - region, - credentials: { - accessKeyId, - secretAccessKey - } - }); -} - -async function readFile(client, bucket, key) { - const params = { - Bucket: bucket, - Key: key - }; - const command = new GetObjectCommand(params); - const response = await client.send(command); - return response; -} - -const clientCache = {}; -export function createS3Loader(region, accessKeyId, secretAccessKey) { - const clientHash = md5(`${region}${accessKeyId}${secretAccessKey}`); - if (!clientCache[clientHash]) { - clientCache[clientHash] = createClient(region, accessKeyId, secretAccessKey); - } - const client = clientCache[clientHash]; - - return { - loadKey: async (bucket: string, key: string): Promise => { - const data = await readFile(client, bucket, key); - const text = await data.Body.transformToString(); - return text; - }, - keyExists: async (bucket: string, key: string): Promise => { - const params = { - Bucket: bucket, - Key: key - }; - const command = new HeadObjectCommand(params); - let exists = false; - - try { - const response = await client.send(command); - exists = response?.$metadata?.httpStatusCode === 200; - } catch (error) { - const bearer: MetadataBearer = error as MetadataBearer; - if (bearer?.$metadata?.httpStatusCode === 404) { - exists = false; - } else { - throw error; - } - } - return !!exists; - } - }; -} diff --git a/packages/site-middleware/src/loaders/getSnapshotFileConfig.ts b/packages/site-middleware/src/loaders/getSnapshotFileConfig.ts deleted file mode 100644 index 764fe5f7..00000000 --- a/packages/site-middleware/src/loaders/getSnapshotFileConfig.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { snapshotFileEnvSchema } from '@jpmorganchase/mosaic-schemas'; - -export const getSnapshotFileConfig = url => { - const env = snapshotFileEnvSchema.safeParse(process.env); - if (!env.success) { - env.error.issues.forEach(issue => { - console.error( - `Missing process.env.${issue.path.join()} environment variable required to load path ${url} from local snapshot` - ); - }); - throw new Error(`Environment variables missing for loading of ${url} for local snapshot`); - } - return { - snapshotDir: env.data.MOSAIC_SNAPSHOT_DIR - }; -}; diff --git a/packages/site-middleware/src/loaders/getSnapshotS3Config.ts b/packages/site-middleware/src/loaders/getSnapshotS3Config.ts deleted file mode 100644 index 0f04a16b..00000000 --- a/packages/site-middleware/src/loaders/getSnapshotS3Config.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { snapshotS3EnvSchema } from '@jpmorganchase/mosaic-schemas'; - -export function getSnapshotS3Config(key) { - const config = snapshotS3EnvSchema.safeParse(process.env); - if (!config.success) { - config.error.issues.forEach(issue => { - console.error( - `Missing process.env.${issue.path.join()} environment variable required to load S3 bucket ${key}` - ); - }); - throw new Error(`Environment variables missing for loading of S3 content for key ${key}`); - } - return { - bucket: config.data.MOSAIC_S3_BUCKET, - region: config.data.MOSAIC_S3_REGION, - accessKeyId: config.data.MOSAIC_S3_ACCESS_KEY_ID, - secretAccessKey: config.data.MOSAIC_S3_SECRET_ACCESS_KEY - }; -} diff --git a/packages/site-middleware/src/loaders/index.ts b/packages/site-middleware/src/loaders/index.ts deleted file mode 100644 index c15e0563..00000000 --- a/packages/site-middleware/src/loaders/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './loadLocalFile.js'; -export * from './createS3Loader.js'; -export * from './getSnapshotS3Config'; -export * from './getSnapshotFileConfig.js'; diff --git a/packages/site-middleware/src/loaders/loadLocalFile.ts b/packages/site-middleware/src/loaders/loadLocalFile.ts deleted file mode 100644 index 85e60228..00000000 --- a/packages/site-middleware/src/loaders/loadLocalFile.ts +++ /dev/null @@ -1,12 +0,0 @@ -import fs from 'fs'; -import path from 'path'; - -export const loadLocalFile = async (filePath: string): Promise => { - let localPath = filePath; - if ((await fs.promises.stat(filePath)).isDirectory()) { - localPath = path.posix.join(localPath, 'index'); - } - const realPath = await fs.promises.realpath(localPath); - const data = await fs.promises.readFile(realPath, 'utf-8'); - return data.toString(); -}; diff --git a/packages/site-middleware/src/middlewarePresets.ts b/packages/site-middleware/src/middlewarePresets.ts deleted file mode 100644 index ae9737c5..00000000 --- a/packages/site-middleware/src/middlewarePresets.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Redirect } from 'next'; -import { SearchIndexSlice, SharedConfigSlice } from '@jpmorganchase/mosaic-store'; -import type { ContentProps } from '@jpmorganchase/mosaic-types'; -import { withSearchIndex } from './withSearchIndex.js'; -import { withSharedConfig } from './withSharedConfig.js'; -import { withMDXContent } from './withMDXContent.js'; -import { MosaicMiddleware, MosaicMiddlewareWithConfig } from './createMiddlewareRunner.js'; -import { withMosaicMode, type MosaicModeProps } from './withMosaicMode.js'; - -export type BaseMosaicAppProps = { - /** Flag to show custom 404 page, whilst maintaining current state/layout */ - show404?: boolean; - /** Flag to show custom 500 page, whilst maintaining current state/layout */ - show500?: boolean; -}; - -/** Abstract Mosaic App Props */ -export type MosaicAppProps = { - /** Page props created by [[`Middleware`]] */ - props?: T; - /** Error description */ - errors?: { - /** Stacktrace * */ - location?: string; - /** Error message * */ - statusText?: string; - /** Error code */ - status?: number; - } & BaseMosaicAppProps; - /** Next JS Redirect */ - redirect?: Redirect; -}; - -/** Mosaic getServerSideProps result which supports error handling */ -export type GetMosaicServerSidePropsResult = MosaicAppProps; - -/** MiddlewarePresets props */ -export type MiddlewarePresetsProps = MosaicModeProps & - ContentProps & - SearchIndexSlice & - SharedConfigSlice; - -/** A collection of preset [[`Middleware`]] plugins that will compose together the page props */ -export const middlewarePresets: Array< - | MosaicMiddleware - | MosaicMiddlewareWithConfig -> = [withMosaicMode, withSharedConfig, withMDXContent, withSearchIndex]; diff --git a/packages/site-middleware/src/plugins/codeBlocks.ts b/packages/site-middleware/src/plugins/codeBlocks.ts deleted file mode 100644 index ef94d2a3..00000000 --- a/packages/site-middleware/src/plugins/codeBlocks.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { Root } from 'hast'; -import type { Plugin, Transformer } from 'unified'; -import { visitParents } from 'unist-util-visit-parents'; -import { fromMarkdown } from 'mdast-util-from-markdown'; -import { mdxjs } from 'micromark-extension-mdxjs'; -import { mdxFromMarkdown } from 'mdast-util-mdx'; -import { type MdxJsxFlowElementHast } from 'mdast-util-mdx-jsx'; -// @ts-ignore -import { propertiesToMdxJsxAttributes } from 'hast-util-properties-to-mdx-jsx-attributes'; - -/** - * Modified from: https://github.com/remcohaszing/rehype-mdx-code-props/blob/main/src/rehype-mdx-code-props.ts - * - * Custom meta parser for codefences that have extra params. e.g. - * ```jsx filename="hello.jsx" - *
    Test!
    - * ``` - * - * Custom meta parser for codefences that have eval or eval="true". - * This allows vars to be resolved and their values used as the code block body. - * ```jsx eval - * meta.data.someStringOnlyAvailableAtRuntime - * ``` - */ -export const transformer: Transformer = ast => { - visitParents(ast, 'element', (node, ancestors) => { - if (node.tagName !== 'code') { - return; - } - - const meta = node.data?.meta; - - if (typeof meta !== 'string' || !meta) { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - let parent = ancestors.at(-1)!; - - if (parent.type !== 'element') { - return; - } - - if (parent.tagName !== 'pre') { - return; - } - - if (parent.children.length !== 1) { - return; - } - - const child = parent; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - parent = ancestors.at(-2)!; - - // Limit eval to just basic strings that start with "meta." - // const isEval = - // /(^| )eval(="true"| |$)/.test(meta) && /^meta\.[a-z0-9_[\].$"']+$/i.test(child.value); - // - // let code; - // if (!isEval) { - // code = JSON.stringify(`${child.value}\n`); - // } else { - // code = child.value; - // } - - const replacement = fromMarkdown(`<${child.tagName} ${meta} />`, { - extensions: [mdxjs()], - mdastExtensions: [mdxFromMarkdown()] - }).children[0] as MdxJsxFlowElementHast; - replacement.children = child.children; - replacement.data = child.data; - replacement.position = child.position; - replacement.attributes.unshift( - ...propertiesToMdxJsxAttributes(child.properties, { elementAttributeNameCase: 'react' }) - ); - - parent.children[parent.children.indexOf(child)] = replacement; - }); -}; - -/** - * A markdown plugin for transforming code metadata. - * - * @returns A unified transformer. - */ -export const codeBlocks: Plugin<[], Root> = () => transformer; diff --git a/packages/site-middleware/src/withMDXContent.ts b/packages/site-middleware/src/withMDXContent.ts deleted file mode 100644 index c4d02828..00000000 --- a/packages/site-middleware/src/withMDXContent.ts +++ /dev/null @@ -1,123 +0,0 @@ -import path from 'path'; -import { GetServerSidePropsContext } from 'next'; -import type { ContentProps, MosaicMode } from '@jpmorganchase/mosaic-types'; -import { MosaicMiddleware } from './createMiddlewareRunner.js'; -import MiddlewareError from './MiddlewareError.js'; -import { - createS3Loader, - getSnapshotFileConfig, - getSnapshotS3Config, - loadLocalFile -} from './loaders'; -import { compileMDX } from './compileMdx.js'; - -if (typeof window !== 'undefined') { - throw new Error('This file should not be loaded on the client.'); -} -function stripParams(resolvedUrl: string) { - const url = new URL(resolvedUrl, 'https://example.com'); - return url.pathname; -} - -function normalizeUrl(url: string) { - return /\/index$/.test(url) ? `${url}.mdx` : url; -} - -async function loadSnapshotFile(url) { - const { snapshotDir } = getSnapshotFileConfig(url); - const normalizedUrl = normalizeUrl(url); - const filePath = path.posix.join(process.cwd(), snapshotDir, normalizedUrl); - try { - return await loadLocalFile(filePath); - } catch (error) { - if (error instanceof Error) { - console.error(error.message); - } - throw new MiddlewareError(404, url, [`Could not read local file '${filePath}' for '${url}'`], { - show404: true - }); - } -} - -async function loadSnapshotS3(url) { - let text; - try { - const { accessKeyId, bucket, region, secretAccessKey } = getSnapshotS3Config(url); - const s3Loader = createS3Loader(region, accessKeyId, secretAccessKey); - const normalizedUrl = normalizeUrl(url); - const s3Key = normalizedUrl.replace(/^\//, ''); - text = await s3Loader.loadKey(bucket, s3Key); - } catch (error) { - if (error instanceof Error) { - console.error(error.message); - } - throw new MiddlewareError(404, url, [`Could not find an S3 object for '${url}'`], { - show404: true - }); - } - return text; -} - -async function loadActiveContent(url) { - let text; - const normalizedUrl = normalizeUrl(url); - const response = await fetch(normalizedUrl); - if (response.ok) { - text = await response.text(); - // If redirect url was returned - } else if (response.status === 302) { - return { - redirect: { - destination: (await response.json()).redirect, - permanent: true - } - }; - } else { - throw new MiddlewareError(404, url, [`Could not fetch any content for ${url}`], { - show404: true - }); - } - return text; -} -/** - * Adds the [[`type`, `source`, `raw`,]] object to the page props - * @param context - */ -export const withMDXContent: MosaicMiddleware = async ( - context: GetServerSidePropsContext -) => { - const { resolvedUrl } = context; - const mosaicMode = (context.res.getHeader('X-Mosaic-Mode') || 'active') as MosaicMode; - const extname = path.extname(resolvedUrl); - const pathname = stripParams(resolvedUrl); - // Any urls which are not prefixed, will default to MDX - const isMDX = extname === '.mdx' || extname === ''; - if (!isMDX) { - return {}; - } - let text; - if (mosaicMode === 'snapshot-file') { - text = await loadSnapshotFile(pathname); - } else if (mosaicMode === 'snapshot-s3') { - text = await loadSnapshotS3(pathname); - } else { - const mosaicUrl = context.res.getHeader('X-Mosaic-Content-Url'); - const fetchedResult = await loadActiveContent(`${mosaicUrl}${pathname}`); - const isRedirect = typeof fetchedResult === 'object'; - if (isRedirect) { - return fetchedResult; - } - text = fetchedResult; - } - try { - const mdxSource = await compileMDX(text); - return { props: { type: 'mdx', source: mdxSource, raw: text } }; - } catch (error) { - console.error(error); - if (error instanceof Error) { - throw new MiddlewareError(500, resolvedUrl, [error.message], { show500: true }); - } else { - throw new MiddlewareError(500, resolvedUrl, ['unexpected error'], { show500: true }); - } - } -}; diff --git a/packages/site-middleware/src/withMosaicMode.ts b/packages/site-middleware/src/withMosaicMode.ts deleted file mode 100644 index 2ada9dbb..00000000 --- a/packages/site-middleware/src/withMosaicMode.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { MosaicMode } from '@jpmorganchase/mosaic-types'; - -import type { MosaicMiddleware } from './createMiddlewareRunner.js'; - -if (typeof window !== 'undefined') { - throw new Error('This file should not be loaded on the client.'); -} - -/** - * [[`MosaicModeProps`]] specifies the mode mosaic uses - */ -export interface MosaicModeProps { - mode?: MosaicMode; -} - -/** - * Adds the [[`MosaicModeProps`]] object to the page props - * @param context - */ -export const withMosaicMode: MosaicMiddleware = async context => { - const mode: MosaicMode = (process.env.MOSAIC_MODE || 'active') as MosaicMode; - const mosaicContentUrl = process.env[`MOSAIC_${mode.toUpperCase()}_MODE_URL`] || ''; - context.res.setHeader('X-Mosaic-Mode', mode); - context.res.setHeader('X-Mosaic-Content-Url', mosaicContentUrl); - return { props: { mode } }; -}; diff --git a/packages/site-middleware/src/withSearchIndex.ts b/packages/site-middleware/src/withSearchIndex.ts deleted file mode 100644 index 887c1215..00000000 --- a/packages/site-middleware/src/withSearchIndex.ts +++ /dev/null @@ -1,104 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { GetServerSidePropsContext } from 'next'; -import type { SearchIndexSlice } from '@jpmorganchase/mosaic-store'; -import { MosaicMiddleware } from './createMiddlewareRunner.js'; -import MiddlewareError from './MiddlewareError.js'; -import { - createS3Loader, - getSnapshotFileConfig, - getSnapshotS3Config, - loadLocalFile -} from './loaders'; - -if (typeof window !== 'undefined') { - throw new Error('This file should not be loaded on the client.'); -} - -const searchDataFile = 'search-data-condensed.json'; -const searchConfigFile = 'search-config.json'; - -const getSnapshotFile = async (urlPath, targetPath) => { - const { snapshotDir } = getSnapshotFileConfig(urlPath); - const filePath = path.join(process.cwd(), snapshotDir, targetPath); - try { - await fs.promises.stat(filePath); - const rawSearchIndex = await loadLocalFile(filePath); - return JSON.parse(rawSearchIndex); - } catch { - console.warn(`Could not load data from ${urlPath}/${targetPath}`); - return false; - } -}; - -const getSnapshotS3File = async (urlPath, targetPath) => { - const { accessKeyId, bucket, region, secretAccessKey } = getSnapshotS3Config(targetPath); - const { keyExists, loadKey } = createS3Loader(region, accessKeyId, secretAccessKey); - const s3KeyExists = await keyExists(bucket, targetPath); - if (s3KeyExists) { - const rawSearchIndex = await loadKey(bucket, targetPath); - return JSON.parse(rawSearchIndex); - } else { - console.warn(`Could not load data from ${urlPath}/${targetPath}`); - return false; - } -}; - -const getFechedFile = async (mosaicUrl, targetPath) => { - const response = await fetch(`${mosaicUrl}/${targetPath}`, { - headers: { - 'Content-Type': 'application/json' - } - }); - if (response.ok) { - return await response.json(); - } else if (response.status !== 404) { - throw Error(`${response.status} - ${response.statusText}`); - } else { - console.warn(`Could not load data from ${mosaicUrl}/${targetPath}`); - return false; - } -}; - -/** - * Adds the [[`searchIndex`]] props to the page props - * @param _context - * @returns site props object - */ -export const withSearchIndex: MosaicMiddleware = async ( - context: GetServerSidePropsContext -) => { - const { res, resolvedUrl } = context; - const isSnapshotFile = res.getHeader('X-Mosaic-Mode') === 'snapshot-file'; - const isSnapshotS3 = res.getHeader('X-Mosaic-Mode') === 'snapshot-s3'; - - const matches = resolvedUrl.match(/(.*)[!/]/); - const urlPath = matches?.length ? matches[1] : ''; - - try { - let searchIndex; - let searchConfig; - if (isSnapshotFile) { - searchIndex = await getSnapshotFile(urlPath, searchDataFile); - searchConfig = await getSnapshotFile(urlPath, searchConfigFile); - } else if (isSnapshotS3) { - searchIndex = await getSnapshotS3File(urlPath, searchDataFile); - searchConfig = await getSnapshotS3File(urlPath, searchConfigFile); - } else { - const mosaicUrl = res.getHeader('X-Mosaic-Content-Url'); - searchIndex = await getFechedFile(mosaicUrl, searchDataFile); - searchConfig = await getFechedFile(mosaicUrl, searchConfigFile); - } - if (searchIndex && searchConfig) { - return { props: { searchConfig, searchIndex } }; - } - return { props: {} }; - } catch (error) { - console.error(error); - let errorMessage = `Could not load any search index for ${resolvedUrl}`; - throw new MiddlewareError(500, resolvedUrl, [errorMessage], { - show404: false, - show500: true - }); - } -}; diff --git a/packages/site-middleware/src/withSession.ts b/packages/site-middleware/src/withSession.ts deleted file mode 100644 index a738d565..00000000 --- a/packages/site-middleware/src/withSession.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { type NextAuthOptions, getServerSession } from 'next-auth'; -import type { Session } from '@jpmorganchase/mosaic-types'; -import { MosaicMiddleware } from './createMiddlewareRunner.js'; -import MiddlewareError from './MiddlewareError.js'; - -if (typeof window !== 'undefined') { - throw new Error('This file should not be loaded on the client.'); -} - -/** - * [[`SessionProps`]] specifies session object containing user profile of logged in user - */ -export interface SessionProps { - session?: Session; -} - -/** - * [[`SessionOptions`]] specifies a configuration object for the withSession middleware - */ -export interface SessionOptions { - /** is login required for the environment */ - loginRequired: boolean; - authOptions: NextAuthOptions; -} - -/** - * Adds the [[`Session`]] object to the page props - * @param context - * @param options - */ -export const withSession: MosaicMiddleware, SessionOptions> = async ( - context, - options -) => { - if (process.env.NEXT_PUBLIC_ENABLE_LOGIN !== 'true') { - return {}; - } - - if (!options?.authOptions) { - const errorMessage = '`authOptions` must be provided.'; - throw new MiddlewareError(500, context.resolvedUrl, [errorMessage], { - show500: true - }); - } - - const session = await getServerSession(context.req, context.res, options.authOptions); - - return { - props: { - session: { - ...session, - isLoggedIn: session !== null - } - } - }; -}; diff --git a/packages/site-middleware/src/withSharedConfig.ts b/packages/site-middleware/src/withSharedConfig.ts deleted file mode 100644 index e8472450..00000000 --- a/packages/site-middleware/src/withSharedConfig.ts +++ /dev/null @@ -1,82 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import { GetServerSidePropsContext } from 'next'; -import type { SharedConfig, SharedConfigSlice } from '@jpmorganchase/mosaic-store'; -import { MosaicMiddleware } from './createMiddlewareRunner.js'; -import MiddlewareError from './MiddlewareError.js'; -import { - createS3Loader, - getSnapshotFileConfig, - getSnapshotS3Config, - loadLocalFile -} from './loaders'; - -if (typeof window !== 'undefined') { - throw new Error('This file should not be loaded on the client.'); -} - -/** - * Adds the [[`sharedConfig`]] props to the page props - * @param _context - * @param _params - */ -export const withSharedConfig: MosaicMiddleware = async ( - context: GetServerSidePropsContext -) => { - const { resolvedUrl, res } = context; - const isSnapshotFile = res.getHeader('X-Mosaic-Mode') === 'snapshot-file'; - const isSnapshotS3 = res.getHeader('X-Mosaic-Mode') === 'snapshot-s3'; - - const matches = resolvedUrl.match(/(.*)[!/]/); - const urlPath = matches?.length ? matches[1] : ''; - try { - let sharedConfig; - if (isSnapshotFile) { - const { snapshotDir } = getSnapshotFileConfig(urlPath); - const filePath = path.join(process.cwd(), snapshotDir, urlPath, 'shared-config.json'); - let fileExists = false; - try { - await fs.promises.stat(filePath); - fileExists = true; - } catch {} - if (fileExists) { - const rawSharedConfig = await loadLocalFile(filePath); - sharedConfig = JSON.parse(rawSharedConfig); - } - } else if (isSnapshotS3) { - const s3Key = `${urlPath}/shared-config.json`.replace(/^\//, ''); - const { accessKeyId, bucket, region, secretAccessKey } = getSnapshotS3Config(s3Key); - const { keyExists, loadKey } = createS3Loader(region, accessKeyId, secretAccessKey); - - const s3KeyExists = await keyExists(bucket, s3Key); - if (s3KeyExists) { - const rawSharedConfig = await loadKey(bucket, s3Key); - sharedConfig = JSON.parse(rawSharedConfig); - } - } else { - const mosaicUrl = res.getHeader('X-Mosaic-Content-Url'); - const response = await fetch(`${mosaicUrl}${urlPath}/shared-config.json`, { - headers: { - 'Content-Type': 'application/json' - } - }); - if (response.ok) { - sharedConfig = await response.json(); - } else if (response.status !== 404) { - throw Error(`${response.status} - ${response.statusText}`); - } - } - if (sharedConfig) { - const { config } = sharedConfig as { config: SharedConfig }; - return { props: { sharedConfig: config } }; - } - return { props: {} }; - } catch (error) { - console.error(error); - let errorMessage = `Could not load any shared config for ${resolvedUrl}`; - throw new MiddlewareError(500, resolvedUrl, [errorMessage], { - show404: false, - show500: true - }); - } -}; diff --git a/packages/site-preset-styles/package.json b/packages/site-preset-styles/package.json index 72891e7d..52985d64 100644 --- a/packages/site-preset-styles/package.json +++ b/packages/site-preset-styles/package.json @@ -30,8 +30,10 @@ "esbuild-node-externals": "^1.0.2" }, "dependencies": { - "@salt-ds/theme": "^1.19.0", + "@salt-ds/theme": "^1.23.0", "@salt-ds/icons": "^1.12.1", + "@salt-ds/core": "^1.37.1", + "@salt-ds/lab": "1.0.0-alpha.54", "@jpmorganchase/mosaic-components": "0.1.0-beta.89", "@jpmorganchase/mosaic-labs-components": "0.1.0-beta.89", "@jpmorganchase/mosaic-open-api-component": "0.1.0-beta.89", @@ -39,7 +41,7 @@ "@jpmorganchase/mosaic-site-components": "0.1.0-beta.89", "@jpmorganchase/mosaic-layouts": "0.1.0-beta.89", "@jpmorganchase/mosaic-theme": "0.1.0-beta.89", - "prismjs": "^1.23.0" + "@jpmorganchase/mosaic-mdx-components": "0.1.0-beta.89" }, "peerDependencies": {} } diff --git a/packages/site-preset-styles/src/index.js b/packages/site-preset-styles/src/index.js index 6aea1782..b8f90da8 100644 --- a/packages/site-preset-styles/src/index.js +++ b/packages/site-preset-styles/src/index.js @@ -1,11 +1,12 @@ import '@salt-ds/theme/css/global.css'; import '@salt-ds/theme/css/theme.css'; +import '@salt-ds/icons/css/salt-icon.css'; +import '@salt-ds/core/css/salt-core.css'; +import '@salt-ds/lab/css/salt-lab.css'; import '@jpmorganchase/mosaic-theme/index.css'; import '@jpmorganchase/mosaic-theme/baseline.css'; -import '@jpmorganchase/mosaic-theme/salt.css'; import '@jpmorganchase/mosaic-layouts/index.css'; import '@jpmorganchase/mosaic-site-components/index.css'; import '@jpmorganchase/mosaic-components/index.css'; -import '@jpmorganchase/mosaic-labs-components/index.css'; +import '@jpmorganchase/mosaic-mdx-components/index.css'; import '@jpmorganchase/mosaic-content-editor-plugin/index.css'; -import 'prismjs/themes/prism.css'; diff --git a/packages/site/.env.local b/packages/site/.env.local index 31464e4a..9d645c17 100644 --- a/packages/site/.env.local +++ b/packages/site/.env.local @@ -3,7 +3,6 @@ MOSAIC_SNAPSHOT_DIR=snapshots/latest OPTIMIZE_IMAGES=false NEXTAUTH_URL=http://localhost:3000 NODE_ENV=development -MOSAIC_ENABLE_SOURCE_PUSH=true NEXTAUTH_URL=http://localhost:3000/ NEXT_PUBLIC_MOSAIC_IBCE_PREVIEW_URL=/api/content/preview NEXT_PUBLIC_MOSAIC_WORKFLOWS_URL=ws://localhost:8080/workflows \ No newline at end of file diff --git a/packages/site/next-env.d.ts b/packages/site/next-env.d.ts index 4f11a03d..40c3d680 100644 --- a/packages/site/next-env.d.ts +++ b/packages/site/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/packages/site/next.config.js b/packages/site/next.config.js index 2246e025..2fbd2d0d 100755 --- a/packages/site/next.config.js +++ b/packages/site/next.config.js @@ -1,5 +1,3 @@ -const webpack = require('webpack'); - module.exports = { reactStrictMode: true, swcMinify: true, diff --git a/packages/site/package.json b/packages/site/package.json index 758392b9..f9f0c12d 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -33,7 +33,9 @@ "@jpmorganchase/mosaic-components": "^0.1.0-beta.89", "@jpmorganchase/mosaic-content-editor-plugin": "^0.1.0-beta.89", "@jpmorganchase/mosaic-layouts": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-loaders": "^0.1.0-beta.89", "@jpmorganchase/mosaic-site-components": "^0.1.0-beta.89", + "@jpmorganchase/mosaic-site-components-next": "^0.1.0-beta.89", "@jpmorganchase/mosaic-site-preset-styles": "^0.1.0-beta.89", "@jpmorganchase/mosaic-sitemap-component": "^0.1.0-beta.89", "@jpmorganchase/mosaic-source-git-repo": "^0.1.0-beta.89", @@ -41,21 +43,22 @@ "@jpmorganchase/mosaic-standard-generator": "^0.1.0-beta.89", "@jpmorganchase/mosaic-store": "^0.1.0-beta.89", "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0", - "@salt-ds/lab": "1.0.0-alpha.50", + "@salt-ds/core": "^1.37.1", + "@salt-ds/lab": "1.0.0-alpha.54", "@types/react": "^18.3.12", + "clsx": "^2.0.0", "next": "^14.0.0", "next-auth": "^4.24.5" }, "devDependencies": { "@next/eslint-plugin-next": "^14.0.0", "@playwright/test": "^1.33.0", - "@types/node": "^16.0.0", + "@types/node": "^18.0.0", "concurrently": "^7.1.0", "cross-env": "^7.0.3", "dotenv-load": "^2.0.1", "eslint-config-next": "^14.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/site/public/search-data.json b/packages/site/public/search-data.json index e08a93ae..72f9a557 100644 --- a/packages/site/public/search-data.json +++ b/packages/site/public/search-data.json @@ -1 +1 @@ -[{"title":"Mosaic","route":"/mosaic/index","content":["True to its name, Mosaic brings together several concepts—including content, design and technical infrastructure—to deliver a unified website experience that is truly greater than the sum of its individual parts.","With Mosaic, you can:","Don't move your content where it does not belong. ","Compose content from remote data sources which\n","are pulled at runtime by our content aggregator.","Visualize your content with your own theme, layouts and components or use the Mosaic Design\n","language.","Extend the existing code and add your own content source types through our simple plugin\n","architecture.","Publish your content through Server Side Rendering (SSR) or generate a snapshot of your content\n","and serve it as a Statically Generated Site (SGS).","Creating a website has never been so easy!"]},{"title":"Sitemap","route":"/mosaic/sitemap","content":["Sitemap"]},{"title":"Aliases","route":"/mosaic/author/aliases","content":["Aliases are virtual 'symlinks' of pages, allowing one page to take on one or more other routes.\n","This is not the same as copying the page, it is a primitive filesystem link which is resolved by Mosaic.","Use Cases","Aliases are great for linking to an old route if you've moved a page to a new place.\n","They can also be used to create short hand links to pages or to 'clone' a copy of a page into\n","another section of the site where it may be relevant.","Example","This is the frontmatter for this page:","---\n","title: Aliases\n","layout: DetailTechnical\n","aliases:\n"," - /mosaic/example/aliases\n","---","Try accessing this page from ","/mosaic/example/aliases"]},{"title":"Fragments","route":"/mosaic/author/fragments","content":["Fragments, also known as content fragments, are powerful tools that allow you to incorporate content from other pages into your documentation. ","By creating an MDX file and using the ","generic directives"," syntax ",", you can easily render the fragment in another file, providing modularity and reusability to your content.","Use Cases","Fragments offer various use cases, such as:","Consistent Content",": Use fragments to maintain consistent content across multiple pages. ","For instance, if you have a table or a tile that appears on multiple pages, you can create a fragment for it and include it in all relevant files. ","This ensures that any updates made to the fragment automatically reflect across the entire documentation.","Reusable Components",": Fragments enable the creation of reusable components or sections. ","You can define a complex or commonly used section once and then include it in multiple pages as needed. ","This approach saves time and effort, as you only need to update the fragment file to propagate changes throughout your documentation.","Modular Documentation",": With fragments, you can break down your documentation into smaller, manageable pieces. ","Each fragment represents a specific topic or section, allowing you to organize and structure your content more efficiently. ","This modular approach simplifies maintenance and makes it easier to navigate and update your documentation.","Usage","Firstly, enable the Fragment Plugin by adding the following to your plugins in ",".","{\n"," modulePath: '@jpmorganchase/mosaic-plugins/FragmentPlugin',\n"," options: {}\n","}","To include a fragment in your content, follow these steps:","Create an MDX file for the fragment you want to reuse. ","Remember to set the sidebar property of your fragment's frontmatter to exclude: true if you don't want the fragment to appear in the vertical navigation menu.","---\n","title: Fragment Title\n","sidebar:\n"," exclude: true\n","---","In the target file where you want to include the fragment, use the remark directive syntax ",".","Markdown Content Example","This is the contents of a fragment located at ",":","---\n","title: Content Fragment\n","sidebar:\n"," exclude: true\n","---\n","\n","#### Fragment Title\n","\n","This is an example fragment of markdown content, being pulled from `../fragments/content-fragment.mdx`.\n","The below code snippet will render the content from the content-fragment.mdx file in your target file:",":fragment{src=\"../fragments/content-fragment.mdx\"}","Example output:","Fragment Title","This is an example fragment of markdown content, being pulled from ",".","Component Example","Here is another example, where the fragment files each contain a "," component.",":fragment{src=\"../fragments/tile-a.mdx\"} :fragment{src=\"../fragments/tile-b.mdx\"}","The above code will render the content from tile-a.mdx and tile-b.mdx files, demonstrated below:"," "]},{"title":"Frontmatter","route":"/mosaic/author/frontmatter","content":["Frontmatter",", also known as page metadata, is a powerful feature that allows easy configuration of a page and Mosaic site components e.g. the sidebar.","Frontmatter is written in yaml syntax and is found at the top of a page between 2 sets of 3 dashes: ",".","Example page yaml","---\n","title: Page Title\n","layout: DetailTechnical\n","sidebar:\n"," priority: 4\n","---\n","\n","// frontmatter is closed and now comes page content\n","# Page Title\n","\n","This is some content.\n","Accessing Frontmatter in content","With the syntax below it is possible to directly reference frontmatter inside content using curly brackets and the "," object.","You can think of "," as a JSON object that holds all the frontmatter of a page and when the Mosaic "," encounters the curly brackets then the value in the frontmatter will be resolved.","{meta.title}\n","{meta.description}\n","{meta.someValueYouHaveAddedToTheFrontmatter}","This is very common to see Mosaic pages that reference the title as shown below:","---\n","title: Title\n","---\n","\n","# {meta.title}","Plugins & Frontmatter","Mosaic plugins can also embed their output into page frontmatter in 2 different ways:","a property is directly added to the page object","a JSON file is generated and referenced using a ","ref","Adding a property to the page","A plugin can add a property to a page simply by extending the page object it receives in the "," lifecycle event:","async function $afterSource(pages) {\n"," for (const page of pages) {\n"," page.newProperty = 'Hello'\n"," }\n"," return pages;\n","}","You could use this property in the page content using ","JSON File","Let's take a look at the ",".","The purpose of this plugin is to crawl the page hierarchy to find the closest "," found in any parent page's frontmatter.","Finds all index pages among the source docs","Deserialises those pages so it can read the frontmatter and content of the page","If a property called "," in the page frontmatter is found a new file named shared-config.json is created","Adds a ref named "," to the shared config file that points to the shared config of the index page","import type { Page, Plugin as PluginType } from '@jpmorganchase/mosaic-types';\n","import { flatten } from 'lodash-es';\n","import path from 'path';\n","\n","function createFileGlob(url, pageExtensions) {\n","if (pageExtensions.length === 1) {\n","return `${url}${pageExtensions[0]}`;\n","}\n","return `${url}{${pageExtensions.join(',')}}`;\n","}\n","\n","interface SharedConfigPluginPage extends Page {\n","sharedConfig?: string;\n","}\n","\n","interface SharedConfigPluginOptions {\n","filename: string;\n","}\n","\n","const SharedConfigPlugin: PluginType = {\n","async $beforeSend(\n"," mutableFilesystem,\n"," { config, serialiser, ignorePages, pageExtensions },\n"," options\n"," ) {\n"," const pagePaths = await mutableFilesystem.promises.glob(\n"," createFileGlob('**/index', pageExtensions),\n"," {\n"," ignore: [options.filename, ...flatten(ignorePages.map(ignore => [ignore, `**/${ignore}`]))],\n","cwd: '/'\n","}\n",");\n","\n"," for (const pagePath of pagePaths) {\n"," const sharedConfigFile = path.join(path.dirname(String(pagePath)), options.filename);\n","\n"," const page = await serialiser.deserialise(\n"," String(pagePath),\n"," await mutableFilesystem.promises.readFile(String(pagePath))\n"," );\n"," if (page.sharedConfig) {\n"," config.setRef(sharedConfigFile, ['config', '$ref'], `${String(pagePath)}#/sharedConfig`);\n"," await mutableFilesystem.promises.writeFile(sharedConfigFile, '{}');\n"," } else {\n"," const baseDir = path.posix.resolve(path.dirname(String(pagePath)), '..","/');\n"," config.setAliases(path.join(baseDir, options.filename), [sharedConfigFile]);\n"," }\n"," }\n","\n","}\n","};\n","\n","export default SharedConfigPlugin;\n"]},{"title":"Author","route":"/mosaic/author/index","content":["Here you will learn how to author documentation using markdown, Mosaic components and page templates"]},{"title":"Markdown Syntax","route":"/mosaic/author/markdown-syntax","content":["Out of the box, Mosaic supports documents written in ","MDX"," which allows React components to be embedded within the ","basic syntax"," outlined in the original Markdown design document.","In addition to the basic markdown syntax, the MDX processor used by Mosaic has been configured to support additional markdown syntax and features:","GitHub flavored markdown (gfm)","Frontmatter","Mosaic Standard Components","All components that comprise the "," export from "," package are available to use out of the box in your documents.","This includes components for all standard markdown syntax and additional components like "," and ",".","Referencing Frontmatter","Frontmatter values can be embedded into the page using the "," object. ","See ","frontmatter"," for more information.","Configuring Supported components","TODO"]},{"title":"Refs","route":"/mosaic/author/refs","content":["Refs are a very powerful feature of Mosaic documents that use ","JSON References"," to reference\n","a property from the page frontmatter or frontmatter of other pages.","The key concept is that of a JSON pointer which takes the form ","A","#","B"," where:","A"," is the relative path from the current schema to a target schema. ","If A is empty, the reference is to a type or property in the same schema, an in-schema reference. ","Otherwise, the reference is to a different schema, a cross-schema reference.","B"," is the complete path from the root of the schema to a type or property in the schema. ","If # in not included or B is empty, the reference is to an entire schema.","To translate this for our purposes:","A"," is the relative path to a file in the filesystem.","B"," is the path to a property in the frontmatter of the file.","It's probably better explained with examples...","Local refs (In-schema reference)","It is possible to reference a page's own frontmatter to avoid duplication:","---\n","title: Refs\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," sidebarPriority:\n"," $ref: '#/sidebar/priority'\n","---","This page you are reading right now has a sidebar priority of ",".","The value of "," comes from "," in the frontmatter.","Notice that because we don't specify a path before the "," in the ref we need to put the whole\n","value inside quotes.","Remote Refs (Cross-schema reference)","It is possible to reference frontmatter of other pages. ","Again this helps avoid duplication but the real power is using refs to build the data model. ","See ","advanced"," for more information.","The ","index"," page of the Author section has this frontmatter:","---\n","title: Author\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," exampleRefData: Hello from Author page\n","---","This page you are currently looking at is referencing the "," in it's frontmatter like this:","---\n","title: Refs\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," authorRef:\n"," $ref: ./#/data/exampleRefData\n","---","I can then use the data and embed it in this page like this:","{meta.data.authorRef}","And here it is: ","Notice that we did not need to put "," in the ref since index pages are resolved\n","automatically.","Advanced","You have had a taste of the power and want to know more? ","OK, lets reference and then list out all the mosaic modes:","The Modes ","index"," page has this frontmatter:","---\n","title: Modes of operation\n","layout: DetailTechnical\n","sidebar:\n"," priority: 4\n","data:\n"," modes:\n"," $ref: ./*#/title\n","---","The ref here is essentially using a wildcard (the *) to grab the "," property from the frontmatter of every page in the modes folder.","We can reference that data just like before:","---\n","title: Refs\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," modes:\n"," $ref: ../configure/modes#/data/modes\n","---","Output","With the code below, the referenced data can be embedded in a page.","
      \n"," {meta.data.modes.map(mode => (\n","
    • {mode}
    • \n"," ))}\n","
    ","Output with Mosaic Components","You can use Mosaic components with referenced data as well. ","Below we are using the "," and "," components.","\n"," {meta.data.modes.map(mode => (\n"," \n"," ))}\n","","Setting Refs using Plugins","Mosaic plugins can create new refs, create new ","global"," refs and see existing refs created by the page or other plugins. ","This is achieved using a special "," property available in the plugin helpers.","Create new refs","Use the "," function from the helpers provided to plugin lifecycle events. ","You need to provide","The file/fullpath to write the ref to","The path to the ptoperty where the ref will be applied","The value of the ref, which can use wildcards","For example, the following would add a property to pages named ",". ","The value is the title of all pages in the ","same"," directory as the current page"," async $afterSource(pages, { config }) {\n"," for (const page of pages) {\n"," config.setRef(page.fullPath, ['titles', '$ref'], `**#/title`);\n"," }\n"," return pages;\n"," }","When setting the property path the last string must be "," otherwise you're not creating a ref\n","that will be resolved by the RefPlugin.","Existing refs","To view refs that already exist you can use ",". ","For example to see all refs for a page:"," config.data.refs[fullPathToPage]","Create global refs","Global refs are similar to regular refs except they do not pre-resolve. ","This means they are resolved when the referenced file is read and the global mosaic filesystem, made up of multiple sources, is used rather than the filesystem of a single source."]},{"title":"Sidebar Configuration","route":"/mosaic/author/sidebars","content":["Sidebar data is generated by the the ","Sidebar Plugin"," which by default uses alphabetical ordering of page names to order the sidebar.","Sidebar frontmatter","A page can add a "," property to its ","frontmatter"," to change the ordering of a sidebar and what title is used for a page in the sidebar.","To rearrange pages in the sidebar or to apply a different label to a page you can specify the following in the page frontmatter:","Sidebar group label","A sidebar group when expanded will reveal the pages contained within the group.\n","Each group has a default page, ",", which is the default for the group and any breadcrumb link.","The"," can define a "," which defines the name of the sidebar group.\n","If no ","groupLabel"," is defined, sidebar group labels will be defined by either ","label"," or ","title",".","To specify the label of the default page, refer to [Sidebar label](.","/Sidebar label)","---\n","title: Sidebar Configuration\n","layout: DetailTechnical\n","sidebar:\n"," groupLabel: Group label\n","---","Sidebar label","By default the ","title"," of a page is used in the sidebar as the label but this can be changed to another label using page frontmatter.","---\n","title: Sidebar Configuration\n","layout: DetailTechnical\n","sidebar:\n"," label: A New Label\n","---","Sidebar priority","Sidebar priority is a number used to sort the sidebar. ","Higher the priority pages appear first in the sidebar ordering.","---\n","title: Sidebar Configuration\n","layout: DetailTechnical\n","sidebar:\n"," priority: 10\n","---","Sidebar Sort Configuration","Sidebar sort configuration allows the sidebar to be sorted using a more sophisticated approach and only needs to be applied to the "," page of the directory you want to sort.","Priority takes precedence over sort configuration so it can be used to override the sort\n","configuration if required.","You must add the sidebar sort configuration to the "," property of an ","index"," page e.g.,","sharedConfig:\n"," sidebar:\n"," sort:\n"," field: data/title\n"," dataType: string\n"," arrange: desc","The properties of the sort configuration are described in the table below:","| Property | Description | Required |\n","| -------- | ------------------------------------------------------------------------------------------ | -------- |\n","| field | the path, separated by ",", used to find the value in page frontmatter you wish to sort by | Yes |\n","| dataType | is the type of the value. ","Can be a "," or "," or ",". ","| Yes |\n","| arrange | "," or "," order | yes |","Newsletters Example","Let's say you have a ","Newsletters"," directory and each page in the directory represents a newsletter. ","You wish to sort the newsletter sidebar by publication date in descending order (newest first).","One way to do this is to edit each page and add a "," which is manually incremented every time a news newsletter is added. ","Alternatively you can add the following sort configuration to the newsletters index page:","sharedConfig:\n"," sidebar:\n"," sort:\n"," field: data/publicationDate\n"," dataType: date\n"," arrange: desc","This will use the "," property in each newsletter to sort the newsletters in the sidebar. ","The publication date is converted to a "," by the Sidebar Plugin to ensure accurate ordering. ","There is now no need to increment a priority when a new newsletter is added.","Example newsletter page frontmatter:","---\n","title: Newsletter 01 Jan 2023\n","description: Newsletter 01 Jan 2023\n","data:\n"," title:\n"," $ref: '#/title'\n"," link: /newsletters/2023-01-01\n"," publicationDate: '2023-01-01'\n","---"]},{"title":"Tags","route":"/mosaic/author/tags","content":["Tags are very similar to ","Refs"," with one very important distinction: Tags work ","across multiple sources",".","In Mosaic, each source has it's own filesystem which are then merged together to form a union of all source filesystems. ","It is in this union filesystem that tags are applied and not to the individual source filesystem that the tag was defined on.","Tags are slower to apply than refs. ","Multiple sources need to run and update before tagged data\n","will be resolved. ","If possible, stick to refs and only use tags when dealing with multiple sources.","Tagging a page","To tag a page, add a "," property to the page frontmatter. ","For example, the Product A page is tagged with \"in-stock\":","---\n","title: Product A\n","description: Mosaic Product A\n","layout: ProductDiscover\n","tags:\n"," - in-stock\n","data:\n"," name:\n"," $ref: '#/title'\n","---"," is always an array","Subscribing to a tag","To subscribe to a tag, use the "," property. ","For example, the Products page has subscribed to the "," property of pages tagged with ",".","---\n","title: Products\n","data:\n"," in-stock:\n"," $tag: in-stock#/data\n","---","A "," can provide a path to a specific piece of metadata on tagged pages, just like a ref.","Example","This page has subscribed to "," and "," tags and is displaying them using 2 Mosaic "," components.","Both the Product A and Product B pages are part of a different source than this page.","In Stock","Out of Stock"]},{"title":"UI Components","route":"/mosaic/author/ui-components","content":[""]},{"title":"Configure","route":"/mosaic/configure/index","content":["Mosaic is a tool which retrieves, formats and combines documentation pages from any number of different external sources (such as GitHub repositories, local disks or REST endpoints), into a single filesystem for you to use in your websites."]},{"title":"Content Fragment","route":"/mosaic/fragments/content-fragment","content":["Fragment Title","This is an example fragment of markdown content, being pulled from ","."]},{"title":"Fragments","route":"/mosaic/fragments/index","content":["This folder contains example fragments that are referenced and rendered in the Fragments docs page."]},{"title":"Tile A","route":"/mosaic/fragments/tile-a","content":[]},{"title":"Tile B","route":"/mosaic/fragments/tile-b","content":[]},{"title":"Create a Site","route":"/mosaic/getting-started/create-a-site","content":["In this guide you will learn how to generate and serve a Mosaic site.","Prerequisites","To begin setting up a Mosaic site, you need to have the following software installed:","Yarn v1","Node.js v18 or higher","Step 1: Generate a Mosaic site","Run the following command in your project directory to generate a new Mosaic site:","npx @jpmorganchase/mosaic-create-site create -o my-sample-site","This command creates a new Mosaic site in the my-sample-site directory.","Next, navigate to the site directory:","cd my-sample-site","Step 2: Serve the site","The example site you have generated comes preconfigured with two ","sources",": a remote repository and a local docs folder. ","Sources are used by Mosaic to pull content from disparate locations and merge them into a single virtual filesystem that can be used by a Mosaic site.","Set up Git credentials","If you want the site to read from remote repositories, you need to set up an environment variable to store your Git credentials. ","Follow these steps:","Open a terminal or command prompt.","Replace "," and "," in the following commands with your actual Git username and personal access token.","On Unix:","export MOSAIC_DOCS_CLONE_CREDENTIALS=\":\"","On Windows:","set MOSAIC_DOCS_CLONE_CREDENTIALS=\":\"","This sets the MOSAIC_DOCS_CLONE_CREDENTIALS environment variable with your Git credentials.","Serve command","Now you can serve your Mosaic site by running the following command:","yarn serve","Access your Mosaic site from a browser using the following URLs:","To browse the content from your local source: http://localhost:3000/local","To browse the content from the Mosaic Git repo source: http://localhost:3000/mosaic","That's it! ","Your Mosaic site is now up and running.","Next Steps:","Deploy your Mosaic site to AWS or Vercel for production use.","Create more pages to expand your site's content.","Configure your own sources in the mosaic.config.mjs file to pull content from different locations.","Theme your site"]},{"title":"Getting Started","route":"/mosaic/getting-started/index","content":["Getting Started with Mosaic","Follow our step-by-step guides to quickly create and deploy your first Mosaic site."]},{"title":"Publish a site to AWS","route":"/mosaic/getting-started/publish-site-to-aws","content":["Publish a site to AWS using S3 snapshots.","Step 1: Generate a Mosaic site","If you have already created your Mosaic site, skip ahead to step 2.","> npx @jpmorganchase/mosaic-create-site -o my-sample-site\n","> cd my-sample-site","Step 2: Create a Github repository","> git init\n","> git remote add origin git@github.com:username/my-sample-site.git\n","> git add .\n","> git commit -m \"initial commit\"\n","> git push origin main","Step 3: Generate a snapshot of content","Consider a snapshot as a directory of static content previously pulled from your content sources, which does not update itself.","> yarn gen:snapshot","Step 4: Configure environment for S3","> export MOSAIC_MODE=\"snapshot-s3\"\n","> export MOSAIC_S3_BUCKET=\"\"\n","> export MOSAIC_S3_REGION=\"\"\n","> export MOSAIC_S3_ACCESS_KEY_ID=\"\"\"\n","> export MOSAIC_S3_SECRET_ACCESS_KEY=\"\"\n","> yarn mosaic upload -S ./snapshots/latest","Step 5: Setup AWS","Switch to the ","AWS Amplify"," console and deploy your app as a SSR application by following the ","AWS docs",".","Setup an S3 bucket as per the ","AWS S3 docs",".","Step 7: Configure your AWS app","Add the environment vars to the hosted app via your console","MOSAIC_MODE=\"snapshot-s3\"\n","MOSAIC_S3_BUCKET=\"\"\n","MOSAIC_S3_REGION=\"\"\n","MOSAIC_S3_ACCESS_KEY_ID=\"\"\"\n","MOSAIC_S3_SECRET_ACCESS_KEY=\"\"","Add the following build settings","version: 1\n","frontend:\n"," phases:\n"," preBuild:\n"," commands:\n"," - yarn install\n"," - env | grep -e MOSAIC >> .env.production\n"," build:\n"," commands:\n"," - yarn run build\n"," artifacts:\n"," baseDirectory: .next\n"," files:\n"," - '**/*'\n"," cache:\n"," paths:\n"," - node_modules/**/*","Ensure the Node is set to 16","Step 8: Upload your snapshot","Upload your snapshot to S3 storage.","> yarn mosaic upload -S ./snapshots/latest"]},{"title":"Publish","route":"/mosaic/publish/index","content":["To create your first Mosaic site, we have created a command line generator that scaffolds a ","standard"," site.","A ","standard"," site offers","an out the box, working site, which showcases local and remote content sources","a minimal set of files that can be configured with your own components, themes, layouts, sources and plugins","an update path that enables you to update Mosaic, independently of your own configuration","Create your first site","Install the Mosaic create site script.","> yarn global add @jpmorganchase/mosaic-create-site","Create a directory for your site and run the "," script.","> mkdir mosaic-sample-site\n","> cd mosaic-sample-site\n","> mosaic-create-site -f .","Define the environment variable, which enables us to access your remote repo.","> export MOSAIC_DOCS_CLONE_CREDENTIALS=\"\"","The "," environment variable is composed of your git username and your PAT token.\n","Follow these ","docs"," to see how to create your own PAT token.","Your site is ready to run.","> yarn serve","In your browser load ","Congratulations, you have created your first Mosaic site."]},{"title":"Publish a site to AWS","route":"/mosaic/publish/publish-site-to-aws","content":["A Mosaic site is a ","Next.Js"," app.","To publish a Next.Js App to AWS, deploy your app as a SSR application by following the ","AWS docs",".","Once the basic app has been configured, add the Mosaic specifics.","Add the environment vars to the hosted app via the Amplify console","MOSAIC_MODE=\"snapshot-s3\"\n","MOSAIC_S3_BUCKET=\"\"\n","MOSAIC_S3_REGION=\"\"\n","MOSAIC_S3_ACCESS_KEY_ID=\"\"\"\n","MOSAIC_S3_SECRET_ACCESS_KEY=\"\"","Add the following build settings","version: 1\n","frontend:\n"," phases:\n"," preBuild:\n"," commands:\n"," - yarn install\n"," - env | grep -e MOSAIC >> .env.production\n"," build:\n"," commands:\n"," - yarn run build\n"," artifacts:\n"," baseDirectory: .next\n"," files:\n"," - '**/*'\n"," cache:\n"," paths:\n"," - node_modules/**/*","Ensure the Node is set to 16"]},{"title":"Publish a site to Vercel","route":"/mosaic/publish/publish-site-to-vercel","content":["A Mosaic site is a ","Next.Js"," app.","To publish a Next.Js App to Vercel, refer to the ","Vercel docs",".","Deployment","As the ","vercel platform"," hosts static content you will need to deploy a mosaic snapshot. ","There is no option to run mosaic in ","active mode",".","1. ","Update Config File","Add the following to the mosaic config file used by your site:"," deployment: { mode: 'snapshot-file', platform: 'vercel' }","2. ","Set Environment Variables","Set 2 ","environment variables"," in the vercel dashboard.","| Variable Name | Value |\n","| ------------------- | ----------------- |\n","| MOSAIC_MODE | snapshot-file |\n","| MOSAIC_SNAPSHOT_DIR | snapshots/latest. ","|","3. ","Run Build and Deploy","The "," command used by vercel must run "," followed by ","The "," command is needed to workaround an ","output file tracing"," problem.","Example:","yarn build && yarn deploy","Output File Tracing","Output File Tracing"," is a feature of Next.js that uses static analysis\n","to determine what files are needed to deploy a production version of an application.","Due to the architecture of mosaic, snapshot files can be ignored by this process and therefore excluded from the build artifacts deployed by vercel.","If you are deploying your site to the ","vercel platform"," then the mosaic site has a "," command that will update the nextjs output trace to include the snapshot files."]},{"title":"Test","route":"/mosaic/test/index","content":["Pages for e2e testing."]},{"title":"Admin","route":"/mosaic/configure/admin/index","content":["There are several admin urls exposed by Mosaic that provide an insight into how the filesystem has been configured and a way to remotely manage sources.","Endpoints","| Endpoint | Method | Description | Params |\n","| -------------------------- | ------ | -------------------------------------------- | ---------------------- |\n","| "," | GET | Returns the JSON from the Mosaic config file | n/a |\n","| "," | GET | Returns the entire mosaic filesystem as JSON | n/a |\n","| "," | GET | Returns a collection of active sources | n/a |\n","| "," | POST | Adds the source | definition & isPreview |\n","| "," | PUT | Stops the source with the provided name | name |\n","| "," | PUT | Restarts the source with the provided name | name |"]},{"title":"Detail Highlight","route":"/mosaic/configure/layouts/detail-highlight","content":["Layout: Detail Highlight","Initialize with "," in your page's frontmatter.","This layout is used to promote, share insights, and statistics for a line of business.","This layout should be used for pages with only one level of nesting, therefore, pagination is not eligible for this\n","layout.","Page geometry"]},{"title":"Detail Overview","route":"/mosaic/configure/layouts/detail-overview","content":["Layout: Detail Overview","Initialize with "," in your page's frontmatter.","This layout is used to present an overview of expected documentation, statisitics, and ability to\n","navigate to more documents.","Avoid making this page too long. ","If it gets too long, we recommend the ","Detail Technical"," template.","Page geometry","Other Layouts","Detail Highlight","Detail Technical","Landing","Product Discover","Product Preview","Filler content","Eiusmod veniam adipisicing est magna id sunt occaecat minim adipisicing ad do pariatur id aliqua.\n","Officia officia deserunt consequat ullamco irure. ","Excepteur deserunt esse occaecat ex aute. ","Duis do\n","do in incididunt cupidatat dolore veniam magna aliquip voluptate laborum. ","Non irure magna amet\n","ullamco culpa esse dolore nostrud. ","Id ea id ipsum incididunt do velit aliquip fugiat do non\n","consequat.","A sub heading","Deserunt sunt pariatur mollit dolor eiusmod. ","Anim sunt officia cillum anim. ","Laborum ullamco\n","consectetur elit dolore quis laborum. ","Eiusmod cillum amet veniam sunt Lorem reprehenderit commodo.\n","Cupidatat cillum ea consequat anim. ","Duis voluptate nulla veniam labore quis tempor.","Commodo reprehenderit excepteur amet aliquip cillum veniam ad. ","Ullamco proident deserunt laboris\n","duis laborum consequat laboris est eu enim nulla. ","Mollit velit consectetur ea aliqua consectetur\n","mollit eu ex deserunt. ","Aute excepteur exercitation esse proident excepteur Lorem. ","Quis cillum\n","occaecat sint voluptate incididunt ea ipsum incididunt duis sint magna magna fugiat.","Third-level heading","Ea do magna aute proident nulla cupidatat esse consectetur anim eu esse. ","Consectetur est voluptate\n","excepteur non dolore consequat fugiat deserunt. ","Est nostrud est ea irure reprehenderit commodo\n","nostrud nulla tempor ipsum tempor sit id exercitation. ","Sunt reprehenderit officia anim id quis\n","pariatur velit cillum incididunt officia sunt. ","Ullamco ipsum cillum minim deserunt eiusmod nostrud\n","irure et nulla laborum ipsum ipsum incididunt. ","Voluptate reprehenderit in occaecat ipsum nulla\n","excepteur excepteur mollit laboris id ad laborum do. ","Qui in laborum nostrud quis occaecat proident\n","ipsum tempor laborum consequat id ut velit occaecat.Aliquip quis qui ullamco ipsum exercitation\n","exercitation excepteur ea ex. ","Proident elit incididunt incididunt ad adipisicing quis deserunt sint\n","laboris deserunt ipsum culpa est. ","Id do ex duis Lorem exercitation amet reprehenderit. ","Voluptate qui\n","tempor qui sit minim sit qui ea id dolor excepteur. ","Laborum elit excepteur enim sunt consequat\n","officia cillum. ","Do ea occaecat ut voluptate ea proident duis minim ad pariatur dolore magna enim\n","duis. ","Sit aliqua aliqua ea mollit enim cupidatat proident incididunt. ","Eu dolore sit non incididunt.\n","Mollit reprehenderit sunt sunt cillum labore velit exercitation officia aliqua ea adipisicing do ea.\n","Commodo et fugiat velit dolore consectetur.","Amet dolore deserunt in ut amet officia exercitation sint excepteur voluptate proident tempor enim\n","est. ","Culpa proident tempor in voluptate laboris sunt consectetur sit cillum excepteur culpa enim\n","velit laboris. ","Pariatur elit amet nostrud tempor nostrud ea. ","Exercitation do aliquip nisi amet id.\n","Lorem labore incididunt sit sit veniam tempor do consectetur do culpa qui.","Id sint deserunt laborum mollit id excepteur","mollit excepteur labore labore dolor. ","Sit cupidatat nostrud ad consequat amet excepteur id sunt\n","labore adipisicing non irure. ","Fugiat exercitation laborum officia minim duis dolor do officia Lorem\n","cillum excepteur. ","Sint elit mollit duis sit ad commodo.","Cillum amet irure ut tempor tempor culpa dolore sint.","Lorem qui ipsum reprehenderit est incididunt duis exercitation ea duis fugiat. ","Consectetur enim id\n","sunt exercitation et dolore ea proident sunt excepteur fugiat dolor. ","Veniam proident dolore irure\n","incididunt deserunt pariatur quis. ","Incididunt ea elit deserunt occaecat eiusmod velit fugiat eiusmod\n","dolor eiusmod ullamco. ","Fugiat fugiat eiusmod occaecat nulla consequat pariatur.","Aliquip non cupidatat irure magna et fugiat sunt amet ex est excepteur irure quis. ","Non culpa magna\n","nisi enim eu nulla esse laborum amet ipsum. ","Eu consectetur labore do id occaecat adipisicing."]},{"title":"Detail Technical","route":"/mosaic/configure/layouts/detail-technical","content":["Layout: Detail Technical","Initialize with "," in your page's frontmatter.","This layout is used for longer, technical, detailed content about the product.","This layout shows less marketing-type visuals and more visual of diagrams, screenshots, and code\n","snippets.","This page can be short or very lengthy.","Page geometry","Other Layouts","Detail Highlight","Detail Overview","Landing","Product Discover","Product Preview","Filler content","Eiusmod veniam adipisicing est magna id sunt occaecat minim adipisicing ad do pariatur id aliqua.\n","Officia officia deserunt consequat ullamco irure. ","Excepteur deserunt esse occaecat ex aute. ","Duis do\n","do in incididunt cupidatat dolore veniam magna aliquip voluptate laborum. ","Non irure magna amet\n","ullamco culpa esse dolore nostrud. ","Id ea id ipsum incididunt do velit aliquip fugiat do non\n","consequat.","A sub heading","Deserunt sunt pariatur mollit dolor eiusmod. ","Anim sunt officia cillum anim. ","Laborum ullamco\n","consectetur elit dolore quis laborum. ","Eiusmod cillum amet veniam sunt Lorem reprehenderit commodo.\n","Cupidatat cillum ea consequat anim. ","Duis voluptate nulla veniam labore quis tempor.","Commodo reprehenderit excepteur amet aliquip cillum veniam ad. ","Ullamco proident deserunt laboris\n","duis laborum consequat laboris est eu enim nulla. ","Mollit velit consectetur ea aliqua consectetur\n","mollit eu ex deserunt. ","Aute excepteur exercitation esse proident excepteur Lorem. ","Quis cillum\n","occaecat sint voluptate incididunt ea ipsum incididunt duis sint magna magna fugiat.","Third-level heading","Ea do magna aute proident nulla cupidatat esse consectetur anim eu esse. ","Consectetur est voluptate\n","excepteur non dolore consequat fugiat deserunt. ","Est nostrud est ea irure reprehenderit commodo\n","nostrud nulla tempor ipsum tempor sit id exercitation. ","Sunt reprehenderit officia anim id quis\n","pariatur velit cillum incididunt officia sunt. ","Ullamco ipsum cillum minim deserunt eiusmod nostrud\n","irure et nulla laborum ipsum ipsum incididunt. ","Voluptate reprehenderit in occaecat ipsum nulla\n","excepteur excepteur mollit laboris id ad laborum do. ","Qui in laborum nostrud quis occaecat proident\n","ipsum tempor laborum consequat id ut velit occaecat.Aliquip quis qui ullamco ipsum exercitation\n","exercitation excepteur ea ex. ","Proident elit incididunt incididunt ad adipisicing quis deserunt sint\n","laboris deserunt ipsum culpa est. ","Id do ex duis Lorem exercitation amet reprehenderit. ","Voluptate qui\n","tempor qui sit minim sit qui ea id dolor excepteur. ","Laborum elit excepteur enim sunt consequat\n","officia cillum. ","Do ea occaecat ut voluptate ea proident duis minim ad pariatur dolore magna enim\n","duis. ","Sit aliqua aliqua ea mollit enim cupidatat proident incididunt. ","Eu dolore sit non incididunt.\n","Mollit reprehenderit sunt sunt cillum labore velit exercitation officia aliqua ea adipisicing do ea.\n","Commodo et fugiat velit dolore consectetur.","Amet dolore deserunt in ut amet officia exercitation sint excepteur voluptate proident tempor enim\n","est. ","Culpa proident tempor in voluptate laboris sunt consectetur sit cillum excepteur culpa enim\n","velit laboris. ","Pariatur elit amet nostrud tempor nostrud ea. ","Exercitation do aliquip nisi amet id.\n","Lorem labore incididunt sit sit veniam tempor do consectetur do culpa qui.","Id sint deserunt laborum mollit id excepteur","mollit excepteur labore labore dolor. ","Sit cupidatat nostrud ad consequat amet excepteur id sunt\n","labore adipisicing non irure. ","Fugiat exercitation laborum officia minim duis dolor do officia Lorem\n","cillum excepteur. ","Sint elit mollit duis sit ad commodo.","Cillum amet irure ut tempor tempor culpa dolore sint.","Lorem qui ipsum reprehenderit est incididunt duis exercitation ea duis fugiat. ","Consectetur enim id\n","sunt exercitation et dolore ea proident sunt excepteur fugiat dolor. ","Veniam proident dolore irure\n","incididunt deserunt pariatur quis. ","Incididunt ea elit deserunt occaecat eiusmod velit fugiat eiusmod\n","dolor eiusmod ullamco. ","Fugiat fugiat eiusmod occaecat nulla consequat pariatur.","Aliquip non cupidatat irure magna et fugiat sunt amet ex est excepteur irure quis. ","Non culpa magna\n","nisi enim eu nulla esse laborum amet ipsum. ","Eu consectetur labore do id occaecat adipisicing."]},{"title":"Layouts","route":"/mosaic/configure/layouts/index","content":["Todo..."]},{"title":"Landing Layout","route":"/mosaic/configure/layouts/landing","content":["Layout: Landing","Initialize with "," in your page's frontmatter.","Use this layout as a landing page to show large branded visuals, and high-level content about the\n","line of business.","Set the tone and voice for your LOB’s experience.","Use components that support your content with places to insert visuals, share any stats or an\n","introduction of LOB’s story.","Use heading styles to show the grouping of content of a section.","Page geometry"]},{"title":"Product Discover Layout","route":"/mosaic/configure/layouts/product-discover","content":["Layout: Product Discover","Initialize with "," in your page's frontmatter.","Use this layout to introduce a product feature with a description and large visual.","This layout is use for marketing and discovery content.","Page geometry"]},{"title":"Product Preview Layout","route":"/mosaic/configure/layouts/product-preview","content":["Layout: Product Preview","Initialize with "," in your page's frontmatter.","This layout has been used to introduce a product and showcase their suite of product offerings.","Use heading styles to show the grouping of content of a section.","Page geometry"]},{"title":"Active mode","route":"/mosaic/configure/modes/active","content":["In "," mode content can be ","pulled"," from heterogeneous data sources and normalized via plugins, to the configured components/theme.\n","As your content changes, the site will ","re-pull"," the content and update your site in real-time.","The standard generated site comes with 2 sources to demonstrate, how 'active' mode work.","a local source, which loads content from ","a remote source, which loads content from a ","sample Github repository","Configuring your content sources","All content is composed together within a ","namespace",".","A ","namespace"," is the scope for aggregated content, represented by the root path.\n","e.g Our sample docs are aggregated into a ","namespace"," called "," and served by the user journey ","Mosaic doc sources are defined by a file called ","Here is how that might look for a standard site.","Pull your local content","To tryout local content creation, add a file called","The load ","Each directory should contain an "," which is the default page, when a page is loaded without a path","Now create other pages and subdirectories and explore how Mosaic, builds your user-journeys from your fileset.","Pull your remote content","To tryout remote content creation, your standard site comes pre-configured to load our Mosaic sample docs.","Edit the "," and change your "," and "," to pull content from other repos."]},{"title":"Modes of operation","route":"/mosaic/configure/modes/index","content":["Mosaic can operate in 3 different modes","Active updates","In ","active"," mode, content updates in real-time.","active"," mode content is pulled at configured intervals in real-time, as defined by ",".","Read the ","active"," configuration docs.","Static content","Consider a snapshot as a directory of static content previously pulled from your content sources, which does not update itself.","In a snapshot mode, the snapshot does update itself.","File based snapshots","In "," mode, immutable snapshots of content are loaded at startup from the local file-system.","Read the ","snapshot-file"," configuration docs.","S3 based snapshots","In "," mode, snapshots of content are loaded at startup from a remote S3 bucket.","Read the ","snapshot-s3"," configuration docs."]},{"title":"Snapshot file mode","route":"/mosaic/configure/modes/snapshot-file","content":["In "," mode a local immutable snapshot can be loaded by the site. ","Typically, the snapshot and the site are\n","deployed together and upon startup the site can load the snapshot from the local file-system.","To use "," mode","export MOSAIC_MODE=\"snapshot-file\"\n","export MOSAIC_SNAPSHOT_DIR=\"./snapshot/latest\"","Generating a snapshot","To generate a snapshot, run","yarn gen:snapshot","Commit the snapshot to your Git repo and push the site+snapshot to your Git repo, within the same branch."]},{"title":"Snapshot AWS/S3 mode","route":"/mosaic/configure/modes/snapshot-s3","content":["In "," mode a snapshot can be loaded from a pre-configured AWS S3 bucket.","To use "," mode","> export MOSAIC_MODE=\"snapshot-s3\"\n","> MOSAIC_S3_BUCKET=\"\"\n","> MOSAIC_S3_REGION=\"\"\n","> MOSAIC_S3_ACCESS_KEY_ID=\"\"\n","> MOSAIC_S3_SECRET_ACCESS_KEY=\"\"","Generating a snapshot","To generate a snapshot, run","yarn gen:snapshot","Uploading a snapshot to S3","To upload a snapshot to S3, define the required environment variables and run","yarn mosaic upload -S "]},{"title":"$AliasPlugin","route":"/mosaic/configure/plugins/alias-plugin","content":["The "," is what powers the ","aliases"," feature of Mosaic.","It does this by scrapes "," from page metadata and also applies all aliases stored in ","Other plugins can use "," to apply new aliases, as long as they call it before this plugin has reaches the "," lifecycle event.","This plugin is added to the plugins collection by Mosaic itself so users do ","not"," need to include it in their own mosaic config file.","Priority","This plugin runs with a priority of -1 so it runs ","after"," most other plugins."]},{"title":"BreadcrumbsPlugin","route":"/mosaic/configure/plugins/breadcrumbs-plugin","content":["The "," is responsible for generating the data needed to show breadcrumbs navigation on pages. ","It then appends this data to a "," property in the page metadata.","Should a page already have a "," property in it's metadata then it is respected and not overwritten.","If a page has a "," property in it's metadata then this is used as the label for the breadcrumb, otherwise the page "," is used.","When the "," is traversing up directories, it needs to know what page in the directory represents the ","breadcrumb"," for that directory. ","It is recommended to use the "," page for this but the page to use is a configurable option of the breadcrumbs plugin.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| ------------- | ------------------------------------------------ |\n","| indexPageName | The page name to use for \"directory\" breadcrumbs |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the breadcrumbs plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/BreadcrumbsPlugin',\n"," options: {\n"," indexPageName: 'index.mdx'\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"BrokenLinksPlugin","route":"/mosaic/configure/plugins/broken-links-plugin","content":["The "," will identify any broken links in pages Mosaic has pulled into it's filesystem by making use of the ","check-links"," package.","It can identify broken links between the pages themselves and external links that pages link out to.","What this plugin is really checking is \"liveness\" of a link.","alive if the URL is reachable (2XX status code)","dead if the URL is not reachable","invalid if the URL was parsed as invalid or used an unsupported protocol","Links may be \"alive\", but the ","content"," of the linked page may not be what you want so continue\n","to check links show what you expect.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| ------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n","| baseUrl | This is used to calculate the full url for links between pages. ","It should be the url Mosaic is running on |\n","| proxyEndpoint | If you are behind a corporate proxy, external link checking will not work unless you specify the proxy endpoint using this option |","Adding to Mosaic","This plugin is ","not"," included in the mosaic config shipped by the Mosaic standard generator so it must be added manually to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/BrokenLinksPlugin',\n"," priority: -1,\n"," // Exclude this plugin in builds\n"," runTimeOnly: true,\n"," options: {\n"," baseUrl: process.env.MOSAIC_ACTIVE_MODE_URL || 'http://localhost:8080',\n"," proxyEndpoint: 'http://some-proxy-url'\n"," }\n"," }\n"," // other plugins\n","];","This plugin needs to be a "," plugin because it needs Mosaic to be running in order to\n","make requests to all of the page links.","Example Output","When a link is found to be broken, you will see the following output in the console:","@jpmorganchase/mosaic-site:serve: 8080 [Mosaic] Broken links found in /local/docs/publish-site-to-vercel.mdx\n","@jpmorganchase/mosaic-site:serve: 8080 Link to https://nextjs.org/davie is dead {\n","@jpmorganchase/mosaic-site:serve: 8080 type: 'link',\n","@jpmorganchase/mosaic-site:serve: 8080 title: null,\n","@jpmorganchase/mosaic-site:serve: 8080 url: 'https://nextjs.org/davie',\n","@jpmorganchase/mosaic-site:serve: 8080 children: [ { type: 'text', value: 'Next.Js', position: [Object] } ],\n","@jpmorganchase/mosaic-site:serve: 8080 position: {\n","@jpmorganchase/mosaic-site:serve: 8080 start: { line: 4, column: 20, offset: 36 },\n","@jpmorganchase/mosaic-site:serve: 8080 end: { line: 4, column: 55, offset: 71 }\n","@jpmorganchase/mosaic-site:serve: 8080 }\n","@jpmorganchase/mosaic-site:serve: 8080 }"]},{"title":"$CodeModPlugin","route":"/mosaic/configure/plugins/codemod-plugin","content":["Todo"]},{"title":"Plugins","route":"/mosaic/configure/plugins/index","content":["Mosaic Plugins are ","lifecycle-based"," hooks that are called on ","every"," source at different stages. ","You will never need to invoke a lifecycle method directly as their execution is managed by a plugin runner.","Plugins enable Mosaic to have a lightweight and flexible, modular architecture by encapsulating features and functionality as plugins.","Installation","Configuration","Plugins are added to the "," collection of the mosaic config file. ","Like ","sources",", plugins have an options property that can be used to provide plugin specific configuration.","| Property | Description | Required |\n","| --------------- | -------------------------------------------------------------------------- | -------- |\n","| modulePath | The path to the installed plugin module | Yes |\n","| disabled | Exclude this plugin completely. ","Defaults to false | No |\n","| runtimeOnly | Exclude this plugin when generating a snapshot. ","Defaults to false | No |\n","| previewDisabled | Exclude this plugin for \"preview\" sources | No |\n","| allowMultiple | Allow multiple instances of this plugin to run. ","| No |\n","| priority | The importance of this plugin. ","Plugins with the highest priority run first | No |\n","| options | Collection of other configuration values | No |"," plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/PagesWithoutFileExtPlugin',\n"," options: {},\n"," priority: 1\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: {}\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/ReadingTimePlugin',\n"," options: {}\n"," }\n","],","There is no need to import the plugin module directly. ","As long as the plugin is installed, Mosaic\n","will be able to import it using the built-in plugin loader.","Default Plugins","The following plugins are always included by Mosaic, regardless of whether they are present in the plugins collection of the Mosaic config file:","$TagPlugin","$AliasPlugin","$CodeModPlugin","$RefPlugin","Plugin errors","Should a plugin fail, the failure will ","not"," cause a source to close or for any other plugin to not run.","Instead plugin errors are tracked by Mosaic and can be viewed in the "," property available on each source listed by the list sources ","admin API",".","Plugin errors will be split by lifecycle event and only the lifecycle events used by the loaded plugins used will be shown.","Multiple Instances","By default, Mosaic will only run one instance of a plugin.","It may be the case that you wish to run the same plugin twice with a slightly different config. ","To do this, you must have 2 instances listed in the plugins collection and they ","both"," must set the "," option to ",".","For example, the config below runs the ","SidebarPlugin"," twice:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: { rootDirGlob: 'products/product-a' },\n"," allowMultiple: true\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: { rootDirGlob: '*/!(","product-a)/*' },\n"," allowMultiple: true\n"," }\n"," // other plugins\n","];"]},{"title":"LazyPagePlugin","route":"/mosaic/configure/plugins/lazy-page-plugin","content":["The "," attempts to reduce the size of the Mosaic filesystem in memory by moving page metadata and content to disk.","It then adds a hook, so that when a page is requested the data is loaded from disk and combined with what is already in the Mosaic filesystem.","It must be the very last to run so that it can strip off metadata and content after other plugins\n","have finished with them.","Priority","This plugin runs with a priority of -2. ","Needs to be the last to run for biggest impact.","Options","| Property | Description |\n","| -------- | ------------------------------------------------------------------------------ |\n","| cacheDir | The directory to store the cache. ","Defaults to "," |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/LazyPagePlugin',\n"," // This plugin must be the very last to run, so it can strip off metadata and content after the other\n"," // plugins are done with them\n"," priority: -2,\n"," // Exclude this plugin in builds\n"," runTimeOnly: true,\n"," options: {\n"," cacheDir: '.tmp/.pull-docs-last-page-plugin-cache'\n"," }\n"," }\n"," // other plugins\n","];","This plugin needs to be a "," plugin because the goal is to reduce the in-memory\n","filesystem size."]},{"title":"PagesWithoutFileExtPlugin","route":"/mosaic/configure/plugins/pages-wthout-extensions-plugin","content":["The "," plugin creates ","aliases"," without the file extension for every page in the Mosaic filesystem.\n","This allows pages to be retrieved from the filesystem without specifying the extension e.g., ",".","The plugin also modifies the "," metadata property of a page to point to the shorter alias.","Priority","This plugin runs with a priority of 1. ","It must run after the ","$AliasPlugin",".","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/PagesWithoutFileExtPlugin',\n"," options: {},\n"," priority: 1\n"," }\n"," // other plugins\n","];"]},{"title":"PublicAssetsPlugin","route":"/mosaic/configure/plugins/public-assets-plugin","content":["The "," is responsible for finding \"assets\" in the Mosaic filesystem and copying them to another directory.","Typical usecase is for copying "," and "," to the public directory of a Next.js site as these are considered ","static assets"," for Next.js.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| --------- | ----------------------------------------------------------- |\n","| outputDir | The directory to copy the assets to. ","Defaults to "," |\n","| assets | A collection of filenames to copy to the outputDir |","Adding to Mosaic","This plugin is ","not"," included in the mosaic config shipped by the Mosaic standard generator so it must be added manually to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/PublicAssetsPlugin',\n"," priority: -1,\n"," options: {\n"," outputDir: './public',\n"," assets: ['sitemap.xml', 'search-data.json']\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"ReadingTimePlugin","route":"/mosaic/configure/plugins/reading-time-plugin","content":["The "," generates an estimation of how long a page written in MDX will take to read and adds the "," property to the metadata of a page.","Priority","This plugin runs with no special priority.","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/ReadingTimePlugin',\n"," options: {}\n"," }\n"," // other plugins\n","];"]},{"title":"$RefPlugin","route":"/mosaic/configure/plugins/ref-plugin","content":["The "," powers the ","refs"," feature of Mosaic.","The plugin scrapes "," properties from page metadata and also applies all refs stored in ",".","Other plugins can use "," to apply new refs, as long as they call it before this plugin has reaches ",".","Priority","This plugin runs with a priority of -1 so it runs ","after"," most other plugins."]},{"title":"SearchIndexPlugin","route":"/mosaic/configure/plugins/search-index-plugin","content":["The "," is responsible for generating the search index and configuration information for ","Fuse.js"," which is the matching engine powering the client-side search functionality of Mosaic sites.","It outputs 3 files:","Full Search Index - ","Condensed Search Index - ","Search Configuration - ","Full Search Index","On a Mosaic site, the full index is fetched after a page has loaded, thus removing the chance of a huge index slowing down first-load.","Practically, the full index should load in the background before a user searches for something, but should a search be initiated before that (e.g., slow-internet) then the condensed version of the search index is available.","Condensed Search Data","Search Index plugin creates a \"condensed\" version of the search index that only includes the "," and "," for each page. ","This is the \"Minimum Viable Index\" to provide somewhat useable search results client-side while the main search index is loaded in the background.","Search Configuration","Any ","options"," that need to be passed to Fuse.js.","Search relevancy configuration","| Property | Description |\n","| ---------------- | ----------------------------------------------------- |\n","| includeScore | https://www.fusejs.io/api/options.html#includescore |\n","| includeMatches | https://www.fusejs.io/api/options.html#includematches |\n","| maxPatternLength | TODO |\n","| ignoreLocation | https://www.fusejs.io/api/options.html#ignorelocation |\n","| threshold | https://www.fusejs.io/api/options.html#threshold |\n","| keys | https://www.fusejs.io/api/options.html#keys |","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| ------------- | --------------------------------------------------- |\n","| maxLineLength | TODO |\n","| maxLineCount | TODO |\n","| keys | https://www.fusejs.io/api/options.html#keys |\n","| relevancy | ","search relevancy"," |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SearchIndexPlugin',\n"," previewDisabled: true,\n"," options: { maxLineLength: 240, maxLineCount: 240 }\n"," }\n"," // other plugins\n","];","It's usually a good idea to mark this plugin as disabled for preview sources otherwise the pages\n","from the preview will appear in search results."]},{"title":"SharedConfigPlugin","route":"/mosaic/configure/plugins/shared-config-plugin","content":["The "," crawls the page hierarchy to find the closest "," metadata from any parent index's page metadata. ","It then exports a JSON file into each directory with the merged config for that level.","Shared config is typically the place where the following is configured for a Mosaic site:","App Header configuration including site name and main navigation","Footer information","Help links for the left sidebar area","Namespace Shared Configs","Consider 2 sources the share the same ","namespace"," \"product-docs\":","Source A - multiple product directories and main product index page. ","The index page specifies "," metadata.","Source B - pages relevant to a single product. ","Index page does ","not"," have any "," metadata.","Let's also assume that the pages from Source B would also naturally \"fit\" within the pages of Source B (e.g. inside a products directory).","In this scenario, the "," will attempt to copy the shared config file from Source A into the root directory of Source B allowing the Source B pages to use the Source A "," as though it were a product sourced directly from Source A.","Priority","This plugin runs with a priority of 3.","Options","| Property | Description |\n","| -------- | ---------------------------------------------- |\n","| filename | the name of the JSON file output by the plugin |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SharedConfigPlugin',\n"," options: {\n"," filename: 'shared-config.json'\n"," },\n"," priority: 3\n"," }\n"," // other plugins\n","];"]},{"title":"SidebarPlugin","route":"/mosaic/configure/plugins/sidebar-plugin","content":["The "," generates the necessary page metadata needed for the vertical navigation shown on some Mosaic pages.","The output from the plugin is added to a "," metadata property of the page.","Configuration","The "," is used to determine the \"root\" directories of the sidebar. ","So for example:"," - generate sidebar data for the pages that are 3 directories deep in the filesystem hierarchy"," - same as above but ignore the product-a directory"," - generate a sidebar just for product-b in the products directory","To rearrange pages in the sidebar or to apply a different label to a page you can ","configure the sidebar"," using page frontmatter.","Priority","This plugin runs with a priority of 3.","Options","| Property | Description |\n","| ----------- | ----------------------------------------------------------------------------- |\n","| filename | filename of the sidebar json, linked to each related page via ref |\n","| rootDirGlob | Glob pattern for matching directories which should be the root of the sidebar |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: { rootDirGlob: '*/*/*' }\n"," }\n"," // other plugins\n","];"]},{"title":"SiteMapPlugin","route":"/mosaic/configure/plugins/site-map-plugin","content":["The "," generates a ","sitemap"," using the pages in the Mosaic filesystem that adheres to the ","sitemaps XML schema",".","The output of the plugin is a file is named ",".","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| -------- | --------------------------------------------------------------------------------------------------- |\n","| siteUrl | The site URL. ","Used as the prefix for loc entries in the sitemap as these must start with a protocol |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SiteMapPlugin',\n"," previewDisabled: true,\n"," options: { siteUrl: process.env.SITE_URL || 'http://localhost:3000' }\n"," }\n"," // other plugins\n","];","Visualiser","To visualise your sitemap you can combine this plugin with the "," component provided by ",".","Rendering the "," component will render a tree view of your sitemap.","","Have a look at this site's pages, ","here","."]},{"title":"$TagPlugin","route":"/mosaic/configure/plugins/tag-plugin","content":["The "," powers the tags feature of Mosaic.","This plugin scrapes "," from page metadata and also applies all aliases stored in ",".","Tags ultimately resolve down into ","refs",", but are different from normal refs, in that they are applied to the\n","union filesystem (all merged filesystems), not to the individual source filesystem that they were defined on. ","This can be thought of as a ","global ref",".","Other plugins can use "," and modify the "," property, to apply new global refs, as long as\n","they do so before this plugin has reaches ",".","Priority","This plugin runs with no special priority but it must run before both the "," and the ",".","Example use case - Products Page","Let's assume there is a products page on your site and each product is shown as a tile. ","The information for each product tile is sourced from multiple Mosaic sources. ","How can tags be used to reference the product data needed for each tile on the products page?","The product page should add a "," metadata prop to its frontmatter:","---\n","title: Products\n","description: Product index\n","layout: ProductPreview\n","data:\n"," items:\n"," $tag: product#/data\n","---","The tag shown in the example above is saying populate "," of the Products index page with the "," metadata property of pages tagged with ",".","A tagged product page needs to have a "," property which is a collection of tags and one of these needs to be ",". ","This will allow the "," to correctly associate the product page to the product index page. ","It will also need a "," property because thats the property our product index page wants to use for the tiles.","---\n","title: Product A\n","description: My Product description\n","layout: ProductDiscover\n","tags:\n"," - product\n","data:\n"," name:\n"," $ref: '#/title'\n"," date: 2023/02/07\n"," action: Product Overview\n"," description:\n"," $ref: '#/description'\n"," link: /products/a/index\n","---","---\n","title: Product B\n","description: My Product description\n","layout: ProductDiscover\n","tags:\n"," - product\n","data:\n"," name:\n"," $ref: '#/title'\n"," date: 2023/02/07\n"," action: Product Overview\n"," description:\n"," $ref: '#/description'\n"," link: /products/b/index\n","---"]},{"title":"TableOfContentsPlugin","route":"/mosaic/configure/plugins/toc-plugin","content":["The "," generates a Table of Contents for each page in the Mosaic filesystem using the headings on the page.","Heading ranks are used to determine which page headings should be included in the Table of Contents:","| Heading Element (markdown syntax) | Rank |\n","| --------------------------------- | ---- |\n","| h1 (#) | 1 |\n","| h2 (##) | 2 |\n","| h3 (###) | 3 |\n","| h4 (####) | 4 |\n","| h5 (#####) | 5 |\n","| h6 (######) | 6 |","The plugin output is added to a "," metadata property of a page.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| -------- | ----------------------------- |\n","| minRank | The minimum page heading rank |\n","| maxRank | The maximum page heading rank |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/TableOfContentsPlugin',\n"," options: {\n"," minRank: 2,\n"," maxRank: 3\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"Git Repo Source","route":"/mosaic/configure/sources/git-repo-source","content":["The Git Repo Source is used to pull content from a remote git repository e.g. github.","Installation","Credentials and Access tokens","To successfully clone the git repo, the source definition must include credentials that have sufficient permissions to clone the repository.","We recommend storing a ","personal access token"," in an environment variable and using the environment variable in the source definition.","This keeps credentials out of code where they may be accidentally exposed to third parties.","Example","export MOSAIC_DOCS_CLONE_CREDENTIALS=\":\",","Configuration","| Property | Description | Required |\n","| ------------------- | -------------------------------------------------------------------------------- | -------- |\n","| modulePath | The path to the installed module (@jpmorganchase/mosaic-source-git-repo) | Yes |\n","| namespace | The scope for this source. ","| Yes |\n","| disabled | When true, content from this source is not used | No |\n","| options.credentials | Collection of URLS to make requests | Yes |\n","| options.prefixDir | The root path used in the content URL | Yes |\n","| options.subfolder | The name of the folder within the cloned repo containing the docs | Yes |\n","| options.repo | The repo URL | Yes |\n","| options.branch | The branch or tag to clone | Yes |\n","| options.extensions | Collection of file extensions that the source will look for inside the subfolder | Yes |\n","| options.remote | The name of the git remote to use. ","Defaults to origin. ","| Yes |","Example Git Repo Source Definition","\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-git-repo',\n"," namespace: 'mosaic',\n"," options: {\n"," credentials: process.env.MOSAIC_DOCS_CLONE_CREDENTIALS,\n"," prefixDir: 'mosaic',\n"," subfolder: 'docs',\n"," repo: 'https://github.com/jpmorganchase/mosaic.git',\n"," branch: 'main',\n"," extensions: ['.mdx'],\n"," remote: 'origin'\n"," }\n"," }\n"]},{"title":"HTTP Source","route":"/mosaic/configure/sources/http-source","content":["The HTTP Source is used to pull content over HTTP.","Multiple endpoints can be specified and the source will combine and transform the response from each into a single collection of pages.","Installation","Configuration","| Property | Description | Required |\n","| ------------------------------------------ | ----------------------------------------------------------------------------- | -------- |\n","| modulePath | The path to the installed module (@jpmorganchase/mosaic-source-http) | Yes |\n","| namespace | The scope for this source | Yes |\n","| disabled | When true, content from this source is not used | No |\n","| options.endpoints | Collection of URLS to make requests | Yes |\n","| options.prefixDir | The root path used in the content URL | Yes |\n","| options.transformResponseToPagesModulePath | The path of the module used to transform endpoint responses into Mosaic pages | Yes |\n","| options.checkIntervalMins | Number of minutes to wait between requests. ","Defaults to 5 minutes | No |\n","| options.initialDelayMs | Number of milliseconds to wait for making initial request. ","Defaults to 1000 | No |","Example HTTP Source Definition"," {\n"," modulePath: '@jpmorganchase/mosaic-source-http',\n"," namespace: 'my-namespace',\n"," options: {\n"," prefixDir: 'docs',\n"," endpoints: [\n"," 'https://api.data.com/blah',\n"," 'https://api.data.com/hello'\n"," ],\n"," transformResponseToPagesModulePath: '@scope/transformer-package'\n"," }\n"," }"]},{"title":"Sources","route":"/mosaic/configure/sources/index","content":["Sources are what Mosaic uses to pull content from disparate locations and merge into a single virtual filesystem that can be used by a Mosaic Site.","Depending on the ","mode"," used, sources can update periodically ensuring that new content is made available automatically.","Source Definitions","Source Definitions are specified in the "," collection of a mosaic config file.","Each source uses a ","zod schema"," to validate the provided JSON to ensure that all required information for the source to pull content has been provided.","A source definition at a minimum needs to provide the module path of the source and the ","namespace"," that it will use. ","A namespace is not unique across sources though it is common that each source has a different namespace.","Lastly, the options field can be used as a bucket for configuration values needed to configure the source e.g. credentials.","Users are free to add any property as a source option but please read the ","gotchas","\n","first regarding the allowed ","values",".","Example Local Folder Source Definition"," /**\n"," * Demonstrates a local file-system source, in this case a relative path to where the\n"," * site was generated.\n"," * Access from your browser as http://localhost:3000/local\n"," */\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-local-folder',\n"," namespace: 'local', // each site has it's own namespace, think of this as your content's uid\n"," options: {\n"," rootDir: '..","/../docs', // relative path to content\n"," prefixDir: 'local', // root path used for namespace\n"," extensions: ['.mdx'] // extensions of content which should be pulled\n"," }\n"," }","Source Namespace","A Source Namespace is a scoping mechanism for Mosaic sources used to filter the content loaded by Mosaic. ","By default all sources specified in the mosaic config file are loaded.","sources: [\n"," {\n"," namespace: 'my-namespace',\n"," modulePath: '@jpmorganchase/mosaic-source-local-folder'\n"," }\n","];","The following command will ensure mosaic only loads sources with the "," scope.","yarn mosaic serve -c ''./mosaic.config.mjs' -p 8080 --scope \"local\"","Source Schedules","Source schedules define how often sources pull in content that exists remotely and if a failed source is retried. ","More information can be found ","here","Source Types","Out of the box, Mosaic provides 3 source \"types\":","Local Folder","Git Repo Source","HTTP Source","Sources must expose an observable interface so it is possible to compose sources together e.g. the Git Repo source uses the Local Folder source internally to watch the cloned folder for changes.","Watching for Updates","When running in ","active mode",", Mosaic will watch for any changes to the source content and if a change is detected, will initiate a pull of that new content.","How often to check for updates and how updates are triggered are a matter for the source to handle. ","Mosaic simply responds when a source emits new content.","Source Worker Thread","Sources are executed inside their own worker thread to ensure that the main thread is not overloaded. ","It is here that a local virtual filesystem for the source is created and where several of the ","Plugin Lifecycle"," events are triggered.","Gotchas","A service worker thread uses ","postMessage"," to communicate with the main thread and vice-versa.","This is important because it limits what values can be provided in the source definition to those that can be processed by the ","Structured Clone Algorithm","."]},{"title":"Local Folder Source","route":"/mosaic/configure/sources/local-folder-source","content":["The Local Folder Source is used to pull content from a folder located on the same machine as Mosaic is running.","It is common to use this source when running mosaic locally.","Installation","Configuration","| Property | Description | Required |\n","| ------------------ | ------------------------------------------------------------------------------ | -------- |\n","| modulePath | The path to the installed module (@jpmorganchase/mosaic-source-local-folder) | Yes |\n","| namespace | The scope for this source | Yes |\n","| disabled | When true, content from this source is not used | No |\n","| options.rootDir | The top level directory content will be pulled from | Yes |\n","| options.prefixDir | The root path used in the content URL | Yes |\n","| options.extensions | Collection of file extensions that the source will look for inside the rootDir | Yes |","Example Local Folder Source Definition","{\n"," modulePath: '@jpmorganchase/mosaic-source-local-folder',\n"," namespace: 'local', // each site has it's own namespace, think of this as your content's uid\n"," options: {\n"," rootDir: '..","/../docs', // relative path to content\n"," prefixDir: 'local', // root path used for namespace\n"," extensions: ['.mdx'] // extensions of content which should be pulled\n"," }\n","}","This source will look for content with the \".mdx\" extension in a \"docs\" directory 2 levels up from the Mosaic working directory. ","That content is included in the \"local\" namespace and available from a route that is prefixed with \"local\".","So if you had a file, "," then you would be able to view it at ","."]},{"title":"Source Schedules","route":"/mosaic/configure/sources/schedules","content":["A source schedule defines how often a source initiates a content pull and what to do when there is a failure.","A schedule can be specified for each source in the source definition, but should a source not provide a schedule it will inherit the \"global\" schedule.","Configuration","| Property | Description | Required | Default |\n","| ----------------- | -------------------------------------------------------------------------------------- | -------- | ------- |\n","| checkIntervalMins | The length of time in minutes before triggering a content refresh | Yes | 30 mins |\n","| initialDelayMs | Startup delay for the source. ","| Yes | 1000 ms |\n","| retryEnabled | When true, failures will trigger another content pull | No | true |\n","| retryDelayMins | The interval between retries. ","This will rise exponentially on every failure. ","| No | 5 |\n","| maxRetries | Maximum number of retry attempts | No | 100 |\n","| resetOnSuccess | If true, when a source recovers and emits pages it's retry counter is returned to zero | No | true |","Global Schedule","The global schedule applies to all sources that do ","not"," provide their own schedule. ","It can be configured as a top-level property of the Mosaic config file."," schedule: {\n"," checkIntervalMins: 60,\n"," initialDelayMs: 1000,\n"," retryDelayMins: 15,\n"," maxRetries: 20\n"," }","Example","Given the config file below:"," schedule: {\n"," checkIntervalMins: 30,\n"," initialDelayMs: 1000,\n"," retryDelayMins: 5,\n"," maxRetries: 10\n"," },\n"," sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-git-repo',\n"," namespace: 'sourceA',\n"," options: {\n"," credentials: 'credentials',\n"," prefixDir: 'sourceA',\n"," subfolder: 'docs',\n"," repo: 'source-a-repo-url',\n"," branch: 'develop',\n"," extensions: ['.mdx'],\n"," remote: 'origin'\n"," }\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-git-repo',\n"," namespace: 'sourceB',\n"," schedule:{\n"," checkIntervalMins: 60,\n"," initialDelayMs: 5000,\n"," retryDelayMins: 30,\n"," maxRetries: 50\n"," }\n"," options: {\n"," credentials: 'credentials',\n"," prefixDir: 'sourceB',\n"," subfolder: 'docs',\n"," repo: 'source-b-repo-url',\n"," branch: 'develop',\n"," extensions: ['.mdx'],\n"," remote: 'origin'\n"," }\n"," }\n"," ]","Source A will inherit the global schedule so it will:","Start after a 1 second delay","Pull content every 30 minutes","Retry a failed content pull after an initial 5 minute delay","Retry 10 times and if still unsuccessful, closing","Source B has its own schedule so it will:","Start after a 5 second delay","Pull content every 60 minutes","Retry a failed content pull after an initial 30 minute delay","Retry 50 times and if still unsuccessful, closing","Retry Strategy","The retry strategy that Mosaic employs is ","Exponential Backoff",". ","This is a common strategy for networking applications that aims to prevent retries from causing more harm than good.","For example, given a source schedule that has a 1 minute retry delay and will retry a maximum of 3 times then the total time spent retrying is 7 minutes:","1 minute delay then 1st retry","2 minute delay then 2nd retry","4 minute delay then 3rd (and final) retry","Total delay: 1 + 2 + 4 = 7 minutes","As you can see, the delay between retries is growing exponentially giving the content source more time to recover after each retry."]},{"title":"Figma Source","route":"/mosaic/configure/sources/source-figma","content":["The Figma source is used to pull individual design patterns from Figma projects.\n","The source subscribes to Figma project groups, then extracts from project files, tagged patterns.","A Figma pattern tag is defined within a Figma project's ",".","For each retrieved/tagged pattern, a page is created in the Mosaic file-system.\n","Additional Mosaic metadata can be added, to each created page, using ","\n","For instance we could add metadata which defines which product owns a particular pattern and then\n","group patterns based on owner.","Installation","Configuration","The Figma source is an "," and shares the same base configuration.\n","The "," prop has a default transformer which creates a page for each matching Story.","| Property | Description | Required |\n","| ---------- | --------------------------------- | -------- |\n","| prefixDir | path to store figma patterns | Yes |\n","| figmaToken | figma access token | Yes |\n","| projects | array of projects to subscribe to | Yes |\n","| endpoints | figma endpoints | Yes |","Each project is configured from the "," array.","| Property | Description | Required |\n","| ------------- | --------------------------------------------- | -------- |\n","| id | numerical id of the subscribed project group | Yes |\n","| meta | metadata to add to each of the projects pages | Yes |\n","| patternPrefix | prefix to add to all pages created | Yes |"," defined the Figma REST API endpoints.","| Property | Description | Required |\n","| ----------------- | --------------------------------------------------------- | -------- |\n","| getProject | url to return a list of projects within the project group | Yes |\n","| getFile | url to return a project file | Yes |\n","| generateThumbnail | url to generate a thumbnail for the shared Figma node | Yes |","Example Figma Source Definition","sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-figma',\n"," namespace: 'some-namespace',\n"," options: {\n"," proxyEndpoint: 'http://path/to/optional/proxy',\n"," prefixDir: '/some/path/to/where/you/store/pattern/pages',\n"," figmaToken: process.env.FIGMA_TOKEN,\n"," projects: [{\n"," id: 99999,\n"," patternPrefix: 'yourPrefix',\n"," meta: {\n"," tags: [ 'some-tag'],\n"," data: {\n"," owner: 'some-owner'\n"," }\n"," }\n"," }],\n"," endpoints: {\n"," getFile: 'https://api.figma.com/v1/files/:file_id?","plugin_data=shared',\n"," getProject: 'https://api.figma.com/v1/projects/:project_id/files',\n"," generateThumbnail: 'https://api.figma.com/v1/images/:project_id?","ids=:node_id'\n"," }\n"," }\n"," }\n","]"]},{"title":"Readme Source","route":"/mosaic/configure/sources/source-readme","content":["The Readme source is used to pull individual "," text files from repositories.\n","This is a lighter weight version of a Git repository resource, which just pulls a single file,\n","rather the whole contents of the repo.","For each retrieved readme, a page is created in the Mosaic file-system.\n","Additional Mosaic metadata can be added, to each created page, using ","\n","For instance we could add metadata which defines which product owns a particular "," and then\n","group this page, with others, based on owner.","Installation","Configuration","The Readme source is an "," and shares the same base configuration.","| Property | Description | Required |\n","| ----------- | --------------------- | -------- |\n","| accessToken | request access token | Yes |\n","| prefixDir | path to store pages | Yes |\n","| readme | array of readme files | Yes |","Each readme is configured from the "," array.","| Property | Description | Required |\n","| --------------- | ---------------------------- | -------- |\n","| contentTemplate | template for content of page | No |\n","| name | page name used in route | Yes |\n","| readmeUrl | url of readme | Yes |\n","| meta | metadata for page | Yes |","Example Readme Source Definition"," sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-readme',\n"," namespace: 'your-namespace',\n"," schedule: { checkIntervalMins: 0.25, initialDelayMs: 0 },\n"," options: {\n"," prefixDir: '/salt/community-index/readme',\n"," accessToken: 'Bearer your-access-token',\n"," readme: [\n"," {\n"," name: 'your-mosaic-page-name',\n"," readmeUrl: 'https://some/repo/url/readme.md',\n"," contentTemplate: `a template which replaces ::content:: with the content`,\n"," meta: {\n"," layout: 'DetailTechnical',\n"," title: 'Your Page Title',\n"," tags: ['your-tag-if-required'],\n"," description: 'A description for your readme'\n"," }\n"," }]\n"," }\n"," }]"]},{"title":"Storybook Source","route":"/mosaic/configure/sources/source-storybook","content":["The Storybook source is used to pull individual stories from Storybook, based on tags.","The Mosaic source will filter your Storybook's stories, based on "," or ",".","For each matching story, a page is created in the Mosaic file-system.","additional Mosaic tags can be added to each page using ","additional Mosaic metadata can be added to each page using ","This information can be used to create a dynamic index of stories from Storybook.","Installation","Configuration","The Storybook source is an "," and shares the same base configuration.\n","The "," prop has a default transformer which creates a page for each matching Story.","The "," option is an array of Storybook urls that are used as Sources.\n","Each story is matched on "," using the "," Regexp (or by ",").","If specified, "," and "," can be added to any matching pages.","| Property | Description | Required |\n","| --------------- | ---------------------- | -------- |\n","| options.stories | array of story configs | Yes |"," is an array of Storybooks that you want to pull stories from","| Property | Description | Required |\n","| -------------- | ---------------------------------------- | -------- |\n","| storyUrlPrefix | prefix url of the storybook deployment | Yes |\n","| description | description of the storybook stories | Yes |\n","| storiesUrl | url of the storybook's "," | No |\n","| filter | RegExp filter to match on | No |\n","| filterTags | Array of Storybook tags to match on | No |\n","| meta | additional data to add to matching pages | No |","Example Local Folder Source Definition","sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-storybook',\n"," namespace: 'salt',\n"," options: {\n"," prefixDir: '/salt/internal/community-index/story-navigation',\n"," stories: [\n"," {\n"," storiesUrl: 'https://storybook.saltdesignsystem.com/stories.json',\n"," storyUrlPrefix: 'https://storybook.saltdesignsystem.com',\n"," description: 'Navigation patterns created in the Salt Labs',\n"," meta: { // can be any additional metadata you want added to the page's meta.data\n"," tags: ['some-tag'],\n"," data: {\n"," owner: 'Salt',\n"," source: 'STORYBOOK'\n"," }\n"," },\n"," filter: /Lab\\/Tabs/ // this is a Regexp that matches on Storybook title\n"," }\n"," ]\n"," }\n"," }\n","]","Example story metadata format","{\n"," \"type\": \"story\",\n"," \"id\": \"folder--story-1\",\n"," \"name\": \"Story 1\",\n"," \"title\": \"Folder/Story 1\",\n"," \"importPath\": \"./folder/story-1.stories.tsx\",\n"," \"tags\": [\n"," \"dev\",\n"," \"test\"\n"," ]\n","}"]},{"title":"Custom Components","route":"/mosaic/configure/theme/custom-components","content":["Learn how to add your own custom components to your Mosaic site.","Create Components Folder","To start, create a "," folder under "," where you'll store your custom components.","src/\n","└── components/","In this tutorial, we will create a custom "," component.","Create Card Component","Inside the "," folder, create a "," folder, which will contain your React "," component. ","The "," folder should include "," and "," files as shown in the structure below:","├── src/\n","│ ├── components/\n","│ │ └── card/\n","│ │ ├── index.tsx\n","│ │ └── card.module.css","Card Component: index.tsx","Create your "," component within the "," file:","import React from 'react';\n","import styles from './card.module.css';\n","\n","type CardProps = {\n"," title: string;\n"," content: string;\n","};\n","\n","export const Card: React.FC = ({ title, content }) => {\n"," return (\n","
    \n","

    {title}

    \n","

    {content}

    \n","
    \n"," );\n","};","Card Component: card.module.css","Define your component styles in the "," file:",".card {\n"," background-color: #f5f5f5;\n"," border: 1px solid #ccc;\n"," border-radius: 4px;\n"," box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n"," padding: 16px;\n"," transition: box-shadow 0.2s ease-in-out;\n","}\n","\n",".card:hover {\n"," box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n","}\n","\n",".card h2 {\n"," color: #333;\n"," font-size: 24px;\n"," margin-bottom: 8px;\n","}\n","\n",".card p {\n"," color: #666;\n"," font-size: 16px;\n"," line-height: 1.5;\n","}","In this example, we use a CSS file, but you can use whichever styling approach you prefer, such as ","vanilla extract",".","To export your "," component, create an "," file in the "," folder:","export * from './card';","Your final folder structure should look like this:","├── src/\n","│ ├── components/\n","│ │ ├── card/\n","│ │ │ ├── index.tsx\n","│ │ │ └── card.module.css\n","│ │ ├── index.ts","Import Custom Card Component","To use your custom "," component, import it into your site's "," file. ","Add the following line to your imports:","import * as myComponents from '../components';","Replace this line:","const components = mosaicComponents;","with:","const components = {\n"," ...mosaicComponents,\n"," ...myComponents\n","};","This will add your custom components to the site, and any custom components in "," will override the corresponding ones in ",". ","The spread operator (",") merges both "," and "," objects, giving priority to "," when there is a naming conflict.","Use Your Custom Card Component","Now you're ready to use your custom "," component. ","Build and run your site, and add the "," component to an MDX file in your "," folder or another source:","","You can create and add more custom components to your Mosaic site by following the same process."]},{"title":"Custom CSS","route":"/mosaic/configure/theme/custom-css","content":["You can customize the look and feel of your Mosaic site by creating cusotm CSS files. ","Here is a step-by-step guide to help you create your own CSS theme.","Create a CSS folder","To get started, create a folder named \"css\" in the \"src\" folder of your Mosaic project.","src/\n","└── css/","Create your theme","Inside the \"css\" folder, create a folder named \"global\". ","This is where you will add your custom styles.","src/\n","└── css/\n"," ├── global/\n"," ├── index.css","Create an \"index.css\" file inside the \"css\" folder. ","This file will import your custom styles.","@import './global/';","Inside your global folder, create a separate CSS file for each part of your site that you want to customize. ","For instance, if you want to change the text styling, create a \"text.css\" file inside the \"global\" folder. ","Here is an example of how your \"text.css\" file could look like:","h1 {\n"," /* Set custom font size and weight */\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h2 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h3 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h4 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h5 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h6 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","p {\n"," font-size: /* insert value */ ;\n"," line-height: /* insert value */ ;\n","}","You can add as many CSS files as you need, depending on how much you want to customize your site.","Create an \"index.css\" file inside the \"global\" folder. ","This file will import your custom styles, in this example we are importing our \"text.css\" file.","@import './text.css';","Your \"css\" folder should now look like this:","src/\n","└── css/\n"," ├── global/\n"," │ ├── text.css\n"," │ ├── index.css\n"," ├── index.css","Import your custom CSS into your site","To apply your custom styles to your Mosaic site, open your \"_app.tsx\" file and add the following line to the bottom of your imports:","import '../css/index.css';","Congratulations! ","You have successfully applied your custom CSS styles to your site. ","This example demonstrated how to create text styles, but you can use the same approach to customize other aspects of your site as well."]},{"title":"Theming Your Site","route":"/mosaic/configure/theme/index","content":["Create a unique look and feel for your Mosaic site by customizing the CSS theme and integrating your own UI components.","Customize the CSS","Adapt various design elements of your Mosaic site to create a cohesive visual theme. ","Refer to our guide to learn more about crafting a custom CSS theme:","CSS Theme Guide","Import Custom Components","Incorporate your own UI components, to tailor the look and functionality of your content. ","This tutorial will walk you through the process of adding custom components to your site:","Adding Custom Components Tutorial"]},{"title":"Aliases Test","route":"/mosaic/test/aliases/index","content":["This page is the alias test page."]},{"title":"Detail Highlight Test Page","route":"/mosaic/test/layouts/detail-highlight","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Detail Overview Test Page","route":"/mosaic/test/layouts/detail-overview","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Detail Technical Test Page","route":"/mosaic/test/layouts/detail-technical","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Edit Layout","route":"/mosaic/test/layouts/edit","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Full Width Layout","route":"/mosaic/test/layouts/full-width","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Layouts","route":"/mosaic/test/layouts/index","content":["Pages for e2e testing of layouts."]},{"title":"Landing Layout Test Page","route":"/mosaic/test/layouts/landing","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Newsletter Test Page","route":"/mosaic/test/layouts/newsletter","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Product Discover Test Page","route":"/mosaic/test/layouts/product-discover","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Product Preview Test Page","route":"/mosaic/test/layouts/product-preview","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Refs Data","route":"/mosaic/test/refs/data","content":[]},{"title":"Refs Test","route":"/mosaic/test/refs/index","content":["The sidebar priority is ",".","The other page data is ","."]},{"title":"Tags Test","route":"/mosaic/test/tags/index","content":["In Stock","Out of Stock"]},{"title":"$afterSource","route":"/mosaic/configure/plugins/lifecycle/after-source","content":["The first lifecycle event to trigger after receiving pages from a source and runs in a child process.\n","The pages can safely be mutated and will be reflected in the final filesystem that gets generated.\n","It ","must"," return a collection of pages.","The "," lifecycle event is called with:","pages - the collection of pages emitted by the source","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | A mutable object for sharing data with other lifecycle phases of all plugins for this source (including in the main thread) in this plugin |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |","Example - Log out all page routes","async function $afterSource(pages, { config, ignorePages, pageExtensions }) {\n"," for (const page of pages) {\n"," console.log(page.route);\n"," }\n"," return pages;\n","}"]},{"title":"afterUpdate","route":"/mosaic/configure/plugins/lifecycle/after-update","content":["The third lifecycle event to trigger overall and the first to trigger inside the main Mosaic process.","Calls after the filesystem and symlinks have been reconstructed due to a change to the current source pages. ","Pages will ","not"," be cached when read at this stage, to allow for reading content and writing a new copy of it without the cached version taking effect.\n","This method is safe to use with lazy loading, as the filesystem should return the full page when read.","The "," lifecycle event is called with:","mutableFilesystem - Mutable filesystem instance with all of this source's pages inside (and symlinks re-applied)","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | An immutable object for reading data from other lifecycle phases of all plugins for this source in the child process for this plugin. ","Shared only with this source. ","|\n","| globalConfig | An immutable object for reading data from other lifecycle phases of all plugins. ","Shared across all sources. ","|\n","| sharedFilesystem | Mutable filesystem instance independent of any sources. ","Useful for global pages, like sitemaps |\n","| globalFilesystem | Immutable union filesystem instance with all source's pages (and symlinks applied) |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]},{"title":"$beforeSend","route":"/mosaic/configure/plugins/lifecycle/before-send","content":["The second lifecycle event to trigger and does so after a filesystem has been built up from the source pages.","It is the last lifecycle event to run in a source child process before the filesystem is sent to the main Mosaic process and should ","not"," return a value.","The "," lifecycle event is called with:","mutableFilesystem - Mutable virtual filesystem instance with all of this source's pages inside (and symlinks applied)","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | A mutable object for sharing data with other lifecycle phases of all plugins for this source (including in the main thread) in this plugin |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]},{"title":"Lifecycle Events","route":"/mosaic/configure/plugins/lifecycle/index","content":["Plugin lifecycle","The plugin lifecycle is triggered when a source emits content.","Each Mosaic source has its own worker thread and plugin lifecycle events that start with a "," will execute inside the worker thread.\n","All other lifecycle events will execute in the main Mosaic process.","The 5 lifecycle events are:","$afterSource","$beforeSend","afterUpdate","shouldClearCache","shouldUpdateNamespaceSources","Plugin methods that trigger inside the main thread should be asynchronous and highly optimised to\n","avoid holding up the main thread."]},{"title":"shouldClearCache","route":"/mosaic/configure/plugins/lifecycle/should-clear-cache","content":["The fourth lifecycle event to trigger overall and the second to trigger inside the main Mosaic process.","It is called every time ","any"," source emits new pages and should return a boolean to indicate if ","other"," sources should clear their cache in response to the source updating.","Only sources that have already run "," will call this lifecycle hook since there is no\n","cache to clear if they haven't reached that stage in the lifecycle.","The "," lifecycle event is called with:","updatedSourceFilesystem - Immutable filesystem for the source that changed i.e, not the source filesystem","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | An immutable object for reading data from other lifecycle phases of all plugins for this source in the child process for this plugin. ","Shared only with this source. ","|\n","| globalFilesystem | Immutable union filesystem instance with all source's pages (and symlinks applied) |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]},{"title":"shouldUpdateNamespaceSources","route":"/mosaic/configure/plugins/lifecycle/should-update-namespace-sources","content":["The fifth lifecycle event to trigger overall and the third to trigger inside the main Mosaic process.","It is called every time ","any"," source emits new pages and should return a boolean to indicate if ","other sources that share the same ","source namespace"," should re-run ",".","The "," lifecycle event is called with:","updatedSourceFilesystem - Immutable filesystem for the source that changed i.e, not the source filesystem","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | An immutable object for reading data from other lifecycle phases of all plugins for this source in the child process for this plugin. ","Shared only with this source. ","|\n","| globalFilesystem | Immutable union filesystem instance with all source's pages (and symlinks applied) |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]}] \ No newline at end of file +[{"title":"Mosaic","route":"/mosaic/index","content":["True to its name, Mosaic brings together several concepts—including content, design and technical infrastructure—to deliver a unified website experience that is truly greater than the sum of its individual parts.","With Mosaic, you can:","Don't move your content where it does not belong. ","Compose content from remote data sources which\n","are pulled at runtime by our content aggregator.","Visualize your content with your own theme, layouts and components or use the Mosaic Design\n","language.","Extend the existing code and add your own content source types through our simple plugin\n","architecture.","Publish your content through Server Side Rendering (SSR) or generate a snapshot of your content\n","and serve it as a Statically Generated Site (SGS).","Creating a website has never been so easy!"]},{"title":"Sitemap","route":"/mosaic/sitemap","content":["Sitemap"]},{"title":"Aliases","route":"/mosaic/author/aliases","content":["Aliases are virtual 'symlinks' of pages, allowing one page to take on one or more other routes.\n","This is not the same as copying the page, it is a primitive filesystem link which is resolved by Mosaic.","Use Cases","Aliases are great for linking to an old route if you've moved a page to a new place.\n","They can also be used to create short hand links to pages or to 'clone' a copy of a page into\n","another section of the site where it may be relevant.","Example","This is the frontmatter for this page:","---\n","title: Aliases\n","layout: DetailTechnical\n","aliases:\n"," - /mosaic/example/aliases\n","---","Try accessing this page from ","/mosaic/example/aliases"]},{"title":"Fragments","route":"/mosaic/author/fragments","content":["Fragments, also known as content fragments, are powerful tools that allow you to incorporate content from other pages into your documentation. ","By creating an MDX file and using the ","generic directives"," syntax ",", you can easily render the fragment in another file, providing modularity and reusability to your content.","Use Cases","Fragments offer various use cases, such as:","Consistent Content",": Use fragments to maintain consistent content across multiple pages. ","For instance, if you have a table or a tile that appears on multiple pages, you can create a fragment for it and include it in all relevant files. ","This ensures that any updates made to the fragment automatically reflect across the entire documentation.","Reusable Components",": Fragments enable the creation of reusable components or sections. ","You can define a complex or commonly used section once and then include it in multiple pages as needed. ","This approach saves time and effort, as you only need to update the fragment file to propagate changes throughout your documentation.","Modular Documentation",": With fragments, you can break down your documentation into smaller, manageable pieces. ","Each fragment represents a specific topic or section, allowing you to organize and structure your content more efficiently. ","This modular approach simplifies maintenance and makes it easier to navigate and update your documentation.","Usage","Firstly, enable the Fragment Plugin by adding the following to your plugins in ",".","{\n"," modulePath: '@jpmorganchase/mosaic-plugins/FragmentPlugin',\n"," options: {}\n","}","To include a fragment in your content, follow these steps:","Create an MDX file for the fragment you want to reuse. ","Remember to set the sidebar property of your fragment's frontmatter to exclude: true if you don't want the fragment to appear in the vertical navigation menu.","---\n","title: Fragment Title\n","sidebar:\n"," exclude: true\n","---","In the target file where you want to include the fragment, use the remark directive syntax ",".","Markdown Content Example","This is the contents of a fragment located at ",":","---\n","title: Content Fragment\n","sidebar:\n"," exclude: true\n","---\n","\n","#### Fragment Title\n","\n","This is an example fragment of markdown content, being pulled from `../fragments/content-fragment.mdx`.\n","The below code snippet will render the content from the content-fragment.mdx file in your target file:",":fragment{src=\"../fragments/content-fragment.mdx\"}","Example output:","Fragment Title","This is an example fragment of markdown content, being pulled from ",".","Component Example","Here is another example, where the fragment files each contain a "," component.",":fragment{src=\"../fragments/tile-a.mdx\"} :fragment{src=\"../fragments/tile-b.mdx\"}","The above code will render the content from tile-a.mdx and tile-b.mdx files, demonstrated below:"," "]},{"title":"Frontmatter","route":"/mosaic/author/frontmatter","content":["Frontmatter",", also known as page metadata, is a powerful feature that allows easy configuration of a page and Mosaic site components e.g. the sidebar.","Frontmatter is written in yaml syntax and is found at the top of a page between 2 sets of 3 dashes: ",".","Example page yaml","---\n","title: Page Title\n","layout: DetailTechnical\n","sidebar:\n"," priority: 4\n","---\n","\n","// frontmatter is closed and now comes page content\n","# Page Title\n","\n","This is some content.\n","Accessing Frontmatter in content","With the syntax below it is possible to directly reference frontmatter inside content using curly brackets and the "," object.","You can think of "," as a JSON object that holds all the frontmatter of a page and when the Mosaic "," encounters the curly brackets then the value in the frontmatter will be resolved.","{meta.title}\n","{meta.description}\n","{meta.someValueYouHaveAddedToTheFrontmatter}","This is very common to see Mosaic pages that reference the title as shown below:","---\n","title: Title\n","---\n","\n","# {meta.title}","Plugins & Frontmatter","Mosaic plugins can also embed their output into page frontmatter in 2 different ways:","a property is directly added to the page object","a JSON file is generated and referenced using a ","ref","Adding a property to the page","A plugin can add a property to a page simply by extending the page object it receives in the "," lifecycle event:","async function $afterSource(pages) {\n"," for (const page of pages) {\n"," page.newProperty = 'Hello'\n"," }\n"," return pages;\n","}","You could use this property in the page content using ","JSON File","Let's take a look at the ",".","The purpose of this plugin is to crawl the page hierarchy to find the closest "," found in any parent page's frontmatter.","Finds all index pages among the source docs","Deserialises those pages so it can read the frontmatter and content of the page","If a property called "," in the page frontmatter is found a new file named shared-config.json is created","Adds a ref named "," to the shared config file that points to the shared config of the index page","import type { Page, Plugin as PluginType } from '@jpmorganchase/mosaic-types';\n","import { flatten } from 'lodash-es';\n","import path from 'path';\n","\n","function createFileGlob(url, pageExtensions) {\n","if (pageExtensions.length === 1) {\n","return `${url}${pageExtensions[0]}`;\n","}\n","return `${url}{${pageExtensions.join(',')}}`;\n","}\n","\n","interface SharedConfigPluginPage extends Page {\n","sharedConfig?: string;\n","}\n","\n","interface SharedConfigPluginOptions {\n","filename: string;\n","}\n","\n","const SharedConfigPlugin: PluginType = {\n","async $beforeSend(\n"," mutableFilesystem,\n"," { config, serialiser, ignorePages, pageExtensions },\n"," options\n"," ) {\n"," const pagePaths = await mutableFilesystem.promises.glob(\n"," createFileGlob('**/index', pageExtensions),\n"," {\n"," ignore: [options.filename, ...flatten(ignorePages.map(ignore => [ignore, `**/${ignore}`]))],\n","cwd: '/'\n","}\n",");\n","\n"," for (const pagePath of pagePaths) {\n"," const sharedConfigFile = path.join(path.dirname(String(pagePath)), options.filename);\n","\n"," const page = await serialiser.deserialise(\n"," String(pagePath),\n"," await mutableFilesystem.promises.readFile(String(pagePath))\n"," );\n"," if (page.sharedConfig) {\n"," config.setRef(sharedConfigFile, ['config', '$ref'], `${String(pagePath)}#/sharedConfig`);\n"," await mutableFilesystem.promises.writeFile(sharedConfigFile, '{}');\n"," } else {\n"," const baseDir = path.posix.resolve(path.dirname(String(pagePath)), '..","/');\n"," config.setAliases(path.join(baseDir, options.filename), [sharedConfigFile]);\n"," }\n"," }\n","\n","}\n","};\n","\n","export default SharedConfigPlugin;\n"]},{"title":"Author","route":"/mosaic/author/index","content":["Here you will learn how to author documentation using markdown, Mosaic components and page templates"]},{"title":"Markdown Syntax","route":"/mosaic/author/markdown-syntax","content":["Out of the box, Mosaic supports documents written in ","MDX"," which allows React components to be embedded within the ","basic syntax"," outlined in the original Markdown design document.","In addition to the basic markdown syntax, the MDX processor used by Mosaic has been configured to support additional markdown syntax and features:","GitHub flavored markdown (gfm)","Frontmatter","Mosaic Standard Components","All components that comprise the "," export from "," package are available to use out of the box in your documents.","This includes components for all standard markdown syntax and additional components like "," and ",".","Referencing Frontmatter","Frontmatter values can be embedded into the page using the "," object. ","See ","frontmatter"," for more information.","Configuring Supported components","TODO"]},{"title":"Refs","route":"/mosaic/author/refs","content":["Refs are a very powerful feature of Mosaic documents that use ","JSON References"," to reference\n","a property from the page frontmatter or frontmatter of other pages.","The key concept is that of a JSON pointer which takes the form ","A","#","B"," where:","A"," is the relative path from the current schema to a target schema. ","If A is empty, the reference is to a type or property in the same schema, an in-schema reference. ","Otherwise, the reference is to a different schema, a cross-schema reference.","B"," is the complete path from the root of the schema to a type or property in the schema. ","If # in not included or B is empty, the reference is to an entire schema.","To translate this for our purposes:","A"," is the relative path to a file in the filesystem.","B"," is the path to a property in the frontmatter of the file.","It's probably better explained with examples...","Local refs (In-schema reference)","It is possible to reference a page's own frontmatter to avoid duplication:","---\n","title: Refs\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," sidebarPriority:\n"," $ref: '#/sidebar/priority'\n","---","This page you are reading right now has a sidebar priority of ",".","The value of "," comes from "," in the frontmatter.","Notice that because we don't specify a path before the "," in the ref we need to put the whole\n","value inside quotes.","Remote Refs (Cross-schema reference)","It is possible to reference frontmatter of other pages. ","Again this helps avoid duplication but the real power is using refs to build the data model. ","See ","advanced"," for more information.","The ","index"," page of the Author section has this frontmatter:","---\n","title: Author\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," exampleRefData: Hello from Author page\n","---","This page you are currently looking at is referencing the "," in it's frontmatter like this:","---\n","title: Refs\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," authorRef:\n"," $ref: ./#/data/exampleRefData\n","---","I can then use the data and embed it in this page like this:","{meta.data.authorRef}","And here it is: ","Notice that we did not need to put "," in the ref since index pages are resolved\n","automatically.","Advanced","You have had a taste of the power and want to know more? ","OK, lets reference and then list out all the mosaic modes:","The Modes ","index"," page has this frontmatter:","---\n","title: Modes of operation\n","layout: DetailTechnical\n","sidebar:\n"," priority: 4\n","data:\n"," modes:\n"," $ref: ./*#/title\n","---","The ref here is essentially using a wildcard (the *) to grab the "," property from the frontmatter of every page in the modes folder.","We can reference that data just like before:","---\n","title: Refs\n","layout: DetailTechnical\n","sidebar:\n"," priority: 3\n","data:\n"," modes:\n"," $ref: ../configure/modes#/data/modes\n","---","Output","With the code below, the referenced data can be embedded in a page.","
      \n"," {meta.data.modes.map(mode => (\n","
    • {mode}
    • \n"," ))}\n","
    ","Output with Mosaic Components","You can use Mosaic components with referenced data as well. ","Below we are using the "," and "," components.","\n"," {meta.data.modes.map(mode => (\n"," \n"," ))}\n","","Setting Refs using Plugins","Mosaic plugins can create new refs, create new ","global"," refs and see existing refs created by the page or other plugins. ","This is achieved using a special "," property available in the plugin helpers.","Create new refs","Use the "," function from the helpers provided to plugin lifecycle events. ","You need to provide","The file/fullpath to write the ref to","The path to the ptoperty where the ref will be applied","The value of the ref, which can use wildcards","For example, the following would add a property to pages named ",". ","The value is the title of all pages in the ","same"," directory as the current page"," async $afterSource(pages, { config }) {\n"," for (const page of pages) {\n"," config.setRef(page.fullPath, ['titles', '$ref'], `**#/title`);\n"," }\n"," return pages;\n"," }","When setting the property path the last string must be "," otherwise you're not creating a ref\n","that will be resolved by the RefPlugin.","Existing refs","To view refs that already exist you can use ",". ","For example to see all refs for a page:"," config.data.refs[fullPathToPage]","Create global refs","Global refs are similar to regular refs except they do not pre-resolve. ","This means they are resolved when the referenced file is read and the global mosaic filesystem, made up of multiple sources, is used rather than the filesystem of a single source."]},{"title":"Sidebar Configuration","route":"/mosaic/author/sidebars","content":["Sidebar data is generated by the the ","Sidebar Plugin"," which by default uses alphabetical ordering of page names to order the sidebar.","Sidebar frontmatter","A page can add a "," property to its ","frontmatter"," to change the ordering of a sidebar and what title is used for a page in the sidebar.","To rearrange pages in the sidebar or to apply a different label to a page you can specify the following in the page frontmatter:","Sidebar group label","A sidebar group when expanded will reveal the pages contained within the group.\n","Each group has a default page, ",", which is the default for the group and any breadcrumb link.","The"," can define a "," which defines the name of the sidebar group.\n","If no ","groupLabel"," is defined, sidebar group labels will be defined by either ","label"," or ","title",".","To specify the label of the default page, refer to [Sidebar label](.","/Sidebar label)","---\n","title: Sidebar Configuration\n","layout: DetailTechnical\n","sidebar:\n"," groupLabel: Group label\n","---","Sidebar label","By default the ","title"," of a page is used in the sidebar as the label but this can be changed to another label using page frontmatter.","---\n","title: Sidebar Configuration\n","layout: DetailTechnical\n","sidebar:\n"," label: A New Label\n","---","Sidebar priority","Sidebar priority is a number used to sort the sidebar. ","Higher the priority pages appear first in the sidebar ordering.","---\n","title: Sidebar Configuration\n","layout: DetailTechnical\n","sidebar:\n"," priority: 10\n","---","Sidebar Sort Configuration","Sidebar sort configuration allows the sidebar to be sorted using a more sophisticated approach and only needs to be applied to the "," page of the directory you want to sort.","Priority takes precedence over sort configuration so it can be used to override the sort\n","configuration if required.","You must add the sidebar sort configuration to the "," property of an ","index"," page e.g.,","sharedConfig:\n"," sidebar:\n"," sort:\n"," field: data/title\n"," dataType: string\n"," arrange: desc","The properties of the sort configuration are described in the table below:","| Property | Description | Required |\n","| -------- | ------------------------------------------------------------------------------------------ | -------- |\n","| field | the path, separated by ",", used to find the value in page frontmatter you wish to sort by | Yes |\n","| dataType | is the type of the value. ","Can be a "," or "," or ",". ","| Yes |\n","| arrange | "," or "," order | yes |","Newsletters Example","Let's say you have a ","Newsletters"," directory and each page in the directory represents a newsletter. ","You wish to sort the newsletter sidebar by publication date in descending order (newest first).","One way to do this is to edit each page and add a "," which is manually incremented every time a news newsletter is added. ","Alternatively you can add the following sort configuration to the newsletters index page:","sharedConfig:\n"," sidebar:\n"," sort:\n"," field: data/publicationDate\n"," dataType: date\n"," arrange: desc","This will use the "," property in each newsletter to sort the newsletters in the sidebar. ","The publication date is converted to a "," by the Sidebar Plugin to ensure accurate ordering. ","There is now no need to increment a priority when a new newsletter is added.","Example newsletter page frontmatter:","---\n","title: Newsletter 01 Jan 2023\n","description: Newsletter 01 Jan 2023\n","data:\n"," title:\n"," $ref: '#/title'\n"," link: /newsletters/2023-01-01\n"," publicationDate: '2023-01-01'\n","---"]},{"title":"Tags","route":"/mosaic/author/tags","content":["Tags are very similar to ","Refs"," with one very important distinction: Tags work ","across multiple sources",".","In Mosaic, each source has it's own filesystem which are then merged together to form a union of all source filesystems. ","It is in this union filesystem that tags are applied and not to the individual source filesystem that the tag was defined on.","Tags are slower to apply than refs. ","Multiple sources need to run and update before tagged data\n","will be resolved. ","If possible, stick to refs and only use tags when dealing with multiple sources.","Tagging a page","To tag a page, add a "," property to the page frontmatter. ","For example, the Product A page is tagged with \"in-stock\":","---\n","title: Product A\n","description: Mosaic Product A\n","layout: ProductDiscover\n","tags:\n"," - in-stock\n","data:\n"," name:\n"," $ref: '#/title'\n","---"," is always an array","Subscribing to a tag","To subscribe to a tag, use the "," property. ","For example, the Products page has subscribed to the "," property of pages tagged with ",".","---\n","title: Products\n","data:\n"," in-stock:\n"," $tag: in-stock#/data\n","---","A "," can provide a path to a specific piece of metadata on tagged pages, just like a ref.","Example","This page has subscribed to "," and "," tags and is displaying them using 2 Mosaic "," components.","Both the Product A and Product B pages are part of a different source than this page.","In Stock","Out of Stock"]},{"title":"UI Components","route":"/mosaic/author/ui-components","content":[""]},{"title":"Configure","route":"/mosaic/configure/index","content":["Mosaic is a tool which retrieves, formats and combines documentation pages from any number of different external sources (such as GitHub repositories, local disks or REST endpoints), into a single filesystem for you to use in your websites."]},{"title":"Content Fragment","route":"/mosaic/fragments/content-fragment","content":["Fragment Title","This is an example fragment of markdown content, being pulled from ","."]},{"title":"Fragments","route":"/mosaic/fragments/index","content":["This folder contains example fragments that are referenced and rendered in the Fragments docs page."]},{"title":"Tile A","route":"/mosaic/fragments/tile-a","content":[]},{"title":"Tile B","route":"/mosaic/fragments/tile-b","content":[]},{"title":"Create a Site","route":"/mosaic/getting-started/create-a-site","content":["In this guide you will learn how to generate and serve a Mosaic site.","Prerequisites","To begin setting up a Mosaic site, you need to have the following software installed:","Yarn v1","Node.js v18 or higher","Step 1: Generate a Mosaic site","Run the following command in your project directory to generate a new Mosaic site:","npx @jpmorganchase/mosaic-create-site create -o my-sample-site","This command creates a new Mosaic site in the my-sample-site directory.","Next, navigate to the site directory:","cd my-sample-site","Step 2: Serve the site","The example site you have generated comes preconfigured with two ","sources",": a remote repository and a local docs folder. ","Sources are used by Mosaic to pull content from disparate locations and merge them into a single virtual filesystem that can be used by a Mosaic site.","Set up Git credentials","If you want the site to read from remote repositories, you need to set up an environment variable to store your Git credentials. ","Follow these steps:","Open a terminal or command prompt.","Replace "," and "," in the following commands with your actual Git username and personal access token.","On Unix:","export MOSAIC_DOCS_CLONE_CREDENTIALS=\":\"","On Windows:","set MOSAIC_DOCS_CLONE_CREDENTIALS=\":\"","This sets the MOSAIC_DOCS_CLONE_CREDENTIALS environment variable with your Git credentials.","Serve command","Now you can serve your Mosaic site by running the following command:","yarn serve","Access your Mosaic site from a browser using the following URLs:","To browse the content from your local source: http://localhost:3000/local","To browse the content from the Mosaic Git repo source: http://localhost:3000/mosaic","That's it! ","Your Mosaic site is now up and running.","Next Steps:","Deploy your Mosaic site to AWS or Vercel for production use.","Create more pages to expand your site's content.","Configure your own sources in the mosaic.config.mjs file to pull content from different locations.","Theme your site"]},{"title":"Getting Started","route":"/mosaic/getting-started/index","content":["Getting Started with Mosaic","Follow our step-by-step guides to quickly create and deploy your first Mosaic site."]},{"title":"Publish a site to AWS","route":"/mosaic/getting-started/publish-site-to-aws","content":["Publish a site to AWS using S3 snapshots.","Step 1: Generate a Mosaic site","If you have already created your Mosaic site, skip ahead to step 2.","> npx @jpmorganchase/mosaic-create-site -o my-sample-site\n","> cd my-sample-site","Step 2: Create a Github repository","> git init\n","> git remote add origin git@github.com:username/my-sample-site.git\n","> git add .\n","> git commit -m \"initial commit\"\n","> git push origin main","Step 3: Generate a snapshot of content","Consider a snapshot as a directory of static content previously pulled from your content sources, which does not update itself.","> yarn gen:snapshot","Step 4: Configure environment for S3","> export MOSAIC_MODE=\"snapshot-s3\"\n","> export MOSAIC_S3_BUCKET=\"\"\n","> export MOSAIC_S3_REGION=\"\"\n","> export MOSAIC_S3_ACCESS_KEY_ID=\"\"\"\n","> export MOSAIC_S3_SECRET_ACCESS_KEY=\"\"\n","> yarn mosaic upload -S ./snapshots/latest","Step 5: Setup AWS","Switch to the ","AWS Amplify"," console and deploy your app as a SSR application by following the ","AWS docs",".","Setup an S3 bucket as per the ","AWS S3 docs",".","Step 7: Configure your AWS app","Add the environment vars to the hosted app via your console","MOSAIC_MODE=\"snapshot-s3\"\n","MOSAIC_S3_BUCKET=\"\"\n","MOSAIC_S3_REGION=\"\"\n","MOSAIC_S3_ACCESS_KEY_ID=\"\"\"\n","MOSAIC_S3_SECRET_ACCESS_KEY=\"\"","Add the following build settings","version: 1\n","frontend:\n"," phases:\n"," preBuild:\n"," commands:\n"," - yarn install\n"," - env | grep -e MOSAIC >> .env.production\n"," build:\n"," commands:\n"," - yarn run build\n"," artifacts:\n"," baseDirectory: .next\n"," files:\n"," - '**/*'\n"," cache:\n"," paths:\n"," - node_modules/**/*","Ensure the Node is set to 16","Step 8: Upload your snapshot","Upload your snapshot to S3 storage.","> yarn mosaic upload -S ./snapshots/latest"]},{"title":"Publish","route":"/mosaic/publish/index","content":["To create your first Mosaic site, we have created a command line generator that scaffolds a ","standard"," site.","A ","standard"," site offers","an out the box, working site, which showcases local and remote content sources","a minimal set of files that can be configured with your own components, themes, layouts, sources and plugins","an update path that enables you to update Mosaic, independently of your own configuration","Create your first site","Install the Mosaic create site script.","> yarn global add @jpmorganchase/mosaic-create-site","Create a directory for your site and run the "," script.","> mkdir mosaic-sample-site\n","> cd mosaic-sample-site\n","> mosaic-create-site -f .","Define the environment variable, which enables us to access your remote repo.","> export MOSAIC_DOCS_CLONE_CREDENTIALS=\"\"","The "," environment variable is composed of your git username and your PAT token.\n","Follow these ","docs"," to see how to create your own PAT token.","Your site is ready to run.","> yarn serve","In your browser load ","Congratulations, you have created your first Mosaic site."]},{"title":"Publish a site to AWS","route":"/mosaic/publish/publish-site-to-aws","content":["A Mosaic site is a ","Next.Js"," app.","To publish a Next.Js App to AWS, deploy your app as a SSR application by following the ","AWS docs",".","Once the basic app has been configured, add the Mosaic specifics.","Add the environment vars to the hosted app via the Amplify console","MOSAIC_MODE=\"snapshot-s3\"\n","MOSAIC_S3_BUCKET=\"\"\n","MOSAIC_S3_REGION=\"\"\n","MOSAIC_S3_ACCESS_KEY_ID=\"\"\"\n","MOSAIC_S3_SECRET_ACCESS_KEY=\"\"","Add the following build settings","version: 1\n","frontend:\n"," phases:\n"," preBuild:\n"," commands:\n"," - yarn install\n"," - env | grep -e MOSAIC >> .env.production\n"," build:\n"," commands:\n"," - yarn run build\n"," artifacts:\n"," baseDirectory: .next\n"," files:\n"," - '**/*'\n"," cache:\n"," paths:\n"," - node_modules/**/*","Ensure the Node is set to 16"]},{"title":"Publish a site to Vercel","route":"/mosaic/publish/publish-site-to-vercel","content":["A Mosaic site is a ","Next.Js"," app.","To publish a Next.Js App to Vercel, refer to the ","Vercel docs",".","Deployment","As the ","vercel platform"," hosts static content you will need to deploy a mosaic snapshot. ","There is no option to run mosaic in ","active mode",".","1. ","Update Config File","Add the following to the mosaic config file used by your site:"," deployment: { mode: 'snapshot-file', platform: 'vercel' }","2. ","Set Environment Variables","Set 2 ","environment variables"," in the vercel dashboard.","| Variable Name | Value |\n","| ------------------- | ----------------- |\n","| MOSAIC_MODE | snapshot-file |\n","| MOSAIC_SNAPSHOT_DIR | snapshots/latest. ","|","3. ","Run Build and Deploy","The "," command used by vercel must run "," followed by ","The "," command is needed to workaround an ","output file tracing"," problem.","Example:","yarn build && yarn deploy","Output File Tracing","Output File Tracing"," is a feature of Next.js that uses static analysis\n","to determine what files are needed to deploy a production version of an application.","Due to the architecture of mosaic, snapshot files can be ignored by this process and therefore excluded from the build artifacts deployed by vercel.","If you are deploying your site to the ","vercel platform"," then the mosaic site has a "," command that will update the nextjs output trace to include the snapshot files."]},{"title":"Test","route":"/mosaic/test/index","content":["Pages for e2e testing."]},{"title":"Admin","route":"/mosaic/configure/admin/index","content":["There are several admin urls exposed by Mosaic that provide an insight into how the filesystem has been configured and a way to remotely manage sources.","Endpoints","| Endpoint | Method | Description | Params |\n","| -------------------------- | ------ | -------------------------------------------- | ---------------------- |\n","| "," | GET | Returns the JSON from the Mosaic config file | n/a |\n","| "," | GET | Returns the entire mosaic filesystem as JSON | n/a |\n","| "," | GET | Returns a collection of active sources | n/a |\n","| "," | POST | Adds the source | definition & isPreview |\n","| "," | PUT | Stops the source with the provided name | name |\n","| "," | PUT | Restarts the source with the provided name | name |"]},{"title":"Detail Highlight","route":"/mosaic/configure/layouts/detail-highlight","content":["Layout: Detail Highlight","Initialize with "," in your page's frontmatter.","This layout is used to promote, share insights, and statistics for a line of business.","This layout should be used for pages with only one level of nesting, therefore, pagination is not eligible for this\n","layout.","Page geometry"]},{"title":"Detail Overview","route":"/mosaic/configure/layouts/detail-overview","content":["Layout: Detail Overview","Initialize with "," in your page's frontmatter.","This layout is used to present an overview of expected documentation, statisitics, and ability to\n","navigate to more documents.","Avoid making this page too long. ","If it gets too long, we recommend the ","Detail Technical"," template.","Page geometry","Other Layouts","Detail Highlight","Detail Technical","Landing","Product Discover","Product Preview","Filler content","Eiusmod veniam adipisicing est magna id sunt occaecat minim adipisicing ad do pariatur id aliqua.\n","Officia officia deserunt consequat ullamco irure. ","Excepteur deserunt esse occaecat ex aute. ","Duis do\n","do in incididunt cupidatat dolore veniam magna aliquip voluptate laborum. ","Non irure magna amet\n","ullamco culpa esse dolore nostrud. ","Id ea id ipsum incididunt do velit aliquip fugiat do non\n","consequat.","A sub heading","Deserunt sunt pariatur mollit dolor eiusmod. ","Anim sunt officia cillum anim. ","Laborum ullamco\n","consectetur elit dolore quis laborum. ","Eiusmod cillum amet veniam sunt Lorem reprehenderit commodo.\n","Cupidatat cillum ea consequat anim. ","Duis voluptate nulla veniam labore quis tempor.","Commodo reprehenderit excepteur amet aliquip cillum veniam ad. ","Ullamco proident deserunt laboris\n","duis laborum consequat laboris est eu enim nulla. ","Mollit velit consectetur ea aliqua consectetur\n","mollit eu ex deserunt. ","Aute excepteur exercitation esse proident excepteur Lorem. ","Quis cillum\n","occaecat sint voluptate incididunt ea ipsum incididunt duis sint magna magna fugiat.","Third-level heading","Ea do magna aute proident nulla cupidatat esse consectetur anim eu esse. ","Consectetur est voluptate\n","excepteur non dolore consequat fugiat deserunt. ","Est nostrud est ea irure reprehenderit commodo\n","nostrud nulla tempor ipsum tempor sit id exercitation. ","Sunt reprehenderit officia anim id quis\n","pariatur velit cillum incididunt officia sunt. ","Ullamco ipsum cillum minim deserunt eiusmod nostrud\n","irure et nulla laborum ipsum ipsum incididunt. ","Voluptate reprehenderit in occaecat ipsum nulla\n","excepteur excepteur mollit laboris id ad laborum do. ","Qui in laborum nostrud quis occaecat proident\n","ipsum tempor laborum consequat id ut velit occaecat.Aliquip quis qui ullamco ipsum exercitation\n","exercitation excepteur ea ex. ","Proident elit incididunt incididunt ad adipisicing quis deserunt sint\n","laboris deserunt ipsum culpa est. ","Id do ex duis Lorem exercitation amet reprehenderit. ","Voluptate qui\n","tempor qui sit minim sit qui ea id dolor excepteur. ","Laborum elit excepteur enim sunt consequat\n","officia cillum. ","Do ea occaecat ut voluptate ea proident duis minim ad pariatur dolore magna enim\n","duis. ","Sit aliqua aliqua ea mollit enim cupidatat proident incididunt. ","Eu dolore sit non incididunt.\n","Mollit reprehenderit sunt sunt cillum labore velit exercitation officia aliqua ea adipisicing do ea.\n","Commodo et fugiat velit dolore consectetur.","Amet dolore deserunt in ut amet officia exercitation sint excepteur voluptate proident tempor enim\n","est. ","Culpa proident tempor in voluptate laboris sunt consectetur sit cillum excepteur culpa enim\n","velit laboris. ","Pariatur elit amet nostrud tempor nostrud ea. ","Exercitation do aliquip nisi amet id.\n","Lorem labore incididunt sit sit veniam tempor do consectetur do culpa qui.","Id sint deserunt laborum mollit id excepteur","mollit excepteur labore labore dolor. ","Sit cupidatat nostrud ad consequat amet excepteur id sunt\n","labore adipisicing non irure. ","Fugiat exercitation laborum officia minim duis dolor do officia Lorem\n","cillum excepteur. ","Sint elit mollit duis sit ad commodo.","Cillum amet irure ut tempor tempor culpa dolore sint.","Lorem qui ipsum reprehenderit est incididunt duis exercitation ea duis fugiat. ","Consectetur enim id\n","sunt exercitation et dolore ea proident sunt excepteur fugiat dolor. ","Veniam proident dolore irure\n","incididunt deserunt pariatur quis. ","Incididunt ea elit deserunt occaecat eiusmod velit fugiat eiusmod\n","dolor eiusmod ullamco. ","Fugiat fugiat eiusmod occaecat nulla consequat pariatur.","Aliquip non cupidatat irure magna et fugiat sunt amet ex est excepteur irure quis. ","Non culpa magna\n","nisi enim eu nulla esse laborum amet ipsum. ","Eu consectetur labore do id occaecat adipisicing."]},{"title":"Detail Technical","route":"/mosaic/configure/layouts/detail-technical","content":["Layout: Detail Technical","Initialize with "," in your page's frontmatter.","This layout is used for longer, technical, detailed content about the product.","This layout shows less marketing-type visuals and more visual of diagrams, screenshots, and code\n","snippets.","This page can be short or very lengthy.","Page geometry","Other Layouts","Detail Highlight","Detail Overview","Landing","Product Discover","Product Preview","Filler content","Eiusmod veniam adipisicing est magna id sunt occaecat minim adipisicing ad do pariatur id aliqua.\n","Officia officia deserunt consequat ullamco irure. ","Excepteur deserunt esse occaecat ex aute. ","Duis do\n","do in incididunt cupidatat dolore veniam magna aliquip voluptate laborum. ","Non irure magna amet\n","ullamco culpa esse dolore nostrud. ","Id ea id ipsum incididunt do velit aliquip fugiat do non\n","consequat.","A sub heading","Deserunt sunt pariatur mollit dolor eiusmod. ","Anim sunt officia cillum anim. ","Laborum ullamco\n","consectetur elit dolore quis laborum. ","Eiusmod cillum amet veniam sunt Lorem reprehenderit commodo.\n","Cupidatat cillum ea consequat anim. ","Duis voluptate nulla veniam labore quis tempor.","Commodo reprehenderit excepteur amet aliquip cillum veniam ad. ","Ullamco proident deserunt laboris\n","duis laborum consequat laboris est eu enim nulla. ","Mollit velit consectetur ea aliqua consectetur\n","mollit eu ex deserunt. ","Aute excepteur exercitation esse proident excepteur Lorem. ","Quis cillum\n","occaecat sint voluptate incididunt ea ipsum incididunt duis sint magna magna fugiat.","Third-level heading","Ea do magna aute proident nulla cupidatat esse consectetur anim eu esse. ","Consectetur est voluptate\n","excepteur non dolore consequat fugiat deserunt. ","Est nostrud est ea irure reprehenderit commodo\n","nostrud nulla tempor ipsum tempor sit id exercitation. ","Sunt reprehenderit officia anim id quis\n","pariatur velit cillum incididunt officia sunt. ","Ullamco ipsum cillum minim deserunt eiusmod nostrud\n","irure et nulla laborum ipsum ipsum incididunt. ","Voluptate reprehenderit in occaecat ipsum nulla\n","excepteur excepteur mollit laboris id ad laborum do. ","Qui in laborum nostrud quis occaecat proident\n","ipsum tempor laborum consequat id ut velit occaecat.Aliquip quis qui ullamco ipsum exercitation\n","exercitation excepteur ea ex. ","Proident elit incididunt incididunt ad adipisicing quis deserunt sint\n","laboris deserunt ipsum culpa est. ","Id do ex duis Lorem exercitation amet reprehenderit. ","Voluptate qui\n","tempor qui sit minim sit qui ea id dolor excepteur. ","Laborum elit excepteur enim sunt consequat\n","officia cillum. ","Do ea occaecat ut voluptate ea proident duis minim ad pariatur dolore magna enim\n","duis. ","Sit aliqua aliqua ea mollit enim cupidatat proident incididunt. ","Eu dolore sit non incididunt.\n","Mollit reprehenderit sunt sunt cillum labore velit exercitation officia aliqua ea adipisicing do ea.\n","Commodo et fugiat velit dolore consectetur.","Amet dolore deserunt in ut amet officia exercitation sint excepteur voluptate proident tempor enim\n","est. ","Culpa proident tempor in voluptate laboris sunt consectetur sit cillum excepteur culpa enim\n","velit laboris. ","Pariatur elit amet nostrud tempor nostrud ea. ","Exercitation do aliquip nisi amet id.\n","Lorem labore incididunt sit sit veniam tempor do consectetur do culpa qui.","Id sint deserunt laborum mollit id excepteur","mollit excepteur labore labore dolor. ","Sit cupidatat nostrud ad consequat amet excepteur id sunt\n","labore adipisicing non irure. ","Fugiat exercitation laborum officia minim duis dolor do officia Lorem\n","cillum excepteur. ","Sint elit mollit duis sit ad commodo.","Cillum amet irure ut tempor tempor culpa dolore sint.","Lorem qui ipsum reprehenderit est incididunt duis exercitation ea duis fugiat. ","Consectetur enim id\n","sunt exercitation et dolore ea proident sunt excepteur fugiat dolor. ","Veniam proident dolore irure\n","incididunt deserunt pariatur quis. ","Incididunt ea elit deserunt occaecat eiusmod velit fugiat eiusmod\n","dolor eiusmod ullamco. ","Fugiat fugiat eiusmod occaecat nulla consequat pariatur.","Aliquip non cupidatat irure magna et fugiat sunt amet ex est excepteur irure quis. ","Non culpa magna\n","nisi enim eu nulla esse laborum amet ipsum. ","Eu consectetur labore do id occaecat adipisicing."]},{"title":"Layouts","route":"/mosaic/configure/layouts/index","content":["Todo..."]},{"title":"Landing Layout","route":"/mosaic/configure/layouts/landing","content":["Layout: Landing","Initialize with "," in your page's frontmatter.","Use this layout as a landing page to show large branded visuals, and high-level content about the\n","line of business.","Set the tone and voice for your LOB’s experience.","Use components that support your content with places to insert visuals, share any stats or an\n","introduction of LOB’s story.","Use heading styles to show the grouping of content of a section.","Page geometry"]},{"title":"Product Discover Layout","route":"/mosaic/configure/layouts/product-discover","content":["Layout: Product Discover","Initialize with "," in your page's frontmatter.","Use this layout to introduce a product feature with a description and large visual.","This layout is use for marketing and discovery content.","Page geometry"]},{"title":"Product Preview Layout","route":"/mosaic/configure/layouts/product-preview","content":["Layout: Product Preview","Initialize with "," in your page's frontmatter.","This layout has been used to introduce a product and showcase their suite of product offerings.","Use heading styles to show the grouping of content of a section.","Page geometry"]},{"title":"Active mode","route":"/mosaic/configure/modes/active","content":["In "," mode content can be ","pulled"," from heterogeneous data sources and normalized via plugins, to the configured components/theme.\n","As your content changes, the site will ","re-pull"," the content and update your site in real-time.","The standard generated site comes with 2 sources to demonstrate, how 'active' mode work.","a local source, which loads content from ","a remote source, which loads content from a ","sample Github repository","Configuring your content sources","All content is composed together within a ","namespace",".","A ","namespace"," is the scope for aggregated content, represented by the root path.\n","e.g Our sample docs are aggregated into a ","namespace"," called "," and served by the user journey ","Mosaic doc sources are defined by a file called ","Here is how that might look for a standard site.","Pull your local content","To tryout local content creation, add a file called","The load ","Each directory should contain an "," which is the default page, when a page is loaded without a path","Now create other pages and subdirectories and explore how Mosaic, builds your user-journeys from your fileset.","Pull your remote content","To tryout remote content creation, your standard site comes pre-configured to load our Mosaic sample docs.","Edit the "," and change your "," and "," to pull content from other repos."]},{"title":"Modes of operation","route":"/mosaic/configure/modes/index","content":["Mosaic can operate in 3 different modes","Active updates","In ","active"," mode, content updates in real-time.","active"," mode content is pulled at configured intervals in real-time, as defined by ",".","Read the ","active"," configuration docs.","Static content","Consider a snapshot as a directory of static content previously pulled from your content sources, which does not update itself.","In a snapshot mode, the snapshot does update itself.","File based snapshots","In "," mode, immutable snapshots of content are loaded at startup from the local file-system.","Read the ","snapshot-file"," configuration docs.","S3 based snapshots","In "," mode, snapshots of content are loaded at startup from a remote S3 bucket.","Read the ","snapshot-s3"," configuration docs."]},{"title":"Snapshot file mode","route":"/mosaic/configure/modes/snapshot-file","content":["In "," mode a local immutable snapshot can be loaded by the site. ","Typically, the snapshot and the site are\n","deployed together and upon startup the site can load the snapshot from the local file-system.","To use "," mode","export MOSAIC_MODE=\"snapshot-file\"\n","export MOSAIC_SNAPSHOT_DIR=\"./snapshot/latest\"","Generating a snapshot","To generate a snapshot, run","yarn gen:snapshot","Commit the snapshot to your Git repo and push the site+snapshot to your Git repo, within the same branch."]},{"title":"Snapshot AWS/S3 mode","route":"/mosaic/configure/modes/snapshot-s3","content":["In "," mode a snapshot can be loaded from a pre-configured AWS S3 bucket.","To use "," mode","> export MOSAIC_MODE=\"snapshot-s3\"\n","> MOSAIC_S3_BUCKET=\"\"\n","> MOSAIC_S3_REGION=\"\"\n","> MOSAIC_S3_ACCESS_KEY_ID=\"\"\n","> MOSAIC_S3_SECRET_ACCESS_KEY=\"\"","Generating a snapshot","To generate a snapshot, run","yarn gen:snapshot","Uploading a snapshot to S3","To upload a snapshot to S3, define the required environment variables and run","yarn mosaic upload -S "]},{"title":"$AliasPlugin","route":"/mosaic/configure/plugins/alias-plugin","content":["The "," is what powers the ","aliases"," feature of Mosaic.","It does this by scrapes "," from page metadata and also applies all aliases stored in ","Other plugins can use "," to apply new aliases, as long as they call it before this plugin has reaches the "," lifecycle event.","This plugin is added to the plugins collection by Mosaic itself so users do ","not"," need to include it in their own mosaic config file.","Priority","This plugin runs with a priority of -1 so it runs ","after"," most other plugins."]},{"title":"BreadcrumbsPlugin","route":"/mosaic/configure/plugins/breadcrumbs-plugin","content":["The "," is responsible for generating the data needed to show breadcrumbs navigation on pages. ","It then appends this data to a "," property in the page metadata.","Should a page already have a "," property in it's metadata then it is respected and not overwritten.","If a page has a "," property in it's metadata then this is used as the label for the breadcrumb, otherwise the page "," is used.","When the "," is traversing up directories, it needs to know what page in the directory represents the ","breadcrumb"," for that directory. ","It is recommended to use the "," page for this but the page to use is a configurable option of the breadcrumbs plugin.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| ------------- | ------------------------------------------------ |\n","| indexPageName | The page name to use for \"directory\" breadcrumbs |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the breadcrumbs plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/BreadcrumbsPlugin',\n"," options: {\n"," indexPageName: 'index.mdx'\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"BrokenLinksPlugin","route":"/mosaic/configure/plugins/broken-links-plugin","content":["The "," will identify any broken links in pages Mosaic has pulled into it's filesystem by making use of the ","check-links"," package.","It can identify broken links between the pages themselves and external links that pages link out to.","What this plugin is really checking is \"liveness\" of a link.","alive if the URL is reachable (2XX status code)","dead if the URL is not reachable","invalid if the URL was parsed as invalid or used an unsupported protocol","Links may be \"alive\", but the ","content"," of the linked page may not be what you want so continue\n","to check links show what you expect.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| ------------- | --------------------------------------------------------------------------------------------------------------------------------- |\n","| baseUrl | This is used to calculate the full url for links between pages. ","It should be the url Mosaic is running on |\n","| proxyEndpoint | If you are behind a corporate proxy, external link checking will not work unless you specify the proxy endpoint using this option |","Adding to Mosaic","This plugin is ","not"," included in the mosaic config shipped by the Mosaic standard generator so it must be added manually to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/BrokenLinksPlugin',\n"," priority: -1,\n"," // Exclude this plugin in builds\n"," runTimeOnly: true,\n"," options: {\n"," baseUrl: process.env.MOSAIC_ACTIVE_MODE_URL || 'http://localhost:8080',\n"," proxyEndpoint: 'http://some-proxy-url'\n"," }\n"," }\n"," // other plugins\n","];","This plugin needs to be a "," plugin because it needs Mosaic to be running in order to\n","make requests to all of the page links.","Example Output","When a link is found to be broken, you will see the following output in the console:","@jpmorganchase/mosaic-site:serve: 8080 [Mosaic] Broken links found in /local/docs/publish-site-to-vercel.mdx\n","@jpmorganchase/mosaic-site:serve: 8080 Link to https://nextjs.org/davie is dead {\n","@jpmorganchase/mosaic-site:serve: 8080 type: 'link',\n","@jpmorganchase/mosaic-site:serve: 8080 title: null,\n","@jpmorganchase/mosaic-site:serve: 8080 url: 'https://nextjs.org/davie',\n","@jpmorganchase/mosaic-site:serve: 8080 children: [ { type: 'text', value: 'Next.Js', position: [Object] } ],\n","@jpmorganchase/mosaic-site:serve: 8080 position: {\n","@jpmorganchase/mosaic-site:serve: 8080 start: { line: 4, column: 20, offset: 36 },\n","@jpmorganchase/mosaic-site:serve: 8080 end: { line: 4, column: 55, offset: 71 }\n","@jpmorganchase/mosaic-site:serve: 8080 }\n","@jpmorganchase/mosaic-site:serve: 8080 }"]},{"title":"$CodeModPlugin","route":"/mosaic/configure/plugins/codemod-plugin","content":["Todo"]},{"title":"DocumentAssetsPlugin","route":"/mosaic/configure/plugins/document-assets-plugin","content":["The "," is responsible for copying assets from a document sub-directory to the public folder of your site. ","This is particularly useful for co-locating images within your document structure and referencing them from documents using relative paths.","Co-locating Images","A common use case is to store images within the same directory structure as your documents. ","This allows you to reference images using relative paths.","For example, to load an image (",") from a sub-directory called ",", relative to the document's path, you can use the following Markdown:","![","alt text](./images/mosaic.jpg)","This will render the image as follows:\n","Centralized Image Directory","Alternatively, if you prefer to store all your images in a common parent directory, you can reference them using a relative path that navigates up the directory structure.","For example, to load an image from a common parent directory, you can use:","![","alt text](..","/../images/mosaic.jpg)","Handling Absolute Paths and URLs","The plugin ignores image paths that start with a leading slash (/) or are fully qualified URLs. ","This ensures that only relative paths are processed and copied to the public folder.","![","alt text](/images/mosaic.jpg)\n","![","alt text](https://www.saltdesignsystem.com/img/hero_image.svg)","Priority","This plugin runs with a priority of -1 so it runs ","after"," most other plugins.","Options","| Property | Description | Default |\n","| ------------ | ---------------------------------------------------------------------------------- | ------------- |\n","| srcDir | The path where pages reside ","after"," cloning or when running locally | './docs' |\n","| outputDir | There path to your site's public images directory where you want to put the images | './public' |\n","| assetSubDirs | An array of subdirectory globs that could contain assets | ['**/images'] |\n","| imagesPrefix | The prefix that is added to all new paths | '/images' |","Adding to Mosaic","This plugin is ","not"," included in the mosaic config shipped by the Mosaic standard generator so it must be added manually to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/DocumentAssetsPlugin',\n"," priority: -1,\n"," options: {\n"," srcDir: `..","/../docs`,\n"," outputDir: './public/images/mosaic',\n"," assetSubDirs: ['**/images'],\n"," imagesPrefix: '/images'\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"Plugins","route":"/mosaic/configure/plugins/index","content":["Mosaic Plugins are ","lifecycle-based"," hooks that are called on ","every"," source at different stages. ","You will never need to invoke a lifecycle method directly as their execution is managed by a plugin runner.","Plugins enable Mosaic to have a lightweight and flexible, modular architecture by encapsulating features and functionality as plugins.","Installation","Configuration","Plugins are added to the "," collection of the mosaic config file. ","Like ","sources",", plugins have an options property that can be used to provide plugin specific configuration.","| Property | Description | Required |\n","| --------------- | -------------------------------------------------------------------------- | -------- |\n","| modulePath | The path to the installed plugin module | Yes |\n","| disabled | Exclude this plugin completely. ","Defaults to false | No |\n","| runtimeOnly | Exclude this plugin when generating a snapshot. ","Defaults to false | No |\n","| previewDisabled | Exclude this plugin for \"preview\" sources | No |\n","| allowMultiple | Allow multiple instances of this plugin to run. ","| No |\n","| priority | The importance of this plugin. ","Plugins with the highest priority run first | No |\n","| options | Collection of other configuration values | No |"," plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/PagesWithoutFileExtPlugin',\n"," options: {},\n"," priority: 1\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: {}\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/ReadingTimePlugin',\n"," options: {}\n"," }\n","],","There is no need to import the plugin module directly. ","As long as the plugin is installed, Mosaic\n","will be able to import it using the built-in plugin loader.","Default Plugins","The following plugins are always included by Mosaic, regardless of whether they are present in the plugins collection of the Mosaic config file:","$TagPlugin","$AliasPlugin","$CodeModPlugin","$RefPlugin","Plugin errors","Should a plugin fail, the failure will ","not"," cause a source to close or for any other plugin to not run.","Instead plugin errors are tracked by Mosaic and can be viewed in the "," property available on each source listed by the list sources ","admin API",".","Plugin errors will be split by lifecycle event and only the lifecycle events used by the loaded plugins used will be shown.","Multiple Instances","By default, Mosaic will only run one instance of a plugin.","It may be the case that you wish to run the same plugin twice with a slightly different config. ","To do this, you must have 2 instances listed in the plugins collection and they ","both"," must set the "," option to ",".","For example, the config below runs the ","SidebarPlugin"," twice:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: { rootDirGlob: 'products/product-a' },\n"," allowMultiple: true\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: { rootDirGlob: '*/!(","product-a)/*' },\n"," allowMultiple: true\n"," }\n"," // other plugins\n","];"]},{"title":"LazyPagePlugin","route":"/mosaic/configure/plugins/lazy-page-plugin","content":["The "," attempts to reduce the size of the Mosaic filesystem in memory by moving page metadata and content to disk.","It then adds a hook, so that when a page is requested the data is loaded from disk and combined with what is already in the Mosaic filesystem.","It must be the very last to run so that it can strip off metadata and content after other plugins\n","have finished with them.","Priority","This plugin runs with a priority of -2. ","Needs to be the last to run for biggest impact.","Options","| Property | Description |\n","| -------- | ------------------------------------------------------------------------------ |\n","| cacheDir | The directory to store the cache. ","Defaults to "," |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/LazyPagePlugin',\n"," // This plugin must be the very last to run, so it can strip off metadata and content after the other\n"," // plugins are done with them\n"," priority: -2,\n"," // Exclude this plugin in builds\n"," runTimeOnly: true,\n"," options: {\n"," cacheDir: '.tmp/.pull-docs-last-page-plugin-cache'\n"," }\n"," }\n"," // other plugins\n","];","This plugin needs to be a "," plugin because the goal is to reduce the in-memory\n","filesystem size."]},{"title":"PagesWithoutFileExtPlugin","route":"/mosaic/configure/plugins/pages-wthout-extensions-plugin","content":["The "," plugin creates ","aliases"," without the file extension for every page in the Mosaic filesystem.\n","This allows pages to be retrieved from the filesystem without specifying the extension e.g., ",".","The plugin also modifies the "," metadata property of a page to point to the shorter alias.","Priority","This plugin runs with a priority of 1. ","It must run after the ","$AliasPlugin",".","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/PagesWithoutFileExtPlugin',\n"," options: {},\n"," priority: 1\n"," }\n"," // other plugins\n","];"]},{"title":"PublicAssetsPlugin","route":"/mosaic/configure/plugins/public-assets-plugin","content":["The "," is responsible for finding \"assets\" in the Mosaic filesystem and copying them to another directory.","Typical use-case is for copying "," and "," to the public directory of a Next.js site as these are considered ","static assets"," for Next.js.","Priority","This plugin runs with a priority of -1 so it runs ","after"," most other plugins.","Options","| Property | Description |\n","| --------- | ----------------------------------------------------------- |\n","| outputDir | The directory to copy the assets to. ","Defaults to "," |\n","| assets | A collection of filenames to copy to the outputDir |","Adding to Mosaic","This plugin is ","not"," included in the mosaic config shipped by the Mosaic standard generator so it must be added manually to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/PublicAssetsPlugin',\n"," priority: -1,\n"," options: {\n"," outputDir: './public',\n"," assets: ['sitemap.xml', 'search-data.json']\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"ReadingTimePlugin","route":"/mosaic/configure/plugins/reading-time-plugin","content":["The "," generates an estimation of how long a page written in MDX will take to read and adds the "," property to the metadata of a page.","Priority","This plugin runs with no special priority.","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/ReadingTimePlugin',\n"," options: {}\n"," }\n"," // other plugins\n","];"]},{"title":"$RefPlugin","route":"/mosaic/configure/plugins/ref-plugin","content":["The "," powers the ","refs"," feature of Mosaic.","The plugin scrapes "," properties from page metadata and also applies all refs stored in ",".","Other plugins can use "," to apply new refs, as long as they call it before this plugin has reaches ",".","Priority","This plugin runs with a priority of -1 so it runs ","after"," most other plugins."]},{"title":"SearchIndexPlugin","route":"/mosaic/configure/plugins/search-index-plugin","content":["The "," is responsible for generating the search index and configuration information for ","Fuse.js"," which is the matching engine powering the client-side search functionality of Mosaic sites.","It outputs 3 files:","Full Search Index - ","Condensed Search Index - ","Search Configuration - ","Full Search Index","On a Mosaic site, the full index is fetched after a page has loaded, thus removing the chance of a huge index slowing down first-load.","Practically, the full index should load in the background before a user searches for something, but should a search be initiated before that (e.g., slow-internet) then the condensed version of the search index is available.","Condensed Search Data","Search Index plugin creates a \"condensed\" version of the search index that only includes the "," and "," for each page. ","This is the \"Minimum Viable Index\" to provide somewhat useable search results client-side while the main search index is loaded in the background.","Search Configuration","Any ","options"," that need to be passed to Fuse.js.","Search relevancy configuration","| Property | Description |\n","| ---------------- | ----------------------------------------------------- |\n","| includeScore | https://www.fusejs.io/api/options.html#includescore |\n","| includeMatches | https://www.fusejs.io/api/options.html#includematches |\n","| maxPatternLength | TODO |\n","| ignoreLocation | https://www.fusejs.io/api/options.html#ignorelocation |\n","| threshold | https://www.fusejs.io/api/options.html#threshold |\n","| keys | https://www.fusejs.io/api/options.html#keys |","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| ------------- | --------------------------------------------------- |\n","| maxLineLength | TODO |\n","| maxLineCount | TODO |\n","| keys | https://www.fusejs.io/api/options.html#keys |\n","| relevancy | ","search relevancy"," |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SearchIndexPlugin',\n"," previewDisabled: true,\n"," options: { maxLineLength: 240, maxLineCount: 240 }\n"," }\n"," // other plugins\n","];","It's usually a good idea to mark this plugin as disabled for preview sources otherwise the pages\n","from the preview will appear in search results."]},{"title":"SharedConfigPlugin","route":"/mosaic/configure/plugins/shared-config-plugin","content":["The "," crawls the page hierarchy to find the closest "," metadata from any parent index's page metadata. ","It then exports a JSON file into each directory with the merged config for that level.","Shared config is typically the place where the following is configured for a Mosaic site:","App Header configuration including site name and main navigation","Footer information","Help links for the left sidebar area","Namespace Shared Configs","Consider 2 sources the share the same ","namespace"," \"product-docs\":","Source A - multiple product directories and main product index page. ","The index page specifies "," metadata.","Source B - pages relevant to a single product. ","Index page does ","not"," have any "," metadata.","Let's also assume that the pages from Source B would also naturally \"fit\" within the pages of Source B (e.g. inside a products directory).","In this scenario, the "," will attempt to copy the shared config file from Source A into the root directory of Source B allowing the Source B pages to use the Source A "," as though it were a product sourced directly from Source A.","Priority","This plugin runs with a priority of 3.","Options","| Property | Description |\n","| -------- | ---------------------------------------------- |\n","| filename | the name of the JSON file output by the plugin |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SharedConfigPlugin',\n"," options: {\n"," filename: 'shared-config.json'\n"," },\n"," priority: 3\n"," }\n"," // other plugins\n","];"]},{"title":"SidebarPlugin","route":"/mosaic/configure/plugins/sidebar-plugin","content":["The "," generates the necessary page metadata needed for the vertical navigation shown on some Mosaic pages.","The output from the plugin is added to a "," metadata property of the page.","Configuration","The "," is used to determine the \"root\" directories of the sidebar. ","So for example:"," - generate sidebar data for the pages that are 3 directories deep in the filesystem hierarchy"," - same as above but ignore the product-a directory"," - generate a sidebar just for product-b in the products directory","To rearrange pages in the sidebar or to apply a different label to a page you can ","configure the sidebar"," using page frontmatter.","Priority","This plugin runs with a priority of 3.","Options","| Property | Description |\n","| ----------- | ----------------------------------------------------------------------------- |\n","| filename | filename of the sidebar json, linked to each related page via ref |\n","| rootDirGlob | Glob pattern for matching directories which should be the root of the sidebar |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SidebarPlugin',\n"," options: { rootDirGlob: '*/*/*' }\n"," }\n"," // other plugins\n","];"]},{"title":"SiteMapPlugin","route":"/mosaic/configure/plugins/site-map-plugin","content":["The "," generates a ","sitemap"," using the pages in the Mosaic filesystem that adheres to the ","sitemaps XML schema",".","The output of the plugin is a file is named ",".","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| -------- | --------------------------------------------------------------------------------------------------- |\n","| siteUrl | The site URL. ","Used as the prefix for loc entries in the sitemap as these must start with a protocol |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/SiteMapPlugin',\n"," previewDisabled: true,\n"," options: { siteUrl: process.env.SITE_URL || 'http://localhost:3000' }\n"," }\n"," // other plugins\n","];","Visualiser","To visualise your sitemap you can combine this plugin with the "," component provided by ",".","Rendering the "," component will render a tree view of your sitemap.","","Have a look at this site's pages, ","here","."]},{"title":"$TagPlugin","route":"/mosaic/configure/plugins/tag-plugin","content":["The "," powers the tags feature of Mosaic.","This plugin scrapes "," from page metadata and also applies all aliases stored in ",".","Tags ultimately resolve down into ","refs",", but are different from normal refs, in that they are applied to the\n","union filesystem (all merged filesystems), not to the individual source filesystem that they were defined on. ","This can be thought of as a ","global ref",".","Other plugins can use "," and modify the "," property, to apply new global refs, as long as\n","they do so before this plugin has reaches ",".","Priority","This plugin runs with no special priority but it must run before both the "," and the ",".","Example use case - Products Page","Let's assume there is a products page on your site and each product is shown as a tile. ","The information for each product tile is sourced from multiple Mosaic sources. ","How can tags be used to reference the product data needed for each tile on the products page?","The product page should add a "," metadata prop to its frontmatter:","---\n","title: Products\n","description: Product index\n","layout: ProductPreview\n","data:\n"," items:\n"," $tag: product#/data\n","---","The tag shown in the example above is saying populate "," of the Products index page with the "," metadata property of pages tagged with ",".","A tagged product page needs to have a "," property which is a collection of tags and one of these needs to be ",". ","This will allow the "," to correctly associate the product page to the product index page. ","It will also need a "," property because thats the property our product index page wants to use for the tiles.","---\n","title: Product A\n","description: My Product description\n","layout: ProductDiscover\n","tags:\n"," - product\n","data:\n"," name:\n"," $ref: '#/title'\n"," date: 2023/02/07\n"," action: Product Overview\n"," description:\n"," $ref: '#/description'\n"," link: /products/a/index\n","---","---\n","title: Product B\n","description: My Product description\n","layout: ProductDiscover\n","tags:\n"," - product\n","data:\n"," name:\n"," $ref: '#/title'\n"," date: 2023/02/07\n"," action: Product Overview\n"," description:\n"," $ref: '#/description'\n"," link: /products/b/index\n","---"]},{"title":"TableOfContentsPlugin","route":"/mosaic/configure/plugins/toc-plugin","content":["The "," generates a Table of Contents for each page in the Mosaic filesystem using the headings on the page.","Heading ranks are used to determine which page headings should be included in the Table of Contents:","| Heading Element (markdown syntax) | Rank |\n","| --------------------------------- | ---- |\n","| h1 (#) | 1 |\n","| h2 (##) | 2 |\n","| h3 (###) | 3 |\n","| h4 (####) | 4 |\n","| h5 (#####) | 5 |\n","| h6 (######) | 6 |","The plugin output is added to a "," metadata property of a page.","Priority","This plugin runs with no special priority.","Options","| Property | Description |\n","| -------- | ----------------------------- |\n","| minRank | The minimum page heading rank |\n","| maxRank | The maximum page heading rank |","Adding to Mosaic","This plugin is included in the mosaic config shipped by the Mosaic standard generator. ","So if you use the below import in your "," file then the plugin is included already:","import mosaicConfig from '@jpmorganchase/mosaic-standard-generator/dist/fs.config.js';","To add it yourself, add the following to the "," collection:","plugins: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-plugins/TableOfContentsPlugin',\n"," options: {\n"," minRank: 2,\n"," maxRank: 3\n"," }\n"," }\n"," // other plugins\n","];"]},{"title":"Git Repo Source","route":"/mosaic/configure/sources/git-repo-source","content":["The Git Repo Source is used to pull content from a remote git repository e.g. github.","Installation","Credentials and Access tokens","To successfully clone the git repo, the source definition must include credentials that have sufficient permissions to clone the repository.","We recommend storing a ","personal access token"," in an environment variable and using the environment variable in the source definition.","This keeps credentials out of code where they may be accidentally exposed to third parties.","Example","export MOSAIC_DOCS_CLONE_CREDENTIALS=\":\",","Configuration","| Property | Description | Required |\n","| ------------------- | -------------------------------------------------------------------------------- | -------- |\n","| modulePath | The path to the installed module (@jpmorganchase/mosaic-source-git-repo) | Yes |\n","| namespace | The scope for this source. ","| Yes |\n","| disabled | When true, content from this source is not used | No |\n","| options.credentials | Collection of URLS to make requests | Yes |\n","| options.prefixDir | The root path used in the content URL | Yes |\n","| options.subfolder | The name of the folder within the cloned repo containing the docs | Yes |\n","| options.repo | The repo URL | Yes |\n","| options.branch | The branch or tag to clone | Yes |\n","| options.extensions | Collection of file extensions that the source will look for inside the subfolder | Yes |\n","| options.remote | The name of the git remote to use. ","Defaults to origin. ","| Yes |","Example Git Repo Source Definition","\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-git-repo',\n"," namespace: 'mosaic',\n"," options: {\n"," credentials: process.env.MOSAIC_DOCS_CLONE_CREDENTIALS,\n"," prefixDir: 'mosaic',\n"," subfolder: 'docs',\n"," repo: 'https://github.com/jpmorganchase/mosaic.git',\n"," branch: 'main',\n"," extensions: ['.mdx'],\n"," remote: 'origin'\n"," }\n"," }\n"]},{"title":"HTTP Source","route":"/mosaic/configure/sources/http-source","content":["The HTTP Source is used to pull content over HTTP.","Multiple endpoints can be specified and the source will combine and transform the response from each into a single collection of pages.","Installation","Configuration","| Property | Description | Required |\n","| ------------------------------------------ | ----------------------------------------------------------------------------- | -------- |\n","| modulePath | The path to the installed module (@jpmorganchase/mosaic-source-http) | Yes |\n","| namespace | The scope for this source | Yes |\n","| disabled | When true, content from this source is not used | No |\n","| options.endpoints | Collection of URLS to make requests | Yes |\n","| options.prefixDir | The root path used in the content URL | Yes |\n","| options.transformResponseToPagesModulePath | The path of the module used to transform endpoint responses into Mosaic pages | Yes |\n","| options.checkIntervalMins | Number of minutes to wait between requests. ","Defaults to 5 minutes | No |\n","| options.initialDelayMs | Number of milliseconds to wait for making initial request. ","Defaults to 1000 | No |","Example HTTP Source Definition"," {\n"," modulePath: '@jpmorganchase/mosaic-source-http',\n"," namespace: 'my-namespace',\n"," options: {\n"," prefixDir: 'docs',\n"," endpoints: [\n"," 'https://api.data.com/blah',\n"," 'https://api.data.com/hello'\n"," ],\n"," transformResponseToPagesModulePath: '@scope/transformer-package'\n"," }\n"," }"]},{"title":"Sources","route":"/mosaic/configure/sources/index","content":["Sources are what Mosaic uses to pull content from disparate locations and merge into a single virtual filesystem that can be used by a Mosaic Site.","Depending on the ","mode"," used, sources can update periodically ensuring that new content is made available automatically.","Source Definitions","Source Definitions are specified in the "," collection of a mosaic config file.","Each source uses a ","zod schema"," to validate the provided JSON to ensure that all required information for the source to pull content has been provided.","A source definition at a minimum needs to provide the module path of the source and the ","namespace"," that it will use. ","A namespace is not unique across sources though it is common that each source has a different namespace.","Lastly, the options field can be used as a bucket for configuration values needed to configure the source e.g. credentials.","Users are free to add any property as a source option but please read the ","gotchas","\n","first regarding the allowed ","values",".","Example Local Folder Source Definition"," /**\n"," * Demonstrates a local file-system source, in this case a relative path to where the\n"," * site was generated.\n"," * Access from your browser as http://localhost:3000/local\n"," */\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-local-folder',\n"," namespace: 'local', // each site has it's own namespace, think of this as your content's uid\n"," options: {\n"," rootDir: '..","/../docs', // relative path to content\n"," prefixDir: 'local', // root path used for namespace\n"," extensions: ['.mdx'] // extensions of content which should be pulled\n"," }\n"," }","Source Namespace","A Source Namespace is a scoping mechanism for Mosaic sources used to filter the content loaded by Mosaic. ","By default all sources specified in the mosaic config file are loaded.","sources: [\n"," {\n"," namespace: 'my-namespace',\n"," modulePath: '@jpmorganchase/mosaic-source-local-folder'\n"," }\n","];","The following command will ensure mosaic only loads sources with the "," scope.","yarn mosaic serve -c ''./mosaic.config.mjs' -p 8080 --scope \"local\"","Source Schedules","Source schedules define how often sources pull in content that exists remotely and if a failed source is retried. ","More information can be found ","here","Source Types","Out of the box, Mosaic provides 3 source \"types\":","Local Folder","Git Repo Source","HTTP Source","Sources must expose an observable interface so it is possible to compose sources together e.g. the Git Repo source uses the Local Folder source internally to watch the cloned folder for changes.","Watching for Updates","When running in ","active mode",", Mosaic will watch for any changes to the source content and if a change is detected, will initiate a pull of that new content.","How often to check for updates and how updates are triggered are a matter for the source to handle. ","Mosaic simply responds when a source emits new content.","Source Worker Thread","Sources are executed inside their own worker thread to ensure that the main thread is not overloaded. ","It is here that a local virtual filesystem for the source is created and where several of the ","Plugin Lifecycle"," events are triggered.","Gotchas","A service worker thread uses ","postMessage"," to communicate with the main thread and vice-versa.","This is important because it limits what values can be provided in the source definition to those that can be processed by the ","Structured Clone Algorithm","."]},{"title":"Local Folder Source","route":"/mosaic/configure/sources/local-folder-source","content":["The Local Folder Source is used to pull content from a folder located on the same machine as Mosaic is running.","It is common to use this source when running mosaic locally.","Installation","Configuration","| Property | Description | Required |\n","| ------------------ | ------------------------------------------------------------------------------ | -------- |\n","| modulePath | The path to the installed module (@jpmorganchase/mosaic-source-local-folder) | Yes |\n","| namespace | The scope for this source | Yes |\n","| disabled | When true, content from this source is not used | No |\n","| options.rootDir | The top level directory content will be pulled from | Yes |\n","| options.prefixDir | The root path used in the content URL | Yes |\n","| options.extensions | Collection of file extensions that the source will look for inside the rootDir | Yes |","Example Local Folder Source Definition","{\n"," modulePath: '@jpmorganchase/mosaic-source-local-folder',\n"," namespace: 'local', // each site has it's own namespace, think of this as your content's uid\n"," options: {\n"," rootDir: '..","/../docs', // relative path to content\n"," prefixDir: 'local', // root path used for namespace\n"," extensions: ['.mdx'] // extensions of content which should be pulled\n"," }\n","}","This source will look for content with the \".mdx\" extension in a \"docs\" directory 2 levels up from the Mosaic working directory. ","That content is included in the \"local\" namespace and available from a route that is prefixed with \"local\".","So if you had a file, "," then you would be able to view it at ","."]},{"title":"Source Schedules","route":"/mosaic/configure/sources/schedules","content":["A source schedule defines how often a source initiates a content pull and what to do when there is a failure.","A schedule can be specified for each source in the source definition, but should a source not provide a schedule it will inherit the \"global\" schedule.","Configuration","| Property | Description | Required | Default |\n","| ----------------- | -------------------------------------------------------------------------------------- | -------- | ------- |\n","| checkIntervalMins | The length of time in minutes before triggering a content refresh | Yes | 30 mins |\n","| initialDelayMs | Startup delay for the source. ","| Yes | 1000 ms |\n","| retryEnabled | When true, failures will trigger another content pull | No | true |\n","| retryDelayMins | The interval between retries. ","This will rise exponentially on every failure. ","| No | 5 |\n","| maxRetries | Maximum number of retry attempts | No | 100 |\n","| resetOnSuccess | If true, when a source recovers and emits pages it's retry counter is returned to zero | No | true |","Global Schedule","The global schedule applies to all sources that do ","not"," provide their own schedule. ","It can be configured as a top-level property of the Mosaic config file."," schedule: {\n"," checkIntervalMins: 60,\n"," initialDelayMs: 1000,\n"," retryDelayMins: 15,\n"," maxRetries: 20\n"," }","Example","Given the config file below:"," schedule: {\n"," checkIntervalMins: 30,\n"," initialDelayMs: 1000,\n"," retryDelayMins: 5,\n"," maxRetries: 10\n"," },\n"," sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-git-repo',\n"," namespace: 'sourceA',\n"," options: {\n"," credentials: 'credentials',\n"," prefixDir: 'sourceA',\n"," subfolder: 'docs',\n"," repo: 'source-a-repo-url',\n"," branch: 'develop',\n"," extensions: ['.mdx'],\n"," remote: 'origin'\n"," }\n"," },\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-git-repo',\n"," namespace: 'sourceB',\n"," schedule:{\n"," checkIntervalMins: 60,\n"," initialDelayMs: 5000,\n"," retryDelayMins: 30,\n"," maxRetries: 50\n"," }\n"," options: {\n"," credentials: 'credentials',\n"," prefixDir: 'sourceB',\n"," subfolder: 'docs',\n"," repo: 'source-b-repo-url',\n"," branch: 'develop',\n"," extensions: ['.mdx'],\n"," remote: 'origin'\n"," }\n"," }\n"," ]","Source A will inherit the global schedule so it will:","Start after a 1 second delay","Pull content every 30 minutes","Retry a failed content pull after an initial 5 minute delay","Retry 10 times and if still unsuccessful, closing","Source B has its own schedule so it will:","Start after a 5 second delay","Pull content every 60 minutes","Retry a failed content pull after an initial 30 minute delay","Retry 50 times and if still unsuccessful, closing","Retry Strategy","The retry strategy that Mosaic employs is ","Exponential Backoff",". ","This is a common strategy for networking applications that aims to prevent retries from causing more harm than good.","For example, given a source schedule that has a 1 minute retry delay and will retry a maximum of 3 times then the total time spent retrying is 7 minutes:","1 minute delay then 1st retry","2 minute delay then 2nd retry","4 minute delay then 3rd (and final) retry","Total delay: 1 + 2 + 4 = 7 minutes","As you can see, the delay between retries is growing exponentially giving the content source more time to recover after each retry."]},{"title":"Figma Source","route":"/mosaic/configure/sources/source-figma","content":["The Figma source is used to pull individual design patterns from Figma projects.\n","The source subscribes to Figma project groups, then extracts from project files, tagged patterns.","A Figma pattern tag is defined within a Figma project's ",".","For each retrieved/tagged pattern, a page is created in the Mosaic file-system.\n","Additional Mosaic metadata can be added, to each created page, using ","\n","For instance we could add metadata which defines which product owns a particular pattern and then\n","group patterns based on owner.","Installation","Configuration","The Figma source is an "," and shares the same base configuration.\n","The "," prop has a default transformer which creates a page for each matching Story.","| Property | Description | Required |\n","| ---------- | --------------------------------- | -------- |\n","| prefixDir | path to store figma patterns | Yes |\n","| figmaToken | figma access token | Yes |\n","| projects | array of projects to subscribe to | Yes |\n","| endpoints | figma endpoints | Yes |","Each project is configured from the "," array.","| Property | Description | Required |\n","| ------------- | --------------------------------------------- | -------- |\n","| id | numerical id of the subscribed project group | Yes |\n","| meta | metadata to add to each of the projects pages | Yes |\n","| patternPrefix | prefix to add to all pages created | Yes |"," defined the Figma REST API endpoints.","| Property | Description | Required |\n","| ----------------- | --------------------------------------------------------- | -------- |\n","| getProject | url to return a list of projects within the project group | Yes |\n","| getFile | url to return a project file | Yes |\n","| generateThumbnail | url to generate a thumbnail for the shared Figma node | Yes |","Example Figma Source Definition","sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-figma',\n"," namespace: 'some-namespace',\n"," options: {\n"," proxyEndpoint: 'http://path/to/optional/proxy',\n"," prefixDir: '/some/path/to/where/you/store/pattern/pages',\n"," figmaToken: process.env.FIGMA_TOKEN,\n"," projects: [{\n"," id: 99999,\n"," patternPrefix: 'yourPrefix',\n"," meta: {\n"," tags: [ 'some-tag'],\n"," data: {\n"," owner: 'some-owner'\n"," }\n"," }\n"," }],\n"," endpoints: {\n"," getFile: 'https://api.figma.com/v1/files/:file_id?","plugin_data=shared',\n"," getProject: 'https://api.figma.com/v1/projects/:project_id/files',\n"," generateThumbnail: 'https://api.figma.com/v1/images/:project_id?","ids=:node_id'\n"," }\n"," }\n"," }\n","]"]},{"title":"Readme Source","route":"/mosaic/configure/sources/source-readme","content":["The Readme source is used to pull individual "," text files from repositories.\n","This is a lighter weight version of a Git repository resource, which just pulls a single file,\n","rather the whole contents of the repo.","For each retrieved readme, a page is created in the Mosaic file-system.\n","Additional Mosaic metadata can be added, to each created page, using ","\n","For instance we could add metadata which defines which product owns a particular "," and then\n","group this page, with others, based on owner.","Installation","Configuration","The Readme source is an "," and shares the same base configuration.","| Property | Description | Required |\n","| ----------- | --------------------- | -------- |\n","| accessToken | request access token | Yes |\n","| prefixDir | path to store pages | Yes |\n","| readme | array of readme files | Yes |","Each readme is configured from the "," array.","| Property | Description | Required |\n","| --------------- | ---------------------------- | -------- |\n","| contentTemplate | template for content of page | No |\n","| name | page name used in route | Yes |\n","| readmeUrl | url of readme | Yes |\n","| meta | metadata for page | Yes |","Example Readme Source Definition"," sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-readme',\n"," namespace: 'your-namespace',\n"," schedule: { checkIntervalMins: 0.25, initialDelayMs: 0 },\n"," options: {\n"," prefixDir: '/salt/community-index/readme',\n"," accessToken: 'Bearer your-access-token',\n"," readme: [\n"," {\n"," name: 'your-mosaic-page-name',\n"," readmeUrl: 'https://some/repo/url/readme.md',\n"," contentTemplate: `a template which replaces ::content:: with the content`,\n"," meta: {\n"," layout: 'DetailTechnical',\n"," title: 'Your Page Title',\n"," tags: ['your-tag-if-required'],\n"," description: 'A description for your readme'\n"," }\n"," }]\n"," }\n"," }]"]},{"title":"Storybook Source","route":"/mosaic/configure/sources/source-storybook","content":["The Storybook source is used to pull individual stories from Storybook, based on tags.","The Mosaic source will filter your Storybook's stories, based on "," or ",".","For each matching story, a page is created in the Mosaic file-system.","additional Mosaic tags can be added to each page using ","additional Mosaic metadata can be added to each page using ","This information can be used to create a dynamic index of stories from Storybook.","Installation","Configuration","The Storybook source is an "," and shares the same base configuration.\n","The "," prop has a default transformer which creates a page for each matching Story.","The "," option is an array of Storybook urls that are used as Sources.\n","Each story is matched on "," using the "," Regexp (or by ",").","If specified, "," and "," can be added to any matching pages.","| Property | Description | Required |\n","| --------------- | ---------------------- | -------- |\n","| options.stories | array of story configs | Yes |"," is an array of Storybooks that you want to pull stories from","| Property | Description | Required |\n","| -------------- | ---------------------------------------- | -------- |\n","| storyUrlPrefix | prefix url of the storybook deployment | Yes |\n","| description | description of the storybook stories | Yes |\n","| storiesUrl | url of the storybook's "," | No |\n","| filter | RegExp filter to match on | No |\n","| filterTags | Array of Storybook tags to match on | No |\n","| meta | additional data to add to matching pages | No |","Example Local Folder Source Definition","sources: [\n"," {\n"," modulePath: '@jpmorganchase/mosaic-source-storybook',\n"," namespace: 'salt',\n"," options: {\n"," prefixDir: '/salt/internal/community-index/story-navigation',\n"," stories: [\n"," {\n"," storiesUrl: 'https://storybook.saltdesignsystem.com/stories.json',\n"," storyUrlPrefix: 'https://storybook.saltdesignsystem.com',\n"," description: 'Navigation patterns created in the Salt Labs',\n"," meta: { // can be any additional metadata you want added to the page's meta.data\n"," tags: ['some-tag'],\n"," data: {\n"," owner: 'Salt',\n"," source: 'STORYBOOK'\n"," }\n"," },\n"," filter: /Lab\\/Tabs/ // this is a Regexp that matches on Storybook title\n"," }\n"," ]\n"," }\n"," }\n","]","Example story metadata format","{\n"," \"type\": \"story\",\n"," \"id\": \"folder--story-1\",\n"," \"name\": \"Story 1\",\n"," \"title\": \"Folder/Story 1\",\n"," \"importPath\": \"./folder/story-1.stories.tsx\",\n"," \"tags\": [\n"," \"dev\",\n"," \"test\"\n"," ]\n","}"]},{"title":"Custom Components","route":"/mosaic/configure/theme/custom-components","content":["Learn how to add your own custom components to your Mosaic site.","Create Components Folder","To start, create a "," folder under "," where you'll store your custom components.","src/\n","└── components/","In this tutorial, we will create a custom "," component.","Create Card Component","Inside the "," folder, create a "," folder, which will contain your React "," component. ","The "," folder should include "," and "," files as shown in the structure below:","├── src/\n","│ ├── components/\n","│ │ └── card/\n","│ │ ├── index.tsx\n","│ │ └── card.module.css","Card Component: index.tsx","Create your "," component within the "," file:","import React from 'react';\n","import styles from './card.module.css';\n","\n","type CardProps = {\n"," title: string;\n"," content: string;\n","};\n","\n","export const Card: React.FC = ({ title, content }) => {\n"," return (\n","
    \n","

    {title}

    \n","

    {content}

    \n","
    \n"," );\n","};","Card Component: card.module.css","Define your component styles in the "," file:",".card {\n"," background-color: #f5f5f5;\n"," border: 1px solid #ccc;\n"," border-radius: 4px;\n"," box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n"," padding: 16px;\n"," transition: box-shadow 0.2s ease-in-out;\n","}\n","\n",".card:hover {\n"," box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);\n","}\n","\n",".card h2 {\n"," color: #333;\n"," font-size: 24px;\n"," margin-bottom: 8px;\n","}\n","\n",".card p {\n"," color: #666;\n"," font-size: 16px;\n"," line-height: 1.5;\n","}","In this example, we use a CSS file, but you can use whichever styling approach you prefer, such as ","vanilla extract",".","To export your "," component, create an "," file in the "," folder:","export * from './card';","Your final folder structure should look like this:","├── src/\n","│ ├── components/\n","│ │ ├── card/\n","│ │ │ ├── index.tsx\n","│ │ │ └── card.module.css\n","│ │ ├── index.ts","Import Custom Card Component","To use your custom "," component, import it into your site's "," file. ","Add the following line to your imports:","import * as myComponents from '../components';","Replace this line:","const components = mosaicComponents;","with:","const components = {\n"," ...mosaicComponents,\n"," ...myComponents\n","};","This will add your custom components to the site, and any custom components in "," will override the corresponding ones in ",". ","The spread operator (",") merges both "," and "," objects, giving priority to "," when there is a naming conflict.","Use Your Custom Card Component","Now you're ready to use your custom "," component. ","Build and run your site, and add the "," component to an MDX file in your "," folder or another source:","","You can create and add more custom components to your Mosaic site by following the same process."]},{"title":"Custom CSS","route":"/mosaic/configure/theme/custom-css","content":["You can customize the look and feel of your Mosaic site by creating cusotm CSS files. ","Here is a step-by-step guide to help you create your own CSS theme.","Create a CSS folder","To get started, create a folder named \"css\" in the \"src\" folder of your Mosaic project.","src/\n","└── css/","Create your theme","Inside the \"css\" folder, create a folder named \"global\". ","This is where you will add your custom styles.","src/\n","└── css/\n"," ├── global/\n"," ├── index.css","Create an \"index.css\" file inside the \"css\" folder. ","This file will import your custom styles.","@import './global/';","Inside your global folder, create a separate CSS file for each part of your site that you want to customize. ","For instance, if you want to change the text styling, create a \"text.css\" file inside the \"global\" folder. ","Here is an example of how your \"text.css\" file could look like:","h1 {\n"," /* Set custom font size and weight */\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h2 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h3 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h4 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h5 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","h6 {\n"," font-size: /* insert value */ ;\n"," font-weight: /* insert value */ ;\n","}\n","\n","p {\n"," font-size: /* insert value */ ;\n"," line-height: /* insert value */ ;\n","}","You can add as many CSS files as you need, depending on how much you want to customize your site.","Create an \"index.css\" file inside the \"global\" folder. ","This file will import your custom styles, in this example we are importing our \"text.css\" file.","@import './text.css';","Your \"css\" folder should now look like this:","src/\n","└── css/\n"," ├── global/\n"," │ ├── text.css\n"," │ ├── index.css\n"," ├── index.css","Import your custom CSS into your site","To apply your custom styles to your Mosaic site, open your \"_app.tsx\" file and add the following line to the bottom of your imports:","import '../css/index.css';","Congratulations! ","You have successfully applied your custom CSS styles to your site. ","This example demonstrated how to create text styles, but you can use the same approach to customize other aspects of your site as well."]},{"title":"Theming Your Site","route":"/mosaic/configure/theme/index","content":["Create a unique look and feel for your Mosaic site by customizing the CSS theme and integrating your own UI components.","Customize the CSS","Adapt various design elements of your Mosaic site to create a cohesive visual theme. ","Refer to our guide to learn more about crafting a custom CSS theme:","CSS Theme Guide","Import Custom Components","Incorporate your own UI components, to tailor the look and functionality of your content. ","This tutorial will walk you through the process of adding custom components to your site:","Adding Custom Components Tutorial"]},{"title":"Aliases Test","route":"/mosaic/test/aliases/index","content":["This page is the alias test page."]},{"title":"Detail Highlight Test Page","route":"/mosaic/test/layouts/detail-highlight","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Detail Overview Test Page","route":"/mosaic/test/layouts/detail-overview","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Detail Technical Test Page","route":"/mosaic/test/layouts/detail-technical","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Edit Layout","route":"/mosaic/test/layouts/edit","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Full Width Layout","route":"/mosaic/test/layouts/full-width","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Layouts","route":"/mosaic/test/layouts/index","content":["Pages for e2e testing of layouts."]},{"title":"Landing Layout Test Page","route":"/mosaic/test/layouts/landing","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Newsletter Test Page","route":"/mosaic/test/layouts/newsletter","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Product Discover Test Page","route":"/mosaic/test/layouts/product-discover","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Product Preview Test Page","route":"/mosaic/test/layouts/product-preview","content":["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 1","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 2","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 3","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum","Heading 4","Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n","Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\n","Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n","Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"]},{"title":"Refs Data","route":"/mosaic/test/refs/data","content":[]},{"title":"Refs Test","route":"/mosaic/test/refs/index","content":["The sidebar priority is ",".","The other page data is ","."]},{"title":"Tags Test","route":"/mosaic/test/tags/index","content":["In Stock","Out of Stock"]},{"title":"$afterSource","route":"/mosaic/configure/plugins/lifecycle/after-source","content":["The first lifecycle event to trigger after receiving pages from a source and runs in a child process.\n","The pages can safely be mutated and will be reflected in the final filesystem that gets generated.\n","It ","must"," return a collection of pages.","The "," lifecycle event is called with:","pages - the collection of pages emitted by the source","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | A mutable object for sharing data with other lifecycle phases of all plugins for this source (including in the main thread) in this plugin |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |","Example - Log out all page routes","async function $afterSource(pages, { config, ignorePages, pageExtensions }) {\n"," for (const page of pages) {\n"," console.log(page.route);\n"," }\n"," return pages;\n","}"]},{"title":"afterUpdate","route":"/mosaic/configure/plugins/lifecycle/after-update","content":["The third lifecycle event to trigger overall and the first to trigger inside the main Mosaic process.","Calls after the filesystem and symlinks have been reconstructed due to a change to the current source pages. ","Pages will ","not"," be cached when read at this stage, to allow for reading content and writing a new copy of it without the cached version taking effect.\n","This method is safe to use with lazy loading, as the filesystem should return the full page when read.","The "," lifecycle event is called with:","mutableFilesystem - Mutable filesystem instance with all of this source's pages inside (and symlinks re-applied)","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | An immutable object for reading data from other lifecycle phases of all plugins for this source in the child process for this plugin. ","Shared only with this source. ","|\n","| globalConfig | An immutable object for reading data from other lifecycle phases of all plugins. ","Shared across all sources. ","|\n","| sharedFilesystem | Mutable filesystem instance independent of any sources. ","Useful for global pages, like sitemaps |\n","| globalFilesystem | Immutable union filesystem instance with all source's pages (and symlinks applied) |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]},{"title":"$beforeSend","route":"/mosaic/configure/plugins/lifecycle/before-send","content":["The second lifecycle event to trigger and does so after a filesystem has been built up from the source pages.","It is the last lifecycle event to run in a source child process before the filesystem is sent to the main Mosaic process and should ","not"," return a value.","The "," lifecycle event is called with:","mutableFilesystem - Mutable virtual filesystem instance with all of this source's pages inside (and symlinks applied)","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | A mutable object for sharing data with other lifecycle phases of all plugins for this source (including in the main thread) in this plugin |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]},{"title":"Lifecycle Events","route":"/mosaic/configure/plugins/lifecycle/index","content":["Plugin lifecycle","The plugin lifecycle is triggered when a source emits content.","Each Mosaic source has its own worker thread and plugin lifecycle events that start with a "," will execute inside the worker thread.\n","All other lifecycle events will execute in the main Mosaic process.","The 5 lifecycle events are:","$afterSource","$beforeSend","afterUpdate","shouldClearCache","shouldUpdateNamespaceSources","Plugin methods that trigger inside the main thread should be asynchronous and highly optimised to\n","avoid holding up the main thread."]},{"title":"shouldClearCache","route":"/mosaic/configure/plugins/lifecycle/should-clear-cache","content":["The fourth lifecycle event to trigger overall and the second to trigger inside the main Mosaic process.","It is called every time ","any"," source emits new pages and should return a boolean to indicate if ","other"," sources should clear their cache in response to the source updating.","Only sources that have already run "," will call this lifecycle hook since there is no\n","cache to clear if they haven't reached that stage in the lifecycle.","The "," lifecycle event is called with:","updatedSourceFilesystem - Immutable filesystem for the source that changed i.e, not the source filesystem","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | An immutable object for reading data from other lifecycle phases of all plugins for this source in the child process for this plugin. ","Shared only with this source. ","|\n","| globalFilesystem | Immutable union filesystem instance with all source's pages (and symlinks applied) |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]},{"title":"shouldUpdateNamespaceSources","route":"/mosaic/configure/plugins/lifecycle/should-update-namespace-sources","content":["The fifth lifecycle event to trigger overall and the third to trigger inside the main Mosaic process.","It is called every time ","any"," source emits new pages and should return a boolean to indicate if ","other sources that share the same ","source namespace"," should re-run ",".","The "," lifecycle event is called with:","updatedSourceFilesystem - Immutable filesystem for the source that changed i.e, not the source filesystem","helpers - an object with useful methods","options - the options specified for the plugin in the mosaic config file","Helpers","The helpers provided with this lifecycle event are listed in the table below.","| Property | Description |\n","| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n","| serialiser | A matching "," for serialising/deserialising pages when reading/writing to the filesystem |\n","| config | An immutable object for reading data from other lifecycle phases of all plugins for this source in the child process for this plugin. ","Shared only with this source. ","|\n","| globalFilesystem | Immutable union filesystem instance with all source's pages (and symlinks applied) |\n","| pageExtensions | A collection of pageExtensions the source is using |\n","| ignorePages | A collection of page globs that are to be ignored for this source |\n","| namespace | The namespace of the source running the plugin |"]}] \ No newline at end of file diff --git a/packages/site/public/sitemap.xml b/packages/site/public/sitemap.xml index ce0967af..04863bf0 100644 --- a/packages/site/public/sitemap.xml +++ b/packages/site/public/sitemap.xml @@ -194,6 +194,11 @@ weekly 0.5 + + https://mosaic-mosaic-dev-team.vercel.app/mosaic/configure/plugins/document-assets-plugin + weekly + 0.5 + https://mosaic-mosaic-dev-team.vercel.app/mosaic/configure/plugins/index weekly diff --git a/packages/site/src/app/[...slug]/Body.tsx b/packages/site/src/app/[...slug]/Body.tsx new file mode 100644 index 00000000..83f28307 --- /dev/null +++ b/packages/site/src/app/[...slug]/Body.tsx @@ -0,0 +1,34 @@ +'use client'; + +import { Editor, useContentEditor } from '@jpmorganchase/mosaic-content-editor-plugin'; +import type { SiteState } from '@jpmorganchase/mosaic-loaders'; +import { useSession } from 'next-auth/react'; +import type { ReactNode } from 'react'; +import type { PreviewAction } from '@jpmorganchase/mosaic-site-components-next'; + +export interface BodyProps { + children?: ReactNode; + meta: Partial; + source: string; + previewAction: PreviewAction; +} + +export function Body({ children, source, meta, previewAction }: BodyProps) { + const { pageState } = useContentEditor(); + const { data: session } = useSession(); + + if (pageState !== 'VIEW' && session !== null) { + return ( + + {children} + + ); + } + return children; +} diff --git a/packages/site/src/app/[...slug]/View.tsx b/packages/site/src/app/[...slug]/View.tsx new file mode 100644 index 00000000..72d9672b --- /dev/null +++ b/packages/site/src/app/[...slug]/View.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { useContentEditor } from '@jpmorganchase/mosaic-content-editor-plugin'; +import { layouts } from '@jpmorganchase/mosaic-layouts'; +import type { SiteState } from '@jpmorganchase/mosaic-loaders'; +import { ReactNode } from 'react'; + +function getLayoutComponent(layout = 'DetailTechnical') { + return layouts?.[layout]; +} + +export interface ViewProps { + children?: ReactNode; + layout: Partial['layout']; +} + +export function View({ children, layout, ...rest }) { + const { pageState } = useContentEditor(); + + const LayoutComponent = pageState === 'VIEW' ? getLayoutComponent(layout) : layouts?.Edit; + + return {children}; +} diff --git a/packages/site/src/app/[...slug]/error.tsx b/packages/site/src/app/[...slug]/error.tsx new file mode 100644 index 00000000..e5b186f1 --- /dev/null +++ b/packages/site/src/app/[...slug]/error.tsx @@ -0,0 +1,22 @@ +'use client'; + +import { useEffect } from 'react'; +import { Button } from '@jpmorganchase/mosaic-components'; +import { Page500 } from '@jpmorganchase/mosaic-site-components'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + useEffect(() => console.error(error), [error]); + + return ( + + + + ); +} diff --git a/packages/site/src/app/[...slug]/layout.tsx b/packages/site/src/app/[...slug]/layout.tsx new file mode 100644 index 00000000..d563ea1b --- /dev/null +++ b/packages/site/src/app/[...slug]/layout.tsx @@ -0,0 +1,57 @@ +import classnames from 'clsx'; +import { SessionProvider, ThemeProvider } from '@jpmorganchase/mosaic-site-components'; +import { + AppHeader, + Breadcrumbs, + Footer, + DocPaginator, + Sidebar, + TableOfContents +} from '@jpmorganchase/mosaic-site-components-next'; +import { themeClassName } from '@jpmorganchase/mosaic-theme'; +import { loadPage, LoadPageError, LoaderData } from '@jpmorganchase/mosaic-loaders'; +import { notFound } from 'next/navigation'; +import { LayoutBase } from '@jpmorganchase/mosaic-layouts'; +import fontClassNames from '../fonts'; +import { View } from './View'; + +export default async function Layout({ params: { slug }, children }) { + const route = `/${slug.join('/')}`; + let metadata: LoaderData = {}; + try { + const { data = {} } = await loadPage(route); + metadata = data; + } catch (error) { + const loadPageError = error as LoadPageError; + if (loadPageError.statusCode === 404) { + notFound(); + } else { + throw error; + } + } + + return ( + + + }> + } + DocPaginatorComponent={ + + } + PrimarySidebarComponent={} + SecondarySidebarComponent={} + > + + {children} + + + + + ); +} diff --git a/packages/site/src/app/[...slug]/page.tsx b/packages/site/src/app/[...slug]/page.tsx new file mode 100644 index 00000000..c38adc4b --- /dev/null +++ b/packages/site/src/app/[...slug]/page.tsx @@ -0,0 +1,48 @@ +import { Suspense } from 'react'; +import { Metadata } from 'next'; +import { loadPage } from '@jpmorganchase/mosaic-loaders'; + +import { type PreviewAction, preview } from '@jpmorganchase/mosaic-site-components-next'; +import { MDXContent, mdxComponents } from '@jpmorganchase/mosaic-site-components-next'; + +import { Body } from './Body'; + +const previewAction: PreviewAction = async options => { + 'use server'; + + return preview({ ...options, components: mdxComponents }); +}; + +export default async function Page({ params: { slug } }) { + const route = `/${slug.join('/')}`; + const { source = '', data = {} } = await loadPage(route); + + return ( + + + + + + ); +} + +export async function generateStaticParams() { + const generateStaticParamsURL = process.env.GENERATE_STATIC_PARAMS_URL; + if (generateStaticParamsURL) { + const pages = await fetch(generateStaticParamsURL).then(res => res.json()); + return pages.map(({ route }) => ({ + slug: route.substring(1).split('/') + })); + } + return []; +} + +export async function generateMetadata({ params: { slug } }): Promise { + const route = `/${slug.join('/')}`; + const { data = {} } = await loadPage(route); + + return { + title: data.title, + description: data.description + }; +} diff --git a/packages/site/src/pages/api/auth/[...nextauth].ts b/packages/site/src/app/api/auth/[...nextauth]/route.ts similarity index 68% rename from packages/site/src/pages/api/auth/[...nextauth].ts rename to packages/site/src/app/api/auth/[...nextauth]/route.ts index 68775f0e..d01ea720 100644 --- a/packages/site/src/pages/api/auth/[...nextauth].ts +++ b/packages/site/src/app/api/auth/[...nextauth]/route.ts @@ -1,7 +1,7 @@ import NextAuth, { NextAuthOptions } from 'next-auth'; import GithubProvider from 'next-auth/providers/github'; -export const authOptions: NextAuthOptions = { +const authOptions: NextAuthOptions = { providers: [ GithubProvider({ clientId: process.env.GITHUB_ID as string, @@ -9,4 +9,7 @@ export const authOptions: NextAuthOptions = { }) ] }; -export default NextAuth(authOptions); + +const handler = NextAuth(authOptions); + +export { handler as GET, handler as POST }; diff --git a/packages/site/src/app/error.tsx b/packages/site/src/app/error.tsx new file mode 100644 index 00000000..753d37af --- /dev/null +++ b/packages/site/src/app/error.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { useEffect } from 'react'; +import classnames from 'clsx'; +import { Page500, SessionProvider, ThemeProvider } from '@jpmorganchase/mosaic-site-components'; +import { Button } from '@jpmorganchase/mosaic-components'; +import { themeClassName } from '@jpmorganchase/mosaic-theme'; + +import fontClassNames from './fonts'; + +export default function Error({ error, reset }: { error: Error; reset: () => void }) { + useEffect(() => console.error(error), [error]); + return ( + + + + + + + + ); +} diff --git a/packages/site/src/app/favicon.ico b/packages/site/src/app/favicon.ico new file mode 100644 index 00000000..b4c425b3 Binary files /dev/null and b/packages/site/src/app/favicon.ico differ diff --git a/packages/site/src/app/fonts.ts b/packages/site/src/app/fonts.ts new file mode 100644 index 00000000..31f862db --- /dev/null +++ b/packages/site/src/app/fonts.ts @@ -0,0 +1,16 @@ +import { Open_Sans, PT_Mono } from 'next/font/google'; + +const ptMono = PT_Mono({ + weight: '400', + display: 'swap', + subsets: ['latin'], + variable: '--salt-typography-fontFamily:' +}); +const openSans = Open_Sans({ + subsets: ['latin'], + variable: '--salt-typography-fontFamily-code', + display: 'swap' +}); + +const fonts = [ptMono.variable, openSans.variable]; +export default fonts; diff --git a/packages/site/src/app/layout.tsx b/packages/site/src/app/layout.tsx new file mode 100644 index 00000000..6a20cf92 --- /dev/null +++ b/packages/site/src/app/layout.tsx @@ -0,0 +1,21 @@ +import { Metadata } from 'next'; + +import '@jpmorganchase/mosaic-site-preset-styles/index.css'; +import { StoreProvider } from '@jpmorganchase/mosaic-store'; +import { Link, Image } from '@jpmorganchase/mosaic-site-components'; + +export const metadata: Metadata = { + title: 'Mosaic' +}; + +export default async function RootLayout({ children }) { + return ( + + + + {children} + + + + ); +} diff --git a/packages/site/src/app/not-found.tsx b/packages/site/src/app/not-found.tsx new file mode 100644 index 00000000..d1a631c8 --- /dev/null +++ b/packages/site/src/app/not-found.tsx @@ -0,0 +1,18 @@ +import classnames from 'clsx'; +import { SessionProvider, ThemeProvider, Page404 } from '@jpmorganchase/mosaic-site-components'; +import { themeClassName } from '@jpmorganchase/mosaic-theme'; + +import fontClassNames from './fonts'; + +export default async function NotFound() { + return ( + + + + + + ); +} diff --git a/packages/site/src/pages/[...route].tsx b/packages/site/src/pages/[...route].tsx deleted file mode 100755 index 5a88224a..00000000 --- a/packages/site/src/pages/[...route].tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import type { GetServerSidePropsContext, GetServerSidePropsResult } from 'next'; -import { Body } from '@jpmorganchase/mosaic-site-components'; -import { - createMiddlewareRunner, - MiddlewareResult, - middlewarePresets -} from '@jpmorganchase/mosaic-site-middleware'; - -import type { MyAppProps, MyMiddlewareProps } from '../types/mosaic'; - -/** - * Extend props passed to MyApp by adding your own middleware ('withMyExampleMiddleware') functions. - * - const middlewareRunner = createMiddlewareRunner({}, - [ - ...middlewarePresets, - [withMyExampleMiddleware, { someProp: 20000 }] - ] - ); - */ -const middlewareRunner = createMiddlewareRunner({}, middlewarePresets); - -export async function getServerSideProps( - context: GetServerSidePropsContext -): Promise>>> { - const props = await middlewareRunner(context, {}); - return props; -} - -/** MyApp will be passed MyAppProps which is created by combining the result of all props created by Middleware */ -const MyApp = props => ; -export default MyApp; diff --git a/packages/site/src/pages/_app.tsx b/packages/site/src/pages/_app.tsx deleted file mode 100755 index 5cbafe06..00000000 --- a/packages/site/src/pages/_app.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { ImageProvider, LinkProvider, ThemeProvider } from '@jpmorganchase/mosaic-components'; -import { LayoutProvider, layouts as mosaicLayouts } from '@jpmorganchase/mosaic-layouts'; -import { - BaseUrlProvider, - Image, - Link, - Metadata, - components as mosaicComponents -} from '@jpmorganchase/mosaic-site-components'; -import { Sitemap } from '@jpmorganchase/mosaic-sitemap-component'; -import { StoreProvider, useCreateStore } from '@jpmorganchase/mosaic-store'; -import { themeClassName } from '@jpmorganchase/mosaic-theme'; -import classnames from 'clsx'; -import { SessionProvider } from 'next-auth/react'; -import type { AppProps } from 'next/app'; -import { Open_Sans, PT_Mono } from 'next/font/google'; -import Head from 'next/head'; -import type { MyAppProps } from '../types/mosaic'; - -import '@jpmorganchase/mosaic-site-preset-styles/index.css'; -import '@jpmorganchase/mosaic-sitemap-component/index.css'; - -const ptMono = PT_Mono({ - weight: '400', - variable: '--salt-typography-fontFamily-ptMono', - display: 'swap', - subsets: ['latin'] -}); -const openSans = Open_Sans({ - subsets: ['latin'], - variable: '--salt-typography-fontFamily-openSans', - display: 'swap' -}); - -const components = { ...mosaicComponents, Sitemap }; -const layoutComponents = mosaicLayouts; - -export default function MyApp({ Component, pageProps = {} }: AppProps) { - const { searchIndex, searchConfig, sharedConfig, source } = pageProps; - const frontmatter = source?.frontmatter || {}; - const storeProps = { - sharedConfig, - searchIndex, - searchConfig, - ...frontmatter - }; - const createStore = useCreateStore(storeProps); - return ( - - - - - - - - - - - - - - - - - ); -} diff --git a/packages/site/src/pages/_document.tsx b/packages/site/src/pages/_document.tsx deleted file mode 100644 index f269951e..00000000 --- a/packages/site/src/pages/_document.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { Document } from '@jpmorganchase/mosaic-site-components'; - -export default class CustomisableDocument extends Document { - /** - * Refer to https://nextjs.org/docs/advanced-features/custom-document#customizing-renderpage - * to customize - */ -} diff --git a/packages/site/src/pages/api/content/preview.ts b/packages/site/src/pages/api/content/preview.ts deleted file mode 100644 index b01889f4..00000000 --- a/packages/site/src/pages/api/content/preview.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { compileMDX } from '@jpmorganchase/mosaic-site-middleware'; -import type { NextApiRequest, NextApiResponse } from 'next'; - -function getErrorMessage(error: unknown) { - if (error instanceof Error) return error.message; - return String(error); -} - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - try { - const { body } = req; - let compiledMdx; - try { - if (body.mode === 'markdown') { - compiledMdx = await compileMDX(body.text, false /** don't parse frontmatter */); - } - } catch (ex: unknown) { - return res - .status(200) - .json({ source: null, error: 'compilation error', exception: getErrorMessage(ex) }); - } - return res.status(200).json({ source: compiledMdx }); - } catch (ex) { - return res.status(500).send(getErrorMessage(ex)); - } -} diff --git a/packages/site/src/pages/index.tsx b/packages/site/src/pages/index.tsx deleted file mode 100644 index da034d31..00000000 --- a/packages/site/src/pages/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -export default function ContentIndex() { - throw new Error('This should never render.'); -} - -export const getServerSideProps = async function getServerSideProps() { - try { - return { - redirect: { - permanent: true, - destination: '/mosaic/' - } - }; - } catch (e: any) { - if (e.status === 404) { - throw new Error( - `No homepage found for this route. Cannot redirect to the correct destination.\n\n${e.statusText}` - ); - } - throw e; - } -}; diff --git a/packages/site/src/types/mosaic.ts b/packages/site/src/types/mosaic.ts deleted file mode 100644 index f00d1b4c..00000000 --- a/packages/site/src/types/mosaic.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { MosaicAppProps, MiddlewarePresetsProps } from '@jpmorganchase/mosaic-site-middleware'; - -/** - * In this file, define each of your own Middleware props, which combine together into MyAppProps - * - * Define the props returned by your middleware - * - * export interface ExampleMiddlewareProps { - * someProp?: string; - * } - * - * Insert the options you want to pass to your middleware (optional) - * - * export interface ExampleMiddlewareOptions { - * someOption: number; - * } - * - * Then compose all your own Middleware props with the default MosaicAppProps - * - * export interface MyAppProps extends MosaicAppProps {}; - */ - -export declare type MyMiddlewareProps = MiddlewarePresetsProps /* & ExampleMiddlewareOptions */; - -export type MyAppProps = MosaicAppProps['props']; diff --git a/packages/site/tsconfig.json b/packages/site/tsconfig.json index 3f6d8f8b..fd5a4658 100644 --- a/packages/site/tsconfig.json +++ b/packages/site/tsconfig.json @@ -6,7 +6,7 @@ "esModuleInterop": true, "moduleResolution": "Node", "isolatedModules": true, - "sourceMap": true, + "sourceMap": false, "declaration": true, "allowJs": false, "strict": true, @@ -17,7 +17,6 @@ "noFallthroughCasesInSwitch": true, "skipLibCheck": true, "baseUrl": ".", - "rootDir": "./src", "outDir": "./dist", "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo", "forceConsistentCasingInFileNames": true, @@ -26,8 +25,13 @@ "resolveJsonModule": true, "noImplicitAny": false, "jsx": "preserve", - "module": "esnext" + "module": "esnext", + "plugins": [ + { + "name": "next" + } + ] }, - "include": ["src"], - "exclude": ["node_modules", "dist/**/*"] + "include": ["src", ".next/types/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/packages/sitemap-component/package.json b/packages/sitemap-component/package.json index 6f620631..cf2af65f 100644 --- a/packages/sitemap-component/package.json +++ b/packages/sitemap-component/package.json @@ -33,21 +33,21 @@ "dev": "node ../../scripts/bundle.mjs watch" }, "devDependencies": { - "@types/styled-components": "^5.1.26", - "@vanilla-extract/esbuild-plugin": "^2.3.11", "del-cli": "^4.0.1", "msw": "^2.0.0", "typescript": "^5.0.0" }, "dependencies": { "@jpmorganchase/mosaic-components": "^0.1.0-beta.89", - "@salt-ds/core": "^1.33.0", + "@jpmorganchase/mosaic-theme": "^0.1.0-beta.89", + "@vanilla-extract/css": "^1.6.0", + "@salt-ds/core": "^1.37.1", "d3": "^7.7.0" }, "peerDependencies": { "@types/react": "^18.3.12", "next-auth": "^4.22.1", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/source-git-repo/package.json b/packages/source-git-repo/package.json index 4fad4cbf..8f1f014b 100644 --- a/packages/source-git-repo/package.json +++ b/packages/source-git-repo/package.json @@ -36,7 +36,6 @@ "@jpmorganchase/mosaic-source-local-folder": "^0.1.0-beta.89", "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", "rxjs": "^7.5.5", - "deepmerge": "^4.2.2", "fs-extra": "^9.1.0", "lodash-es": "^4.17.21", "zod": "^3.22.3" diff --git a/packages/source-readme/package.json b/packages/source-readme/package.json index 998c11de..679cf696 100644 --- a/packages/source-readme/package.json +++ b/packages/source-readme/package.json @@ -33,6 +33,7 @@ "@jpmorganchase/mosaic-source-http": "^0.1.0-beta.89", "@jpmorganchase/mosaic-schemas": "^0.1.0-beta.89", "@jpmorganchase/mosaic-types": "^0.1.0-beta.89", + "deepmerge": "^4.2.2", "rxjs": "^7.5.5", "zod": "^3.22.3" }, diff --git a/packages/standard-generator/package.json b/packages/standard-generator/package.json index aa4b64da..ae56d6f7 100644 --- a/packages/standard-generator/package.json +++ b/packages/standard-generator/package.json @@ -21,6 +21,7 @@ "src" ], "dependencies": { - "dotenv-load": "^2.0.1" + "dotenv-load": "^2.0.1", + "fs-extra": "^10.1.0" } } \ No newline at end of file diff --git a/packages/standard-generator/src/templates/package.json.hbs b/packages/standard-generator/src/templates/package.json.hbs index a6806dc0..8c0e2fc6 100644 --- a/packages/standard-generator/src/templates/package.json.hbs +++ b/packages/standard-generator/src/templates/package.json.hbs @@ -31,7 +31,7 @@ "cross-env": "^7.0.3", "dotenv-load": "^2.0.1", "eslint-config-next": "^12.3.1", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/store/package.json b/packages/store/package.json index e4949d52..521dbd4d 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -36,11 +36,11 @@ "typescript": "^5.0.0" }, "dependencies": { - "zustand": "^4.1.1" + "zustand": "^4.5.5" }, "peerDependencies": { "@types/react": "^18.3.12", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/store/src/StoreContext.ts b/packages/store/src/StoreContext.ts new file mode 100644 index 00000000..9e7c19c1 --- /dev/null +++ b/packages/store/src/StoreContext.ts @@ -0,0 +1,6 @@ +import { createContext } from 'react'; +import { type StoreApi } from 'zustand'; +import { StoreState } from './store'; + +export const StoreContext = createContext | null>(null); +StoreContext.displayName = 'StoreContext'; diff --git a/packages/store/src/StoreProvider.tsx b/packages/store/src/StoreProvider.tsx new file mode 100644 index 00000000..17b5394d --- /dev/null +++ b/packages/store/src/StoreProvider.tsx @@ -0,0 +1,21 @@ +import React, { useEffect, useRef } from 'react'; + +import { StoreContext } from './StoreContext'; +import { InitialStoreState, createStore } from './store'; + +export interface StoreProviderProps extends InitialStoreState { + children?: React.ReactNode; +} + +export const StoreProvider = ({ children, ...restProps }: StoreProviderProps) => { + const storeRef = useRef | null>(null); + if (!storeRef.current) { + storeRef.current = createStore({ ...restProps }); + } + + useEffect(() => { + storeRef.current?.persist.rehydrate(); + }, []); + + return {children}; +}; diff --git a/packages/store/src/__tests__/store.test.ts b/packages/store/src/__tests__/store.test.ts index 29118a0e..788114fe 100644 --- a/packages/store/src/__tests__/store.test.ts +++ b/packages/store/src/__tests__/store.test.ts @@ -1,211 +1,17 @@ import { describe, test, expect } from 'vitest'; -import { renderHook } from '@testing-library/react'; +import { type InitialStoreState, createStore } from '../store.js'; -import { createWrapper } from './test-utils/utils'; -import { initializeStore, SiteState, useCreateStore, useStore } from '../store'; - -function getStateToVerify(currentState: SiteState) { - let { actions, ...stateToVerify } = currentState; - return stateToVerify; -} - -describe('GIVEN the store initializer', () => { - describe('WHEN no other state is provided', () => { - test('THEN the store api provides the initial default state', () => { - const storeApi = initializeStore(); - expect(getStateToVerify(storeApi.getState())).toEqual({ - breadcrumbs: [], - navigation: {}, - sidebarData: [], - tableOfContents: [], - searchIndex: [], - searchConfig: {}, - sharedConfig: {}, - description: undefined, - layout: undefined, - route: undefined, - title: undefined, - colorMode: 'light' - }); - }); - }); - - describe('WHEN additional state is preloaded', () => { - test('THEN the store api provides the initial default state plus the preloaded state', () => { - const state = { - description: 'description', - layout: 'layout', - route: 'route', - title: 'title' - }; - - const storeApi = initializeStore(state); - expect(getStateToVerify(storeApi.getState())).toEqual({ - breadcrumbs: [], - navigation: {}, - searchIndex: [], - searchConfig: {}, - sidebarData: [], - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - - ...state - }); - }); - }); -}); - -describe('GIVEN the `useCreateStore` hook', () => { - describe('WHEN executed on the client', () => { - test('THEN the store content is replaced', () => { - const state = { layout: 'layout', description: 'des' }; - const { result, rerender } = renderHook(() => useCreateStore(state)); - let currentState = result.current().getState(); - - expect(getStateToVerify(currentState)).toEqual({ - breadcrumbs: [], - navigation: {}, - searchIndex: [], - searchConfig: {}, - sidebarData: [], - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - ...state, - route: undefined, - title: undefined - }); - - // update the state and rerender - state.layout = 'new'; - state.description = 'description'; - rerender(); - - currentState = result.current().getState(); - expect(getStateToVerify(currentState)).toEqual({ - breadcrumbs: [], - navigation: {}, - sidebarData: [], - searchIndex: [], - searchConfig: {}, - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - description: 'description', - layout: 'new', - route: undefined, - title: undefined - }); - }); - test('THEN defaults applied when page props do not include default state', () => { - const state: { layout?: string; description: string } = { - layout: 'layout', - description: 'des' +describe('GIVEN the createStore function', () => { + describe('WHEN custom initial state is provided', () => { + test('THEN it overrides the default initial state', () => { + const initialState: InitialStoreState = { + colorMode: 'dark' }; - const { result, rerender } = renderHook(() => useCreateStore(state)); - let currentState = result.current().getState(); - expect(getStateToVerify(currentState)).toEqual({ - breadcrumbs: [], - navigation: {}, - searchIndex: [], - searchConfig: {}, - sidebarData: [], - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - ...state, - route: undefined, - title: undefined - }); - - /** - * we use delete here because the idea is that the page does not indicate a layout IN ANY WAY - * This is different from the page saying my layout is undefined - */ - delete state.layout; - state.description = 'description'; - rerender(); - - currentState = result.current().getState(); - expect(getStateToVerify(currentState)).toEqual({ - breadcrumbs: [], - navigation: {}, - sidebarData: [], - searchIndex: [], - searchConfig: {}, - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - description: 'description', - layout: undefined, - route: undefined, - title: undefined - }); - }); - }); - - describe('WHEN executed on the server', () => { - test('THEN the store is always reinitialized', () => { - const state = { layout: 'layout', description: 'des' }; - const { result, rerender } = renderHook(() => useCreateStore(state, true)); - let currentState = result.current().getState(); - - expect(getStateToVerify(currentState)).toEqual({ - breadcrumbs: [], - navigation: {}, - searchIndex: [], - searchConfig: {}, - sidebarData: [], - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - ...state, - route: undefined, - title: undefined - }); - - // update the state and rerender - state.layout = 'new'; - state.description = 'description'; - rerender(); - - currentState = result.current().getState(); - - expect(getStateToVerify(currentState)).toEqual({ - breadcrumbs: [], - navigation: {}, - sidebarData: [], - searchIndex: [], - searchConfig: {}, - tableOfContents: [], - sharedConfig: {}, - colorMode: 'light', - description: 'description', - layout: 'new', - route: undefined, - title: undefined - }); - }); - }); -}); - -describe('GIVEN the `useStore` hook', () => { - describe('WHEN a selector is provided', () => { - test('THEN the hook applies the selector and returns the selected state', () => { - const { result } = renderHook(() => useStore(state => state.title), { - wrapper: createWrapper({ title: 'title' }) - }); - expect(result.current).toEqual('title'); - }); - }); + const store = createStore(initialState); + const state = store.getState(); - describe('WHEN rendered outside of the StoreContext', () => { - test('THEN the hook throws an error', () => { - expect(() => renderHook(() => useStore(state => state.title))).toThrowError( - 'Missing StoreProvider in the tree' - ); + expect(state.colorMode).toEqual(initialState.colorMode); }); }); }); diff --git a/packages/store/src/__tests__/test-utils/utils.tsx b/packages/store/src/__tests__/test-utils/utils.tsx deleted file mode 100644 index b15d8a6a..00000000 --- a/packages/store/src/__tests__/test-utils/utils.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; - -import { useCreateStore, StoreProvider } from '../../store'; -import type { SiteState } from '../../store'; - -export function renderWithStore(ui: React.ReactElement) { - const { rerender, ...result } = render({ui}); - return { - ...result, - rerender: (rerenderUi: React.ReactElement) => rerender({rerenderUi}) - }; -} - -export function createWrapper(state?: Partial) { - return ({ children }: { children?: React.ReactNode }) => {children}; -} - -/** - * - * @param state: initial state for the site store - */ -const Store: React.FC<{ state?: Partial }> = ({ children, state = {} }) => { - const createStore = useCreateStore(state); - return {children}; -}; diff --git a/packages/store/src/__tests__/useAppHeader.test.ts b/packages/store/src/__tests__/useAppHeader.test.ts deleted file mode 100644 index b6f3d673..00000000 --- a/packages/store/src/__tests__/useAppHeader.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { describe, expect, test, vi, beforeAll, afterAll } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useAppHeader } from '../useAppHeader'; -import { SiteState } from '../store'; - -const header: SiteState['sharedConfig']['header'] = { - homeLink: '/mosaic', - logo: '/some/url', - title: 'Digital Platform mosaic', - menu: [ - { - title: 'Products', - link: '/mosaic/products' - }, - { - title: 'Case Studies', - link: '/mosaic/case-studies' - }, - { - title: 'Docs & Tools', - links: [ - { - title: 'Docs', - link: '/mosaic/docs' - }, - { - title: 'APIs', - link: '/mosaic/apis' - }, - { - title: 'Start Your Project', - link: '/apps/app-registry' - } - ] - }, - { - title: 'Community Updates', - links: [ - { - title: 'Newsletters', - link: '/mosaic/newsletters' - }, - { - title: 'Blog', - link: ' http://www.example.com/blog' - }, - { - title: 'Podcasts', - link: '/mosaic/podcasts' - }, - { - title: 'Release Notes', - link: '/mosaic/release-notes' - }, - { - title: 'Operating Model', - link: ' http://www.example.com/release-notes' - } - ] - }, - { - title: 'Support', - links: [ - { - title: 'Contact', - link: '/mosaic/support' - }, - { - title: 'Contributing', - link: '/mosaic/support/contributing' - }, - { - title: 'Incidents & RCAs', - link: '/mosaic/support/rcas' - }, - { - title: 'Runbooks', - link: '/mosaic/support/runbooks' - }, - { - title: 'Site', - link: '/mosaic/site' - } - ] - } - ] -}; - -const invalidMenuHeader: SiteState['sharedConfig']['header'] = { - homeLink: '/mosaic', - logo: '/some/url', - title: 'Mosaic', - menu: [ - { - title: 'Products', - // @ts-ignore - href: '/mosaic/products' - } - ] -}; - -const state: Partial = { - sharedConfig: { header } -}; - -const inValidMenuState: Partial = { - sharedConfig: { header: invalidMenuHeader } -}; - -describe('GIVEN the `useAppHeader` hook', () => { - describe('WHEN there are no header props in the store', () => { - test('THEN the hook returns undefined', () => { - const { result } = renderHook(() => useAppHeader(), { - wrapper: createWrapper() - }); - expect(result.current).toBeUndefined(); - }); - }); - - describe('WHEN there are header props in the store', () => { - test('THEN the hook returns a homelink, logo and a title', () => { - const { result } = renderHook(() => useAppHeader(), { - wrapper: createWrapper({ ...state }) - }); - expect(result.current?.homeLink).toEqual(header.homeLink); - expect(result.current?.logo).toEqual(header.logo); - expect(result.current?.title).toEqual(header.title); - expect(result.current?.menu.length).toEqual(5); - }); - test('AND THEN the menu items have been assigned a type', () => { - const { result } = renderHook(() => useAppHeader(), { - wrapper: createWrapper({ ...state }) - }); - expect(result.current?.menu.length).toEqual(5); - expect(result.current?.menu[0].type).toEqual('link'); - expect(result.current?.menu[3].type).toEqual('menu'); - }); - - describe('AND WHEN invalid menu items are provided', () => { - let consoleError; - beforeAll(() => { - consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}); - }); - afterAll(() => { - vi.restoreAllMocks(); - }); - test('THEN no menu items are returned', () => { - const { result } = renderHook(() => useAppHeader(), { - wrapper: createWrapper({ ...inValidMenuState }) - }); - expect(result.current?.menu.length).toEqual(0); - expect(consoleError).toHaveBeenCalledTimes(1); - }); - }); - }); -}); diff --git a/packages/store/src/__tests__/useBreadcrumbs.test.ts b/packages/store/src/__tests__/useBreadcrumbs.test.ts deleted file mode 100644 index 4dd5caef..00000000 --- a/packages/store/src/__tests__/useBreadcrumbs.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { type Breadcrumb, useBreadcrumbs } from '../useBreadcrumbs'; - -const breadcrumbs: Breadcrumb[] = [ - { label: 'Home', path: '/home', hidden: false, id: '1' }, - { label: 'Docs', path: '/docs', hidden: false, id: '2' }, - { label: 'Products', path: '/products', hidden: false, id: '3' } -]; - -describe('GIVEN the `useBreadcrumbs` hook', () => { - describe('WHEN there are NO breadcrumbs are in the store', () => { - test('THEN the hook returns an empty collection', () => { - const { result } = renderHook(() => useBreadcrumbs(), { - wrapper: createWrapper() - }); - expect(result.current.breadcrumbs).toEqual([]); - expect(result.current.enabled).toEqual(false); - }); - }); - - describe('WHEN there are breadcrumbs are in the store', () => { - test('THEN the hook returns those breadcrumbs', () => { - const { result } = renderHook(() => useBreadcrumbs(), { - wrapper: createWrapper({ breadcrumbs }) - }); - expect(result.current.breadcrumbs.length).toEqual(3); - expect(result.current.enabled).toEqual(true); - }); - }); - - describe('WHEN there are breadcrumbs are in the store', () => { - test('THEN enabled is false if there are not enough breadcrumbs', () => { - const { result } = renderHook(() => useBreadcrumbs(10), { - wrapper: createWrapper({ breadcrumbs }) - }); - expect(result.current.breadcrumbs.length).toEqual(3); - expect(result.current.enabled).toEqual(false); - }); - }); -}); diff --git a/packages/store/src/__tests__/useColorMode.test.ts b/packages/store/src/__tests__/useColorMode.test.ts index e714bcd6..3a906b1e 100644 --- a/packages/store/src/__tests__/useColorMode.test.ts +++ b/packages/store/src/__tests__/useColorMode.test.ts @@ -1,6 +1,6 @@ -import { describe, test, expect } from 'vitest'; -import { renderHook } from '@testing-library/react'; -import { createWrapper } from './test-utils/utils'; +import { describe, expect, test } from 'vitest'; +import { act, renderHook } from '@testing-library/react'; +import { createWrapper } from './utils/utils'; import { useColorMode } from '../useColorMode'; describe('GIVEN the `useColorMode` hook', () => { @@ -8,6 +8,25 @@ describe('GIVEN the `useColorMode` hook', () => { const { result } = renderHook(() => useColorMode(), { wrapper: createWrapper() }); - expect(result.current).toEqual('light'); + expect(result.current.colorMode).toEqual('light'); + }); + + describe('AND WHEN the setColorMode action is used', () => { + test('THEN colorMode is updated', () => { + const storeWrapper = createWrapper(); + + const { result: useColorModeResult, rerender } = renderHook(() => useColorMode(), { + wrapper: storeWrapper + }); + + expect(useColorModeResult.current.colorMode).toEqual('light'); + const { setColorMode } = useColorModeResult.current; + + act(() => { + setColorMode('dark'); + rerender(); + }); + expect(useColorModeResult.current.colorMode).toEqual('dark'); + }); }); }); diff --git a/packages/store/src/__tests__/useFooter.test.ts b/packages/store/src/__tests__/useFooter.test.ts deleted file mode 100644 index 1a0294ac..00000000 --- a/packages/store/src/__tests__/useFooter.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useFooter } from '../useFooter'; -import { SiteState } from '../store'; - -const footer: SiteState['sharedConfig']['footer'] = { - description: 'Sign up to stay on top of new developments and solutions.', - title: 'Subscribe to our Newsletter', - href: 'http://a.link.com', - label: 'Sign Up', - helpLinks: { - stackoverflowLabel: 'View our Stack Overflow', - stackoverflowUrl: 'https://stack.com', - symphonyLabel: 'Chat on Symphony', - symphonyUrl: 'symphony://a_stream' - } -}; - -const invalidMenuHeader: SiteState['sharedConfig']['header'] = { - homeLink: '/developer', - logo: '/some/url', - title: 'Digital Platform Developer', - menu: [ - { - title: 'Products', - // @ts-ignore - href: '/developer/products' - } - ] -}; - -const state: Partial = { - sharedConfig: { footer } -}; - -describe('GIVEN the `useFooter` hook', () => { - describe('WHEN there are no footer props in the store', () => { - test('THEN the hook returns undefined', () => { - const { result } = renderHook(() => useFooter(), { - wrapper: createWrapper() - }); - expect(result.current).toBeUndefined(); - }); - }); - - describe('WHEN there are footer props in the store', () => { - test('THEN the footer props are returned', () => { - const { result } = renderHook(() => useFooter(), { - wrapper: createWrapper({ ...state }) - }); - expect(result.current?.description).toEqual(footer.description); - expect(result.current?.title).toEqual(footer.title); - expect(result.current?.href).toEqual(footer.href); - expect(result.current?.label).toEqual(footer.label); - expect(result.current?.helpLinks.stackoverflowLabel).toEqual( - footer.helpLinks.stackoverflowLabel - ); - expect(result.current?.helpLinks.stackoverflowUrl).toEqual(footer.helpLinks.stackoverflowUrl); - expect(result.current?.helpLinks.symphonyLabel).toEqual(footer.helpLinks.symphonyLabel); - expect(result.current?.helpLinks.symphonyUrl).toEqual(footer.helpLinks.symphonyUrl); - }); - }); -}); diff --git a/packages/store/src/__tests__/useImageComponent.test.ts b/packages/store/src/__tests__/useImageComponent.test.ts new file mode 100644 index 00000000..85bb1587 --- /dev/null +++ b/packages/store/src/__tests__/useImageComponent.test.ts @@ -0,0 +1,27 @@ +import { describe, test, expect } from 'vitest'; +import { renderHook } from '@testing-library/react'; + +import { createWrapper } from './utils/utils'; +import { useImageComponent } from '../useImageComponent'; + +describe('GIVEN the `useImageComponent` hook', () => { + describe('WHEN a user does **not** provide an ImageComponent', () => { + test('THEN the img element is returned', () => { + const { result } = renderHook(() => useImageComponent(), { + wrapper: createWrapper() + }); + expect(result.current).toEqual('img'); + }); + }); + + describe('WHEN a user does provide a ImageComponent', () => { + test('THEN the hook returns the layout', () => { + const myImageComponent = () => 'hi'; + + const { result } = renderHook(() => useImageComponent(), { + wrapper: createWrapper({ ImageComponent: myImageComponent }) + }); + expect(result.current).toEqual(myImageComponent); + }); + }); +}); diff --git a/packages/store/src/__tests__/useLayout.test.ts b/packages/store/src/__tests__/useLayout.test.ts deleted file mode 100644 index bf5b23e1..00000000 --- a/packages/store/src/__tests__/useLayout.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useLayout } from '../useLayout'; - -describe('GIVEN the `useLayout` hook', () => { - describe('WHEN there is no layout in the store', () => { - test('THEN the hook returns undefined', () => { - const { result } = renderHook(() => useLayout(), { - wrapper: createWrapper() - }); - expect(result.current.layout).toBeUndefined(); - }); - }); - - describe('WHEN there is a layout in the store', () => { - test('THEN the hook returns the layout', () => { - const { result } = renderHook(() => useLayout(), { - wrapper: createWrapper({ layout: 'LayoutName' }) - }); - expect(result.current.layout).toEqual('LayoutName'); - }); - }); -}); diff --git a/packages/store/src/__tests__/useLinkComponent.test.ts b/packages/store/src/__tests__/useLinkComponent.test.ts new file mode 100644 index 00000000..a9388c14 --- /dev/null +++ b/packages/store/src/__tests__/useLinkComponent.test.ts @@ -0,0 +1,27 @@ +import { describe, test, expect } from 'vitest'; +import { renderHook } from '@testing-library/react'; + +import { createWrapper } from './utils/utils'; +import { useLinkComponent } from '../useLinkComponent'; + +describe('GIVEN the `useLinkComponent` hook', () => { + describe('WHEN a user does **not** provide a LinkComponent', () => { + test('THEN the anchor element is returned', () => { + const { result } = renderHook(() => useLinkComponent(), { + wrapper: createWrapper() + }); + expect(result.current).toEqual('a'); + }); + }); + + describe('WHEN a user does provide a LinkComponent', () => { + test('THEN the hook returns the layout', () => { + const myLinkComponent = () => 'hi'; + + const { result } = renderHook(() => useLinkComponent(), { + wrapper: createWrapper({ LinkComponent: myLinkComponent }) + }); + expect(result.current).toEqual(myLinkComponent); + }); + }); +}); diff --git a/packages/store/src/__tests__/useMeta.test.ts b/packages/store/src/__tests__/useMeta.test.ts deleted file mode 100644 index 3ec08e66..00000000 --- a/packages/store/src/__tests__/useMeta.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useMeta } from '../useMeta'; -import { Breadcrumb } from '../types'; - -const breadcrumbs: Breadcrumb[] = [ - { label: 'Home', path: '/home', hidden: false, id: '1' }, - { label: 'Docs', path: '/docs', hidden: false, id: '2' }, - { label: 'Products', path: '/products', hidden: false, id: '3' } -]; - -describe('GIVEN the `useMeta` hook', () => { - describe('WHEN there is no metadata in the store', () => { - test('THEN the hook returns no metadata', () => { - const { result } = renderHook(() => useMeta(), { - wrapper: createWrapper() - }); - expect(result.current.meta.breadcrumbs).toEqual([]); - expect(result.current.meta.description).toBeUndefined(); - expect(result.current.meta.title).toBeUndefined(); - }); - }); - - describe('WHEN there is metadata in the store', () => { - test('THEN the hook returns the metadata', () => { - const { result } = renderHook(() => useMeta(), { - wrapper: createWrapper({ breadcrumbs, title: 'A Title', description: 'A Description' }) - }); - expect(result.current.meta.breadcrumbs).toEqual(breadcrumbs); - expect(result.current.meta.title).toEqual('A Title'); - expect(result.current.meta.description).toEqual('A Description'); - }); - }); -}); diff --git a/packages/store/src/__tests__/useRoute.test.ts b/packages/store/src/__tests__/useRoute.test.ts deleted file mode 100644 index 8c2bfee5..00000000 --- a/packages/store/src/__tests__/useRoute.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useRoute } from '../useRoute'; - -describe('GIVEN the `useRoute` hook', () => { - describe('WHEN there is no route in the store', () => { - test('THEN the hook returns no route', () => { - const { result } = renderHook(() => useRoute(), { - wrapper: createWrapper() - }); - expect(result.current.route).toBeUndefined(); - }); - }); - - describe('WHEN there is a route in the store', () => { - test('THEN the hook returns the route', () => { - const { result } = renderHook(() => useRoute(), { - wrapper: createWrapper({ route: 'a/route/to/a/page' }) - }); - expect(result.current.route).toEqual('a/route/to/a/page'); - }); - }); -}); diff --git a/packages/store/src/__tests__/useSearchIndex.test.ts b/packages/store/src/__tests__/useSearchIndex.test.ts deleted file mode 100644 index dbfa9201..00000000 --- a/packages/store/src/__tests__/useSearchIndex.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useSearchIndex } from '../useSearchIndex'; -import { SiteState } from '../store'; - -const index: SiteState['searchIndex'] = [ - { - title: 'Test item title', - content: ['test item content sentence 1', 'test item content sentence 2'], - route: 'test/page/route' - }, - { - title: 'Second test item title', - content: [ - 'Laborum deserunt laboris in quis dolor est laboris incididunt exercitation sunt voluptate.', - 'Nisi amet cupidatat ut laborum Lorem eu qui fugiat ad.' - ], - route: 'second/test/page/route' - } -]; - -const config: SiteState['searchConfig'] = { - includeScore: false, - includeMatches: true, - maxPatternLength: 240, - ignoreLocation: true, - threshold: 0.3, - keys: ['title', 'content'] -}; - -const state: Partial = { - searchIndex: index, - searchConfig: config -}; - -describe('GIVEN the useSearchIndex hook', () => { - describe('WHEN there is no search index in the store', () => { - test('THEN searchEnabled is set to false', () => { - const { result } = renderHook(() => useSearchIndex(), { - wrapper: createWrapper() - }); - expect(result.current.searchEnabled).toBe(false); - }); - test('AND searchIndex is set to an empty array', () => { - const { result } = renderHook(() => useSearchIndex(), { - wrapper: createWrapper() - }); - expect(Array.isArray(result.current.searchIndex)).toBe(true); - expect(result.current.searchIndex.length).toBe(0); - }); - test('AND searchConfig is set to an empty object', () => { - const { result } = renderHook(() => useSearchIndex(), { - wrapper: createWrapper() - }); - expect(typeof result.current.searchConfig).toBe('object'); - expect(Object.keys(result.current.searchConfig).length).toBe(0); - }); - }); - - describe('WHEN there is a search index in the store', () => { - test('THEN searchEnabled is set to true', () => { - const { result } = renderHook(() => useSearchIndex(), { - wrapper: createWrapper({ ...state }) - }); - expect(result.current.searchEnabled).toBe(true); - }); - test('AND searchIndex contains the index', () => { - const { result } = renderHook(() => useSearchIndex(), { - wrapper: createWrapper({ ...state }) - }); - expect(Array.isArray(result.current.searchIndex)).toBe(true); - expect(result.current.searchIndex.length).toBe(index.length); - }); - test('AND searchIndex contains the search config', () => { - const { result } = renderHook(() => useSearchIndex(), { - wrapper: createWrapper({ ...state }) - }); - expect(typeof result.current.searchConfig).toBe('object'); - expect(Object.keys(result.current.searchConfig).length).toBe(Object.keys(config).length); - expect(result.current.searchConfig.ignoreLocation).toBe(true); - expect(result.current.searchConfig.maxPatternLength).toBe(240); - expect(result.current.searchConfig.keys).toEqual(['title', 'content']); - }); - }); -}); diff --git a/packages/store/src/__tests__/useStore.test.ts b/packages/store/src/__tests__/useStore.test.ts new file mode 100644 index 00000000..2fe9bea3 --- /dev/null +++ b/packages/store/src/__tests__/useStore.test.ts @@ -0,0 +1,29 @@ +import { describe, test, expect, afterAll, vi } from 'vitest'; +import { renderHook } from '@testing-library/react'; + +import { createWrapper } from './utils/utils'; +import { useStore } from '../useStore'; + +const consoleErrorMock = vi.spyOn(console, 'error').mockImplementation(() => {}); + +describe('GIVEN the `useStore` hook', () => { + describe('WHEN a selector is provided', () => { + test('THEN the hook applies the selector and returns the selected state', () => { + const { result } = renderHook(() => useStore(state => state.colorMode), { + wrapper: createWrapper({ colorMode: 'dark' }) + }); + expect(result.current).toEqual('dark'); + }); + }); + + describe('WHEN rendered outside of the StoreContext', () => { + afterAll(() => { + consoleErrorMock.mockRestore(); + }); + test('THEN the hook throws an error', () => { + expect(() => renderHook(() => useStore(state => state.colorMode))).toThrowError( + 'Missing StoreProvider in the tree' + ); + }); + }); +}); diff --git a/packages/store/src/__tests__/useStoreActions.test.ts b/packages/store/src/__tests__/useStoreActions.test.ts deleted file mode 100644 index 5138c096..00000000 --- a/packages/store/src/__tests__/useStoreActions.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import { renderHook, act } from '@testing-library/react'; -import { createWrapper } from './test-utils/utils'; -import { useStoreActions } from '../useStoreActions'; -import { useColorMode } from '../useColorMode'; - -describe('GIVEN the `useStoreActions` hook', () => { - test('THEN there is a setColorMode action', () => { - const { result } = renderHook(() => useStoreActions(), { - wrapper: createWrapper() - }); - expect(result.current.setColorMode).not.toBeUndefined(); - }); - - describe('AND WHEN the setColorMode action is used', () => { - test('THEN colorMode is updated', () => { - const { result: useStoreActionsResult } = renderHook(() => useStoreActions(), { - wrapper: createWrapper() - }); - - const { result: useColorModeResult, rerender } = renderHook(() => useColorMode(), { - wrapper: createWrapper() - }); - - expect(useColorModeResult.current).toEqual('light'); - - const { setColorMode } = useStoreActionsResult.current; - - act(() => { - setColorMode('dark'); - rerender(); - }); - expect(useColorModeResult.current).toEqual('dark'); - }); - }); -}); diff --git a/packages/store/src/__tests__/useTableOfContents.test.ts b/packages/store/src/__tests__/useTableOfContents.test.ts deleted file mode 100644 index 50f7fd39..00000000 --- a/packages/store/src/__tests__/useTableOfContents.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { describe, expect, test } from 'vitest'; -import { renderHook } from '@testing-library/react'; - -import { createWrapper } from './test-utils/utils'; -import { useTableOfContents } from '../useTableOfContents'; -import type { TableOfContentsItem } from '../types/tableOfContents'; - -const tableOfContents: TableOfContentsItem[] = [ - { level: 2, id: 'what-is-analytics', text: 'What is Analytics' }, - { level: 2, id: 'what-is-our-goal', text: 'What is our goal?' }, - { level: 2, id: 'benefits-of-using-our-service', text: 'Benefits of using our Service' }, - { level: 2, id: 'useful-go-links', text: 'Useful Go links' } -]; - -describe('GIVEN the `useTableOfContents` hook', () => { - describe('WHEN there is NO table of contents in the store', () => { - test('THEN the hook returns an empty collection', () => { - const { result } = renderHook(() => useTableOfContents(), { - wrapper: createWrapper() - }); - expect(result.current.tableOfContents).toEqual([]); - }); - }); - - describe('WHEN table of contents is in the store', () => { - test('THEN the hook returns the tabel of contents', () => { - const { result } = renderHook(() => useTableOfContents(), { - wrapper: createWrapper({ tableOfContents }) - }); - expect(result.current.tableOfContents.length).toEqual(4); - }); - }); -}); diff --git a/packages/store/src/__tests__/utils/utils.tsx b/packages/store/src/__tests__/utils/utils.tsx new file mode 100644 index 00000000..e6ef1b66 --- /dev/null +++ b/packages/store/src/__tests__/utils/utils.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import { StoreProvider } from '../../StoreProvider'; +import type { SiteState } from '../../store'; + +export function renderWithStore(ui: React.ReactElement) { + return render({ui}); +} + +export function createWrapper(state?: Partial) { + return ({ children }: { children?: React.ReactNode }) => ( + {children} + ); +} diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 5b6326dc..96efa2ee 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -1,14 +1,8 @@ +'use client'; + export * from './store'; -export * from './useAppHeader'; -export * from './useBreadcrumbs'; +export * from './StoreProvider'; +export * from './useStore'; export * from './useColorMode'; -export * from './useLayout'; -export * from './useMeta'; -export * from './useNavigation'; -export * from './useRoute'; -export * from './useSearchIndex'; -export * from './useSidebar'; -export * from './useTableOfContents'; -export * from './useFooter'; -export * from './useStoreActions'; -export * from './types'; +export * from './useImageComponent'; +export * from './useLinkComponent'; diff --git a/packages/store/src/store.ts b/packages/store/src/store.ts index 45520bb2..1151e43b 100644 --- a/packages/store/src/store.ts +++ b/packages/store/src/store.ts @@ -1,141 +1,48 @@ -import { useLayoutEffect, createContext, useContext } from 'react'; -import { createStore, StoreApi, useStore as useZustandStore } from 'zustand'; +import type { ElementType } from 'react'; +import { createStore as createZustandStore } from 'zustand'; import { devtools, persist } from 'zustand/middleware'; -import type { BreadcrumbsSlice } from './types/breadcrumbs'; -import type { SearchIndexSlice } from './types/searchIndex'; -import type { SharedConfigSlice } from './types/sharedConfig'; -import type { LayoutSlice } from './types/layout'; -import type { NavigationSlice } from './types/navigation'; -import type { SidebarSlice } from './types/sidebar'; -import type { TableOfContentsSlice } from './types/tableOfContents'; -import type { ColorMode } from './types/colorMode'; +import type { ColorMode } from './useColorMode'; -let store: StoreApi; - -export type SiteState = BreadcrumbsSlice & - LayoutSlice & - SidebarSlice & - TableOfContentsSlice & - NavigationSlice & - SearchIndexSlice & - SharedConfigSlice & { - /** Page metadata description, used by search */ - description?: string; - /** Page route */ - route?: string; - /** Page title */ - title?: string; - colorMode: ColorMode; - actions: { - setColorMode: (colorMode: ColorMode) => void; - }; +export type StoreState = { + colorMode: ColorMode; + ImageComponent: ElementType; + LinkComponent: ElementType; + actions: { + setColorMode: (colorMode: ColorMode) => void; }; +}; -type PeristedStoreState = Pick; -type DefaultSiteState = Omit; +type PeristedStoreState = Pick; +export type DefaultStoreState = Omit; +export type InitialStoreState = Partial; -function getDefaultInitialState(): DefaultSiteState { +function getDefaultInitialState(initialState?: InitialStoreState): DefaultStoreState { return { - breadcrumbs: [], - sidebarData: [], - tableOfContents: [], - navigation: {}, - searchIndex: [], - searchConfig: {}, - sharedConfig: {}, - description: undefined, - layout: undefined, - route: undefined, - title: undefined, - colorMode: 'light' + colorMode: 'light', + ImageComponent: 'img', + LinkComponent: 'a', + ...initialState }; } -const StoreContext = createContext(undefined); -StoreContext.displayName = 'StoreContext'; -const StoreProvider = StoreContext.Provider; - const storeMiddlewares = stateCreatorFn => devtools( - persist(stateCreatorFn, { + persist(stateCreatorFn, { name: 'mosaic-theme-pref', - partialize: (state: SiteState) => ({ + skipHydration: true, + partialize: (state: StoreState) => ({ colorMode: state.colorMode }) }) ); -const initializeStore = (preloadedState: Partial = {}) => { - const mosaicStore = createStore( +export const createStore = (initialState?: InitialStoreState) => + createZustandStore( storeMiddlewares(set => ({ - ...getDefaultInitialState(), - ...preloadedState, + ...getDefaultInitialState(initialState), actions: { setColorMode: (colorMode: ColorMode) => set({ colorMode }) } })) ); - return mosaicStore; -}; - -function useCreateStore(serverInitialState: Partial, isSSR = false) { - // Server side code: For SSR & SSG, always use a new store. - if (typeof window === 'undefined' || isSSR) { - return () => initializeStore(serverInitialState); - } - // End of server side code - - // Client side code: - // Next.js always re-uses same store regardless of whether page is a SSR or SSG or CSR type. - const isReusingStore = Boolean(store); - store = store ?? initializeStore(serverInitialState); - - // When next.js re-renders _app while re-using an older store, then replace current state with - // the new state (in the next render cycle). - // (Why next render cycle? Because react cannot re-render while a render is already in progress. - // i.e. we cannot do a setState() as that will initiate a re-render) - // - // eslint complaining "React Hooks must be called in the exact same order in every component render" - // is ignorable as this code runs in same order in a given environment (i.e. client or server) - useLayoutEffect(() => { - // serverInitialState is undefined for CSR pages. It is up to you if you want to reset - // states on CSR page navigation or not. I have chosen not to, but if you choose to, - // then add `serverInitialState = getDefaultInitialState()` here. - if (serverInitialState && isReusingStore) { - // recombine the page props with the initial state so that if page props are missing something then the default gets applied - const pageState = { ...getDefaultInitialState(), ...serverInitialState }; - const { colorMode, actions, ...restStoreState } = store.getState(); - - store.setState( - { - // re-use functions from existing store - ...restStoreState, - // but reset all other properties. - ...pageState, - colorMode, - actions - }, - true // replace states, rather than shallow merging - ); - } - }); - - return () => store; -} - -/** - * Hook providing access to state stored in the site store - */ -function useStore( - selector: (state: SiteState) => T, - equalityFn?: (left: T, right: T) => boolean -): T { - const storeFromContext = useContext(StoreContext); - if (!storeFromContext) { - throw new Error('Missing StoreProvider in the tree'); - } - return useZustandStore(storeFromContext, selector, equalityFn); -} - -export { useCreateStore, StoreProvider, useStore, initializeStore }; diff --git a/packages/store/src/types/colorMode.ts b/packages/store/src/types/colorMode.ts deleted file mode 100644 index 3a5ba714..00000000 --- a/packages/store/src/types/colorMode.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const lightMode = 'light'; -export const darkMode = 'dark'; - -export type ColorMode = typeof lightMode | typeof darkMode; diff --git a/packages/store/src/types/index.ts b/packages/store/src/types/index.ts deleted file mode 100644 index 4fe2e957..00000000 --- a/packages/store/src/types/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -export * from './appHeader'; -export * from './breadcrumbs'; -export * from './footer'; -export * from './helpLinks'; -export * from './layout'; -export * from './meta'; -export * from './navigation'; -export * from './route'; -export * from './searchIndex'; -export * from './sharedConfig'; -export * from './sidebar'; -export * from './tableOfContents'; diff --git a/packages/store/src/types/layout.ts b/packages/store/src/types/layout.ts deleted file mode 100644 index 93b220e0..00000000 --- a/packages/store/src/types/layout.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * [[`LayoutSlice`]] specifies the page's anatomy - */ -export type LayoutSlice = { - /** Layout name that will define the page's anatomy */ - layout?: string; -}; diff --git a/packages/store/src/types/meta.ts b/packages/store/src/types/meta.ts deleted file mode 100644 index 28737478..00000000 --- a/packages/store/src/types/meta.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Breadcrumb } from './breadcrumbs'; - -/** - * [[`MetaSlice`]] specifies the page's head metadata - */ -export type MetaSlice = { - /** Page description */ - description?: string; - /** Page breadcrumbs */ - breadcrumbs?: Breadcrumb[]; - /** Page title */ - title?: string; -}; diff --git a/packages/store/src/types/route.ts b/packages/store/src/types/route.ts deleted file mode 100644 index 494db536..00000000 --- a/packages/store/src/types/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * [[`RouteSlice`]] specifies the page's route - */ -export type RouteSlice = { - /** Current page route */ - route?: string; -}; diff --git a/packages/store/src/useAppHeader.ts b/packages/store/src/useAppHeader.ts deleted file mode 100644 index 05479025..00000000 --- a/packages/store/src/useAppHeader.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useStore } from './store'; -import { MenuItemType } from './types'; -import type { - AppHeaderSlice, - AppHeaderMenuLinkItem, - AppHeaderMenuLinksItem, - MenuLinkItem, - MenuLinksItem -} from './types'; - -function isMenu(menu: MenuLinkItem | MenuLinksItem): menu is MenuLinksItem { - return (menu as MenuLinksItem).links !== undefined; -} -function isMenuLink(menu: MenuLinkItem | MenuLinksItem): menu is MenuLinkItem { - return (menu as MenuLinkItem).link !== undefined; -} - -export type AppHeaderMenu = Array; - -export interface AppHeader extends Omit { - menu: AppHeaderMenu; -} - -export function useAppHeader(): AppHeader | undefined { - const appHeader = useStore(state => state.sharedConfig?.header); - if (!appHeader) { - return undefined; - } - const typedMenu: AppHeaderMenu = appHeader.menu.reduce((result, menu) => { - if (isMenu(menu)) { - const linksItem: AppHeaderMenuLinksItem = { ...menu, type: MenuItemType.MENU }; - return [...result, linksItem]; - } - if (isMenuLink(menu)) { - const linkItem: AppHeaderMenuLinkItem = { ...menu, type: MenuItemType.LINK }; - return [...result, linkItem]; - } - console.error('Unknown menu type passed to useAppHeader, ignoring', menu); - return result; - }, []); - - return { - ...appHeader, - menu: typedMenu - }; -} diff --git a/packages/store/src/useBreadcrumbs.ts b/packages/store/src/useBreadcrumbs.ts deleted file mode 100644 index d402cf37..00000000 --- a/packages/store/src/useBreadcrumbs.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useStore } from './store'; - -export type { Breadcrumb } from './types'; - -export function useBreadcrumbs(minCrumbs = 1) { - const breadcrumbs = useStore(state => state.breadcrumbs); - return { - breadcrumbs: breadcrumbs || [], - enabled: breadcrumbs?.length > minCrumbs - }; -} diff --git a/packages/store/src/useColorMode.ts b/packages/store/src/useColorMode.ts index 66c34926..bcf0f842 100644 --- a/packages/store/src/useColorMode.ts +++ b/packages/store/src/useColorMode.ts @@ -1,8 +1,15 @@ -import { useStore } from './store'; +import { useStore } from './useStore'; -import { ColorMode } from './types/colorMode'; +export const lightMode = 'light'; +export const darkMode = 'dark'; -export function useColorMode(): ColorMode { - const colorMode = useStore(state => state.colorMode); - return colorMode; +export type ColorMode = typeof lightMode | typeof darkMode; +export type SetColorMode = (colorMode: ColorMode) => void; + +export function useColorMode(): { colorMode: ColorMode; setColorMode: SetColorMode } { + const result = useStore(state => ({ + colorMode: state.colorMode, + setColorMode: state.actions.setColorMode + })); + return result; } diff --git a/packages/store/src/useFooter.ts b/packages/store/src/useFooter.ts deleted file mode 100644 index da4e36fb..00000000 --- a/packages/store/src/useFooter.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useStore } from './store'; -import type { FooterSlice } from './types'; - -type Footer = FooterSlice | undefined; - -export function useFooter(): Footer { - const footer = useStore(state => state.sharedConfig?.footer); - - return footer; -} diff --git a/packages/store/src/useImageComponent.ts b/packages/store/src/useImageComponent.ts new file mode 100644 index 00000000..6ea25dc3 --- /dev/null +++ b/packages/store/src/useImageComponent.ts @@ -0,0 +1,7 @@ +import type { ElementType } from 'react'; +import { useStore } from './useStore'; + +export function useImageComponent(): ElementType { + const component = useStore(state => state.ImageComponent); + return component; +} diff --git a/packages/store/src/useLayout.ts b/packages/store/src/useLayout.ts deleted file mode 100644 index 5c284cd8..00000000 --- a/packages/store/src/useLayout.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useStore } from './store'; -import type { LayoutSlice } from './types'; - -type Layout = LayoutSlice; - -export function useLayout(): Layout { - const layout = useStore(state => state.layout); - return { layout }; -} diff --git a/packages/store/src/useLinkComponent.ts b/packages/store/src/useLinkComponent.ts new file mode 100644 index 00000000..95ecd579 --- /dev/null +++ b/packages/store/src/useLinkComponent.ts @@ -0,0 +1,7 @@ +import type { ElementType } from 'react'; +import { useStore } from './useStore'; + +export function useLinkComponent(): ElementType { + const component = useStore(state => state.LinkComponent); + return component; +} diff --git a/packages/store/src/useMeta.ts b/packages/store/src/useMeta.ts deleted file mode 100644 index 5b3746f0..00000000 --- a/packages/store/src/useMeta.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useStore } from './store'; -import type { MetaSlice } from './types'; - -export type Meta = { - meta: MetaSlice; -}; - -export function useMeta(): Meta { - const meta = useStore(state => ({ - description: state.description, - breadcrumbs: state.breadcrumbs, - title: state.title - })); - return { - meta - }; -} diff --git a/packages/store/src/useNavigation.ts b/packages/store/src/useNavigation.ts deleted file mode 100644 index cd54fdac..00000000 --- a/packages/store/src/useNavigation.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useStore } from './store'; -import type { Navigation } from './types'; - -export function useNavigation(): Navigation { - const { next, prev } = useStore(state => state.navigation) || {}; - return { - next, - prev - }; -} diff --git a/packages/store/src/useRoute.ts b/packages/store/src/useRoute.ts deleted file mode 100644 index d67286e5..00000000 --- a/packages/store/src/useRoute.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useStore } from './store'; -import type { RouteSlice } from './types'; - -export function useRoute(): RouteSlice { - const route = useStore(state => state.route); - return { - route - }; -} diff --git a/packages/store/src/useSearchIndex.ts b/packages/store/src/useSearchIndex.ts deleted file mode 100644 index 1f81fc9a..00000000 --- a/packages/store/src/useSearchIndex.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useStore } from './store'; - -export type { SearchIndexSlice } from './types'; - -export function useSearchIndex() { - const searchIndex = useStore(state => state.searchIndex); - const searchConfig = useStore(state => state.searchConfig); - - const searchEnabled = - searchConfig !== undefined && searchIndex !== undefined && searchIndex.length > 0; - - return { - searchEnabled, - searchIndex: searchEnabled ? searchIndex : [], - searchConfig: searchEnabled ? searchConfig : {} - }; -} diff --git a/packages/store/src/useSidebar.ts b/packages/store/src/useSidebar.ts deleted file mode 100644 index 3228fe31..00000000 --- a/packages/store/src/useSidebar.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useStore } from './store'; -import { useBreadcrumbs } from './useBreadcrumbs'; -import { useRoute } from './useRoute'; -import type { Breadcrumb } from './types'; - -export function useSidebar() { - const menu = useStore(state => state.sidebarData) || []; - const { route } = useRoute(); - const { breadcrumbs } = useBreadcrumbs(); - - return { - menu, - selectedNodeId: route, - selectedGroupIds: getIds(breadcrumbs) - }; -} -function getIds(breadcrumbs: Breadcrumb[]) { - const pathIds = breadcrumbs.reduce( - (result, { id }) => [...result, id.replace(/\/[^\/]*$/, '')], - [] - ); - return new Set(pathIds); -} diff --git a/packages/store/src/useStore.ts b/packages/store/src/useStore.ts new file mode 100644 index 00000000..4549280d --- /dev/null +++ b/packages/store/src/useStore.ts @@ -0,0 +1,16 @@ +import { useContext } from 'react'; +import { useStore as useZustandStore } from 'zustand'; + +import { StoreState } from './store'; +import { StoreContext } from './StoreContext'; + +/** + * Hook providing access to state stored in the site store + */ +export function useStore(selector: (state: StoreState) => T): T { + const storeFromContext = useContext(StoreContext); + if (!storeFromContext) { + throw new Error('Missing StoreProvider in the tree'); + } + return useZustandStore(storeFromContext, selector); +} diff --git a/packages/store/src/useStoreActions.ts b/packages/store/src/useStoreActions.ts deleted file mode 100644 index 0230d6c0..00000000 --- a/packages/store/src/useStoreActions.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { useStore } from './store'; -export const useStoreActions = () => useStore(state => state.actions); diff --git a/packages/store/src/useTableOfContents.ts b/packages/store/src/useTableOfContents.ts deleted file mode 100644 index 959da2c0..00000000 --- a/packages/store/src/useTableOfContents.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { useStore } from './store'; - -export function useTableOfContents() { - const tableOfContents = useStore(state => state.tableOfContents) || []; - return { - tableOfContents - }; -} diff --git a/packages/theme/package.json b/packages/theme/package.json index 6e2b644d..3f8be3dd 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -42,7 +42,6 @@ "fast-glob": "^3.2.7" }, "dependencies": { - "@salt-ds/icons": "^1.12.1", "@vanilla-extract/css": "^1.6.0", "@vanilla-extract/css-utils": "^0.1.1", "@vanilla-extract/sprinkles": "^1.3.0", @@ -51,7 +50,7 @@ }, "peerDependencies": { "@types/react": "^18.3.12", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react": "^18.3.0", + "react-dom": "^18.3.0" } } diff --git a/packages/theme/src/baseline/baseline.css.ts b/packages/theme/src/baseline/baseline.css.ts index 7e09bc12..f9be85be 100644 --- a/packages/theme/src/baseline/baseline.css.ts +++ b/packages/theme/src/baseline/baseline.css.ts @@ -1,5 +1,5 @@ import { globalStyle } from '@vanilla-extract/css'; -import { ssrClassName } from '../ssrClassName'; +import { config } from '../config'; globalStyle('html, body', { fontFamily: 'var(--salt-text-fontFamily, "Open Sans")', @@ -16,7 +16,7 @@ globalStyle('*', { boxSizing: 'border-box' }); -globalStyle('p,h1, h2, h3, h4, h5, h6, ul, ol, li, pre', { +globalStyle('p,h1, h2, h3, h4, h5, h6, ul, ol, li, pre, figure', { margin: 0, fontFamily: 'inherit' }); @@ -41,6 +41,6 @@ globalStyle('li > p', { display: 'inline' }); -globalStyle(`.${ssrClassName} svg, .${ssrClassName} img`, { +globalStyle(`.${config.ssrClassName} svg, .${config.ssrClassName} img`, { display: 'none' }); diff --git a/packages/theme/src/color/callout.css.ts b/packages/theme/src/color/callout.css.ts index 0bbe20be..42d4c7d1 100644 --- a/packages/theme/src/color/callout.css.ts +++ b/packages/theme/src/color/callout.css.ts @@ -1,5 +1,5 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; -import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; +import { recipe } from '@vanilla-extract/recipes'; import { vars } from '../vars.css'; import { bothModeConditions } from './modes'; @@ -16,7 +16,6 @@ export const calloutColorProperties = defineProperties({ } }); export const calloutColorSprinkles = createSprinkles(calloutColorProperties); -export type CalloutColorSprinkles = Parameters[0]; export const calloutColor = recipe({ variants: { variant: { @@ -53,42 +52,3 @@ export const calloutColor = recipe({ } } }); -export type CalloutColorVariants = RecipeVariants; - -export const calloutBackgroundColor = recipe({ - variants: { - variant: { - note: calloutColorSprinkles({ - backgroundColor: { - lightMode: vars.color.light.callout.note, - darkMode: vars.color.dark.callout.note - } - }), - important: calloutColorSprinkles({ - backgroundColor: { - lightMode: vars.color.light.callout.important, - darkMode: vars.color.dark.callout.important - } - }), - tip: calloutColorSprinkles({ - backgroundColor: { - lightMode: vars.color.light.callout.tip, - darkMode: vars.color.dark.callout.tip - } - }), - caution: calloutColorSprinkles({ - backgroundColor: { - lightMode: vars.color.light.callout.caution, - darkMode: vars.color.dark.callout.caution - } - }), - warning: calloutColorSprinkles({ - backgroundColor: { - lightMode: vars.color.light.callout.warning, - darkMode: vars.color.dark.callout.warning - } - }) - } - } -}); -export type CalloutBackgroundColorVariants = RecipeVariants; diff --git a/packages/theme/src/config.ts b/packages/theme/src/config.ts index e26ec304..936321e4 100644 --- a/packages/theme/src/config.ts +++ b/packages/theme/src/config.ts @@ -1,7 +1,8 @@ /** Some values we want to make available to the JS code, rather than via CSS vars */ export const config = { appHeader: { height: 44 }, - main: { width: 1128, wideWidth: 1440 } + main: { width: 1128, wideWidth: 1440 }, + ssrClassName: 'mosaic-ssr' }; export type ThemeConfig = typeof config; diff --git a/packages/theme/src/gutter/index.css.ts b/packages/theme/src/gutter/index.css.ts new file mode 100644 index 00000000..65226c87 --- /dev/null +++ b/packages/theme/src/gutter/index.css.ts @@ -0,0 +1,23 @@ +import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; +import { calc } from '@vanilla-extract/css-utils'; + +import { vars } from '../vars.css'; + +export const gutterElement = recipe({ + variants: { + variant: { + anchor: { + marginLeft: vars.space.horizontal.anchor + }, + accent: { + marginLeft: vars.space.horizontal.gutter, + paddingLeft: calc(vars.space.horizontal.gutter) + .multiply(-1) + .subtract(vars.border.width.thick) + .toString() + } + } + } +}); + +export type GutterElementVariants = RecipeVariants; diff --git a/packages/theme/src/index.ts b/packages/theme/src/index.ts index a162bd6f..6069fee2 100644 --- a/packages/theme/src/index.ts +++ b/packages/theme/src/index.ts @@ -11,7 +11,6 @@ import { import { shadow } from './shadow'; import { vars } from './vars.css'; -export * from './ssrClassName'; export { themeClassName, vars } from './vars.css'; export * from './animation'; export * from './blockquote'; @@ -22,10 +21,12 @@ export * from './componentExample'; export * from './config'; export * from './feature'; export * from './grid'; +export * from './gutter/index.css'; export * from './hero'; export * from './impact'; export * from './link'; export * from './list'; +export * from './pre/index.css'; export * from './responsive'; export * from './shadow'; export * from './sidebar'; diff --git a/packages/theme/src/pre/index.css.ts b/packages/theme/src/pre/index.css.ts new file mode 100644 index 00000000..7e350362 --- /dev/null +++ b/packages/theme/src/pre/index.css.ts @@ -0,0 +1,11 @@ +import { globalStyle } from '@vanilla-extract/css'; +import { spaceVars } from '../responsive/vars.css'; + +globalStyle('pre', { + overflowX: 'auto', + padding: `${spaceVars.vertical.x2} ${spaceVars.horizontal.none}` +}); + +globalStyle('pre [data-line]', { + overflowX: 'auto' +}); diff --git a/packages/theme/src/responsive/responsive.css.ts b/packages/theme/src/responsive/responsive.css.ts index 1e0a0363..462387c4 100644 --- a/packages/theme/src/responsive/responsive.css.ts +++ b/packages/theme/src/responsive/responsive.css.ts @@ -1,9 +1,7 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; -import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { calc } from '@vanilla-extract/css-utils'; import { breakpoint } from './breakpoint'; -import { vars } from '../vars.css'; +import { spaceVars } from './vars.css'; export const responsiveConditions = { mobile: { '@media': `screen and (min-width: ${breakpoint.mobile}px)` }, @@ -12,114 +10,46 @@ export const responsiveConditions = { desktop: { '@media': `screen and (min-width: ${breakpoint.desktop}px)` } }; -export const marginProperties = defineProperties({ - conditions: responsiveConditions, - defaultCondition: 'mobile', - responsiveArray: ['mobile', 'tablet', 'web', 'desktop'], - properties: { - marginTop: vars.space.vertical, - marginRight: vars.space.horizontal, - marginBottom: vars.space.vertical, - marginLeft: vars.space.horizontal - }, - shorthands: { - margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], - marginX: ['marginLeft', 'marginRight'], - marginY: ['marginTop', 'marginBottom'] - } -}); - -export const paddingProperties = defineProperties({ - conditions: responsiveConditions, - defaultCondition: 'mobile', - responsiveArray: ['mobile', 'tablet', 'web', 'desktop'], - properties: { - paddingTop: vars.space.vertical, - paddingRight: vars.space.horizontal, - paddingBottom: vars.space.vertical, - paddingLeft: vars.space.horizontal - }, - shorthands: { - padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], - paddingX: ['paddingLeft', 'paddingRight'], - paddingY: ['paddingTop', 'paddingBottom'] - } -}); - -export const positionProperties = defineProperties({ - conditions: responsiveConditions, - defaultCondition: 'mobile', - responsiveArray: ['mobile', 'tablet', 'web', 'desktop'], - properties: { - top: vars.space.vertical, - right: vars.space.horizontal, - bottom: vars.space.vertical, - left: vars.space.horizontal - } -}); - export const responsiveProperties = defineProperties({ conditions: responsiveConditions, defaultCondition: 'mobile', responsiveArray: ['mobile', 'tablet', 'web', 'desktop'], properties: { + // margin + marginTop: spaceVars.vertical, + marginRight: spaceVars.horizontal, + marginBottom: spaceVars.vertical, + marginLeft: spaceVars.horizontal, + // padding + paddingTop: spaceVars.vertical, + paddingRight: spaceVars.horizontal, + paddingBottom: spaceVars.vertical, + paddingLeft: spaceVars.horizontal, + // position + top: spaceVars.vertical, + right: spaceVars.horizontal, + bottom: spaceVars.vertical, + left: spaceVars.horizontal, + // flexbox display: ['none', 'flex', 'initial', 'inherit'], flexDirection: ['row', 'row-reverse', 'column', 'column-reverse'], flexWrap: ['wrap', 'nowrap'], - width: ['100%'] - } -}); - -export const gapProperties = defineProperties({ - conditions: responsiveConditions, - defaultCondition: 'mobile', - responsiveArray: ['mobile', 'tablet', 'web', 'desktop'], - properties: { - columnGap: vars.space.horizontal, - rowGap: vars.space.vertical + width: ['100%'], + // gap + columnGap: spaceVars.horizontal, + rowGap: spaceVars.vertical }, shorthands: { + margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], + marginX: ['marginLeft', 'marginRight'], + marginY: ['marginTop', 'marginBottom'], + padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], + paddingX: ['paddingLeft', 'paddingRight'], + paddingY: ['paddingTop', 'paddingBottom'], gap: ['columnGap', 'rowGap'] } }); -export const marginSprinkles = createSprinkles(marginProperties); -export type MarginSprinkles = Parameters[0]; - -export const paddingSprinkles = createSprinkles(paddingProperties); -export type PaddingSprinkles = Parameters[0]; - -export const positioningSprinkles = createSprinkles(positionProperties); -export type PositioningSprinkles = Parameters[0]; - -export const gapSprinkles = createSprinkles(gapProperties); -export type GapSprinkles = Parameters[0]; - -export const responsiveSprinkles = createSprinkles( - marginProperties, - paddingProperties, - positionProperties, - responsiveProperties, - gapProperties -); +export const responsiveSprinkles = createSprinkles(responsiveProperties); export type ResponsiveSprinkles = Parameters[0]; - -export const gutterElement = recipe({ - variants: { - variant: { - anchor: { - marginLeft: vars.space.horizontal.anchor - }, - accent: { - marginLeft: vars.space.horizontal.gutter, - paddingLeft: calc(vars.space.horizontal.gutter) - .multiply(-1) - .subtract(vars.border.width.thick) - .toString() - } - } - } -}); - -export type GutterElementVariants = RecipeVariants; diff --git a/packages/theme/src/ssrClassName.ts b/packages/theme/src/ssrClassName.ts deleted file mode 100644 index e1734c00..00000000 --- a/packages/theme/src/ssrClassName.ts +++ /dev/null @@ -1 +0,0 @@ -export const ssrClassName = 'mosaic-ssr'; diff --git a/packages/theme/src/typography/action.css.ts b/packages/theme/src/typography/action.css.ts index d25bba1e..81d9d79c 100644 --- a/packages/theme/src/typography/action.css.ts +++ b/packages/theme/src/typography/action.css.ts @@ -1,8 +1,10 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; -import { vars } from '../vars.css'; +import { responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const actionProperties = defineProperties({ properties: { @@ -31,11 +33,7 @@ const actionProperties = defineProperties({ } }); -export const actionSprinkles = createSprinkles( - actionProperties, - marginProperties, - paddingProperties -); +export const actionSprinkles = createSprinkles(actionProperties); export type ActionSprinkles = Parameters[0]; diff --git a/packages/theme/src/typography/amount.css.ts b/packages/theme/src/typography/amount.css.ts index 61d274d0..de2c9efb 100644 --- a/packages/theme/src/typography/amount.css.ts +++ b/packages/theme/src/typography/amount.css.ts @@ -1,8 +1,10 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; +import { responsiveSprinkles } from '../responsive'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const amountProperties = defineProperties({ properties: { @@ -20,11 +22,7 @@ const amountProperties = defineProperties({ } }); -export const amountSprinkles = createSprinkles( - amountProperties, - marginProperties, - paddingProperties -); +export const amountSprinkles = createSprinkles(amountProperties); export type AmountSprinkles = Parameters[0]; diff --git a/packages/theme/src/typography/caption.css.ts b/packages/theme/src/typography/caption.css.ts index daa4cca6..7acc8d84 100644 --- a/packages/theme/src/typography/caption.css.ts +++ b/packages/theme/src/typography/caption.css.ts @@ -1,8 +1,10 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; -import { vars } from '../vars.css'; +import { responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const captionProperties = defineProperties({ properties: { @@ -29,11 +31,7 @@ const captionProperties = defineProperties({ } }); -export const captionSprinkles = createSprinkles( - captionProperties, - marginProperties, - paddingProperties -); +export const captionSprinkles = createSprinkles(captionProperties); export type CaptionSprinkles = Parameters[0]; diff --git a/packages/theme/src/typography/code.css.ts b/packages/theme/src/typography/code.css.ts index 1e4f84af..193e6e57 100644 --- a/packages/theme/src/typography/code.css.ts +++ b/packages/theme/src/typography/code.css.ts @@ -1,9 +1,12 @@ -import { style } from '@vanilla-extract/css'; +import { style, globalStyle } from '@vanilla-extract/css'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { backgroundColor, foregroundColor } from '../color'; +import { spaceVars } from '../responsive/vars.css'; import { neutralBorder } from '../border'; +import { fontSizeVars, fontWeightVars } from './vars.css'; +import { lightMode, darkMode, backgroundColor } from '../color'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; export const code = recipe({ variants: { @@ -11,19 +14,19 @@ export const code = recipe({ variant: { regular: style([ { + fontFamily: 'var(--salt-typography-fontFamily-code)', fontSize: vars.fontSize.s70, - paddingLeft: vars.space.horizontal.x2, - paddingRight: vars.space.horizontal.x2, - whiteSpace: 'nowrap', + paddingLeft: spaceVars.horizontal.x2, + paddingRight: spaceVars.horizontal.x2, + whiteSpace: 'pre-wrap', selectors: { ['code.&']: { fontWeight: vars.fontWeight.light } } }, - neutralBorder({ variant: 'low', borderWidth: 'thin' }), backgroundColor({ variant: 'emphasis' }), - foregroundColor({ variant: 'mid' }) + neutralBorder({ variant: 'low', borderWidth: 'thin' }) ]) } }, @@ -32,3 +35,23 @@ export const code = recipe({ } }); export type CodeVariants = RecipeVariants; + +/** + * Apply correct colors in light/dark mode + * https://rehype-pretty-code.netlify.app/#multiple-themes-dark-and-light-mode + */ +globalStyle( + `${lightMode} code[data-theme*=" "], + ${lightMode} code[data-theme*=" "] span`, + { + color: 'var(--shiki-light)' + } +); + +globalStyle( + `${darkMode} code[data-theme*=" "], + ${darkMode} code[data-theme*=" "] span`, + { + color: 'var(--shiki-dark)' + } +); diff --git a/packages/theme/src/typography/eyebrow.css.ts b/packages/theme/src/typography/eyebrow.css.ts index 1fd19ae0..885e02cb 100644 --- a/packages/theme/src/typography/eyebrow.css.ts +++ b/packages/theme/src/typography/eyebrow.css.ts @@ -1,8 +1,10 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; +import { responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const eyebrowProperties = defineProperties({ properties: { @@ -20,11 +22,7 @@ const eyebrowProperties = defineProperties({ } }); -export const eyebrowSprinkles = createSprinkles( - eyebrowProperties, - marginProperties, - paddingProperties -); +export const eyebrowSprinkles = createSprinkles(eyebrowProperties); export type EyebrowSprinkles = Parameters[0]; diff --git a/packages/theme/src/typography/heading.css.ts b/packages/theme/src/typography/heading.css.ts index db7b148f..a713d1f4 100644 --- a/packages/theme/src/typography/heading.css.ts +++ b/packages/theme/src/typography/heading.css.ts @@ -1,13 +1,11 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { - marginProperties, - paddingProperties, - responsiveConditions, - responsiveSprinkles -} from '../responsive'; +import { spaceVars } from '../responsive/vars.css'; +import { responsiveConditions, responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const headingProperties = defineProperties({ conditions: responsiveConditions, @@ -39,11 +37,7 @@ const headingProperties = defineProperties({ } }); -export const headingSprinkles = createSprinkles( - headingProperties, - marginProperties, - paddingProperties -); +export const headingSprinkles = createSprinkles(headingProperties); export type HeadingSprinkles = Parameters[0]; @@ -110,7 +104,7 @@ export const heading = recipe({ context: 'markdown' }, style: { - marginTop: vars.space.vertical.none + marginTop: spaceVars.vertical.none } }, { @@ -119,7 +113,7 @@ export const heading = recipe({ context: 'markdown' }, style: { - marginTop: vars.space.vertical.none + marginTop: spaceVars.vertical.none } }, { @@ -128,7 +122,7 @@ export const heading = recipe({ context: 'markdown' }, style: { - marginTop: vars.space.vertical.x13 + marginTop: spaceVars.vertical.x13 } }, { @@ -137,7 +131,7 @@ export const heading = recipe({ context: 'markdown' }, style: { - marginTop: vars.space.vertical.x10 + marginTop: spaceVars.vertical.x10 } }, { @@ -146,7 +140,7 @@ export const heading = recipe({ context: 'markdown' }, style: { - marginTop: vars.space.vertical.x10 + marginTop: spaceVars.vertical.x10 } } ], diff --git a/packages/theme/src/typography/paragraph.css.ts b/packages/theme/src/typography/paragraph.css.ts index 405f3821..3f41c558 100644 --- a/packages/theme/src/typography/paragraph.css.ts +++ b/packages/theme/src/typography/paragraph.css.ts @@ -1,8 +1,10 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; +import { responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const paragraphProperties = defineProperties({ properties: { @@ -29,11 +31,7 @@ const paragraphProperties = defineProperties({ } }); -export const paragraphSprinkles = createSprinkles( - paragraphProperties, - marginProperties, - paddingProperties -); +export const paragraphSprinkles = createSprinkles(paragraphProperties); export type ParagraphSprinkles = Parameters[0]; diff --git a/packages/theme/src/typography/subtitle.css.ts b/packages/theme/src/typography/subtitle.css.ts index d44e991c..e46b1583 100644 --- a/packages/theme/src/typography/subtitle.css.ts +++ b/packages/theme/src/typography/subtitle.css.ts @@ -1,8 +1,10 @@ import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; +import { responsiveSprinkles } from '../responsive'; +import { fontSizeVars, fontWeightVars } from './vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const subtitleProperties = defineProperties({ properties: { @@ -25,11 +27,7 @@ const subtitleProperties = defineProperties({ } }); -export const subtitleSprinkles = createSprinkles( - subtitleProperties, - marginProperties, - paddingProperties -); +export const subtitleSprinkles = createSprinkles(subtitleProperties); export type SubtitleSprinkles = Parameters[0]; diff --git a/packages/theme/src/typography/watermark.css.ts b/packages/theme/src/typography/watermark.css.ts index 9172b22d..9d4cc780 100644 --- a/packages/theme/src/typography/watermark.css.ts +++ b/packages/theme/src/typography/watermark.css.ts @@ -2,9 +2,12 @@ import { style } from '@vanilla-extract/css'; import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; -import { vars } from '../vars.css'; -import { marginProperties, paddingProperties, responsiveSprinkles } from '../responsive'; +import { responsiveSprinkles } from '../responsive'; import { opacity } from '../opacity'; +import { fontSizeVars, fontWeightVars } from './vars.css'; +import { colorVars } from '../color/vars.css'; + +const vars = { fontSize: fontSizeVars, fontWeight: fontWeightVars }; const watermarkProperties = defineProperties({ properties: { @@ -22,11 +25,7 @@ const watermarkProperties = defineProperties({ } }); -export const watermarkSprinkles = createSprinkles( - watermarkProperties, - marginProperties, - paddingProperties -); +export const watermarkSprinkles = createSprinkles(watermarkProperties); export type WatermarkSprinkles = Parameters[0]; @@ -38,6 +37,7 @@ export const watermark = recipe({ selectors: { '&:before': { content: 'open-quote', + color: colorVars.unknown, fontSize: '3em', position: 'absolute', left: '0.25em', diff --git a/packages/types/package.json b/packages/types/package.json index 838bf50d..97acb80d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -30,7 +30,6 @@ "devDependencies": { "@jpmorganchase/mosaic-schemas": "^0.1.0-beta.89", "memfs": "^3.4.12", - "rxjs": "^7.5.5", - "zod": "^3.22.3" + "rxjs": "^7.5.5" } } diff --git a/packages/store/src/types/appHeader.ts b/packages/types/src/appHeader.ts similarity index 66% rename from packages/store/src/types/appHeader.ts rename to packages/types/src/appHeader.ts index 9d5db600..324ec298 100644 --- a/packages/store/src/types/appHeader.ts +++ b/packages/types/src/appHeader.ts @@ -1,4 +1,4 @@ -import { SearchIndexSlice } from './searchIndex'; +import { SearchConfig, SearchIndex } from './searchIndex.js'; /** * [[`MenuItemType`]] defines the type of App header Menu items @@ -25,18 +25,6 @@ export interface MenuLinksItem { type: MenuItemType.MENU; } -/** - * [[`MenuLinksItem`]] defines a menu of links and includes a type - */ -export interface AppHeaderMenuLinksItem { - /** Collection of link options */ - links: MenuLinkItem[]; - /** Title of Tab */ - title: string; - /** Type of Tab */ - type: MenuItemType.MENU; -} - /** * [[`MenuLinkItem`]] define a menu link */ @@ -49,18 +37,6 @@ export interface MenuLinkItem { type: MenuItemType.LINK; } -/** - * [[`MenuLinkItem`]] define a menu link and include a type - */ -export interface AppHeaderMenuLinkItem { - /** URL linked by Tab */ - link: string; - /** Title of Tab */ - title?: string; - /** Type of Tab */ - type: MenuItemType.LINK; -} - /** * [[`AppHeaderSlice`]] specifies the contents of the AppHeader */ @@ -76,5 +52,8 @@ export type AppHeaderSlice = { /** Search namespace, suffix used on env variable `process.env.SEARCH_ENDPOINT` to filter search results to a specific namespace. */ searchNamespace?: string; /** Search index, created by SearchIndexPlugin */ - searchIndex?: SearchIndexSlice; + searchIndex?: { + searchIndex?: SearchIndex; + searchConfig?: SearchConfig; + }; }; diff --git a/packages/store/src/types/breadcrumbs.ts b/packages/types/src/breadcrumbs.ts similarity index 61% rename from packages/store/src/types/breadcrumbs.ts rename to packages/types/src/breadcrumbs.ts index 8365db4a..ac5fc04f 100644 --- a/packages/store/src/types/breadcrumbs.ts +++ b/packages/types/src/breadcrumbs.ts @@ -11,11 +11,3 @@ export type Breadcrumb = { /** Breadcrumb label */ label: string; }; - -/** - * [[`BreadcrumbsSlice`]] is the associated [[`Breadcrumb`]] items - */ -export type BreadcrumbsSlice = { - /** Path described in breadcrumbs */ - breadcrumbs: Breadcrumb[]; -}; diff --git a/packages/store/src/types/footer.ts b/packages/types/src/footer.ts similarity index 87% rename from packages/store/src/types/footer.ts rename to packages/types/src/footer.ts index 0cd90f40..da0051f7 100644 --- a/packages/store/src/types/footer.ts +++ b/packages/types/src/footer.ts @@ -1,4 +1,4 @@ -import { HelpLinks } from './helpLinks'; +import { HelpLinks } from './helpLinks.js'; /** * [[`FooterSlice`]] specifies the footer props diff --git a/packages/store/src/types/helpLinks.ts b/packages/types/src/helpLinks.ts similarity index 100% rename from packages/store/src/types/helpLinks.ts rename to packages/types/src/helpLinks.ts diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index aed4209a..2d0c5165 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -7,6 +7,15 @@ import type { SourceSchedule } from '@jpmorganchase/mosaic-schemas'; +export * from './appHeader.js'; +export * from './breadcrumbs.js'; +export * from './footer.js'; +export * from './helpLinks.js'; +export * from './navigation.js'; +export * from './searchIndex.js'; +export * from './sharedConfig.js'; +export * from './sidebar.js'; +export * from './tableOfContents.js'; export * from './Content.js'; export * from './IFileAccess.js'; export * from './Meta.js'; diff --git a/packages/store/src/types/navigation.ts b/packages/types/src/navigation.ts similarity index 86% rename from packages/store/src/types/navigation.ts rename to packages/types/src/navigation.ts index 5e9936cb..fa28324d 100644 --- a/packages/store/src/types/navigation.ts +++ b/packages/types/src/navigation.ts @@ -19,7 +19,3 @@ export type Navigation = { /** previous page in sequence */ prev?: NavigationLink; }; - -export type NavigationSlice = { - navigation?: Navigation; -}; diff --git a/packages/store/src/types/searchIndex.ts b/packages/types/src/searchIndex.ts similarity index 78% rename from packages/store/src/types/searchIndex.ts rename to packages/types/src/searchIndex.ts index 3085cb16..4d20f93a 100644 --- a/packages/store/src/types/searchIndex.ts +++ b/packages/types/src/searchIndex.ts @@ -14,8 +14,3 @@ export type SearchConfig = { threshold?: number; keys?: string[] | { name: string; weight: number }[]; }; - -export type SearchIndexSlice = { - searchIndex?: SearchIndex; - searchConfig?: SearchConfig; -}; diff --git a/packages/store/src/types/sharedConfig.ts b/packages/types/src/sharedConfig.ts similarity index 54% rename from packages/store/src/types/sharedConfig.ts rename to packages/types/src/sharedConfig.ts index 2f3fd8da..d5d70572 100644 --- a/packages/store/src/types/sharedConfig.ts +++ b/packages/types/src/sharedConfig.ts @@ -1,5 +1,5 @@ -import type { AppHeaderSlice } from './appHeader'; -import type { FooterSlice } from './footer'; +import type { AppHeaderSlice } from './appHeader.js'; +import type { FooterSlice } from './footer.js'; export type SharedConfig = { /** Footer props */ @@ -9,7 +9,3 @@ export type SharedConfig = { /** Sidebar props */ sidebar?: Pick; }; - -export type SharedConfigSlice = { - sharedConfig?: SharedConfig; -}; diff --git a/packages/store/src/types/sidebar.ts b/packages/types/src/sidebar.ts similarity index 82% rename from packages/store/src/types/sidebar.ts rename to packages/types/src/sidebar.ts index f7828286..bc7e8e7f 100644 --- a/packages/store/src/types/sidebar.ts +++ b/packages/types/src/sidebar.ts @@ -30,11 +30,3 @@ export interface SidebarGroup { } export type SidebarItem = SidebarNode | SidebarGroup; - -/** - * [[`SidebarSlice`]] specifies sidebar content - */ -export type SidebarSlice = { - /** Sidebar items */ - sidebarData: SidebarItem[]; -}; diff --git a/packages/store/src/types/tableOfContents.ts b/packages/types/src/tableOfContents.ts similarity index 56% rename from packages/store/src/types/tableOfContents.ts rename to packages/types/src/tableOfContents.ts index 7d3becc7..a63cb383 100644 --- a/packages/store/src/types/tableOfContents.ts +++ b/packages/types/src/tableOfContents.ts @@ -9,11 +9,3 @@ export type TableOfContentsItem = { /** The heading text */ text: string; }; - -/** - * [[`TableOfContentsSlice`]] defines the table of contents for a page - */ -export type TableOfContentsSlice = { - /** Path described in breadcrumbs */ - tableOfContents: TableOfContentsItem[]; -}; diff --git a/yarn.lock b/yarn.lock index f54ee7fe..6f0d9362 100644 --- a/yarn.lock +++ b/yarn.lock @@ -623,156 +623,128 @@ "@babel/code-frame@7.12.11": version "7.12.11" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" - integrity sha1-9K1DWqJj25NbjxDyxVLSP7cWpj8= + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" - integrity sha1-REFra9diS5mPWxr11HCFbEATh4k= +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" + integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== dependencies: - "@babel/highlight" "^7.16.7" - -"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" - integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== - dependencies: - "@babel/highlight" "^7.24.7" + "@babel/helper-validator-identifier" "^7.25.9" + js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/compat-data@^7.25.2": - version "7.25.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" - integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== +"@babel/compat-data@^7.25.9": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" + integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== "@babel/core@^7.23.9": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" - integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" + integrity sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.0" - "@babel/helper-compilation-targets" "^7.25.2" - "@babel/helper-module-transforms" "^7.25.2" - "@babel/helpers" "^7.25.0" - "@babel/parser" "^7.25.0" - "@babel/template" "^7.25.0" - "@babel/traverse" "^7.25.2" - "@babel/types" "^7.25.2" + "@babel/code-frame" "^7.26.0" + "@babel/generator" "^7.26.0" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.0" + "@babel/parser" "^7.26.0" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.26.0" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.25.0", "@babel/generator@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" - integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== +"@babel/generator@^7.25.9", "@babel/generator@^7.26.0": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.2.tgz#87b75813bec87916210e5e01939a4c823d6bb74f" + integrity sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw== dependencies: - "@babel/types" "^7.25.6" + "@babel/parser" "^7.26.2" + "@babel/types" "^7.26.0" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" - jsesc "^2.5.1" + jsesc "^3.0.2" -"@babel/helper-compilation-targets@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" - integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== +"@babel/helper-compilation-targets@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz#55af025ce365be3cdc0c1c1e56c6af617ce88875" + integrity sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ== dependencies: - "@babel/compat-data" "^7.25.2" - "@babel/helper-validator-option" "^7.24.8" - browserslist "^4.23.1" + "@babel/compat-data" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-module-imports@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" - integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== +"@babel/helper-module-imports@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715" + integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw== dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" -"@babel/helper-module-transforms@^7.25.2": - version "7.25.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" - integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== +"@babel/helper-module-transforms@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" + integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-simple-access" "^7.24.7" - "@babel/helper-validator-identifier" "^7.24.7" - "@babel/traverse" "^7.25.2" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" "@babel/helper-plugin-utils@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== -"@babel/helper-simple-access@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" - integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== - dependencies: - "@babel/traverse" "^7.24.7" - "@babel/types" "^7.24.7" - -"@babel/helper-string-parser@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" - integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" - integrity sha1-6MYCQ4xKgZV1EkPakDHRYH0kfK0= - -"@babel/helper-validator-identifier@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" - integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== - -"@babel/helper-validator-option@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" - integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== - -"@babel/helpers@^7.25.0": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" - integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== - dependencies: - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" - -"@babel/highlight@^7.10.4", "@babel/highlight@^7.16.7": - version "7.17.12" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.17.12.tgz#257de56ee5afbd20451ac0a75686b6b404257351" - integrity sha1-JX3lbuWvvSBFGsCnVoa2tAQlc1E= - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" +"@babel/helper-string-parser@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz#1aabb72ee72ed35789b4bbcad3ca2862ce614e8c" + integrity sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA== + +"@babel/helper-validator-identifier@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/helper-validator-option@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72" + integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw== + +"@babel/helpers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.0.tgz#30e621f1eba5aa45fe6f4868d2e9154d884119a4" + integrity sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.0" -"@babel/highlight@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" - integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== +"@babel/highlight@^7.10.4": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.9.tgz#8141ce68fc73757946f983b343f1231f4691acc6" + integrity sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw== dependencies: - "@babel/helper-validator-identifier" "^7.24.7" + "@babel/helper-validator-identifier" "^7.25.9" chalk "^2.4.2" js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.23.9", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" - integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== +"@babel/parser@^7.23.9", "@babel/parser@^7.25.4", "@babel/parser@^7.25.9", "@babel/parser@^7.26.0", "@babel/parser@^7.26.2": + version "7.26.2" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.2.tgz#fd7b6f487cfea09889557ef5d4eeb9ff9a5abd11" + integrity sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ== dependencies: - "@babel/types" "^7.25.6" + "@babel/types" "^7.26.0" "@babel/plugin-syntax-typescript@^7.23.3": version "7.25.9" @@ -781,59 +753,50 @@ dependencies: "@babel/helper-plugin-utils" "^7.25.9" -"@babel/runtime-corejs3@^7.10.2": - version "7.20.0" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.20.0.tgz#56ef7af3cd23d1570969809a5a8782e774e0141a" - integrity sha512-v1JH7PeAAGBEyTQM9TqojVl+b20zXtesFKCJHu50xMxZKD1fX0TKaKHPsZfFkXfs7D1M9M6Eeqg1FkJ3a0x2dA== - dependencies: - core-js-pure "^3.25.1" - regenerator-runtime "^0.13.10" - -"@babel/runtime-corejs3@^7.20.13", "@babel/runtime-corejs3@^7.20.7", "@babel/runtime-corejs3@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.22.5.tgz#bbc769b48edb2bdfd404b65ad1fc3952bf33e3c2" - integrity sha512-TNPDN6aBFaUox2Lu+H/Y1dKKQgr4ucz/FGyCz67RVYLsBpVpUFf1dDngzg+Od8aqbrqwyztkaZjtWCZEUOT8zA== +"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.20.13", "@babel/runtime-corejs3@^7.20.7", "@babel/runtime-corejs3@^7.22.5": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz#5af6bed16073eb4a0191233d61e158a5c768c430" + integrity sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w== dependencies: core-js-pure "^3.30.2" - regenerator-runtime "^0.13.11" + regenerator-runtime "^0.14.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.13", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": - version "7.22.10" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682" - integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ== +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.9", "@babel/runtime@^7.20.13", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.9.2": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" - integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/parser" "^7.25.0" - "@babel/types" "^7.25.0" - -"@babel/traverse@^7.24.7", "@babel/traverse@^7.25.2": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" - integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== - dependencies: - "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.6" - "@babel/parser" "^7.25.6" - "@babel/template" "^7.25.0" - "@babel/types" "^7.25.6" +"@babel/template@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.9.tgz#ecb62d81a8a6f5dc5fe8abfc3901fc52ddf15016" + integrity sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/types" "^7.25.9" + +"@babel/traverse@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.9.tgz#a50f8fe49e7f69f53de5bea7e413cd35c5e13c84" + integrity sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw== + dependencies: + "@babel/code-frame" "^7.25.9" + "@babel/generator" "^7.25.9" + "@babel/parser" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/types" "^7.25.9" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.24.7", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4", "@babel/types@^7.25.6": - version "7.25.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" - integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== +"@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" + integrity sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA== dependencies: - "@babel/helper-string-parser" "^7.24.8" - "@babel/helper-validator-identifier" "^7.24.7" - to-fast-properties "^2.0.0" + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" "@braintree/sanitize-url@=6.0.2", "@braintree/sanitize-url@^6.0.0": version "6.0.2" @@ -1444,12 +1407,12 @@ dependencies: mute-stream "^1.0.0" -"@internationalized/date@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.0.2.tgz#1566a0bcbd82dce4dd54a5b26456bb701068cb89" - integrity sha512-9V1IxesP6ASZj/hYyOXOC4yPJvidbbStyWQKLCQSqhhKACMOXoo+BddXZJy47ju9mqOMpWdrJ2rTx4yTxK9oag== +"@internationalized/date@^3.5.5": + version "3.5.6" + resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.6.tgz#0833c2fa75efb3573f4e3bf10e3895f1019e87dd" + integrity sha512-jLxQjefH9VI5P9UQuqB6qNKnvFt1Ky1TPIzHGsIlCi7sZZoMR8SdYbBGRvM0y+Jtb+ez4ieBzmiAUcpmPYpyOw== dependencies: - "@swc/helpers" "^0.4.14" + "@swc/helpers" "^0.5.0" "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -1468,15 +1431,6 @@ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha1-5F44TkuOwWvOL9kDr3hFD2v37Jg= -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -1486,16 +1440,11 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.24" -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": +"@jridgewell/resolve-uri@^3.1.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - "@jridgewell/set-array@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" @@ -1524,14 +1473,6 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz#dcfe3e95f224c8fe97a87a5235defec999aa92ea" - integrity sha1-3P4+lfIkyP6XqHpSNd7+yZmqkuo= - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jsdevtools/ono@^7.1.3": version "7.1.3" resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" @@ -1761,9 +1702,9 @@ read-yaml-file "^1.1.0" "@mdx-js/mdx@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.0.1.tgz#617bd2629ae561fdca1bb88e3badd947f5a82191" - integrity sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-3.1.0.tgz#10235cab8ad7d356c262e8c21c68df5850a97dc3" + integrity sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw== dependencies: "@types/estree" "^1.0.0" "@types/estree-jsx" "^1.0.0" @@ -1771,14 +1712,15 @@ "@types/mdx" "^2.0.0" collapse-white-space "^2.0.0" devlop "^1.0.0" - estree-util-build-jsx "^3.0.0" estree-util-is-identifier-name "^3.0.0" - estree-util-to-js "^2.0.0" + estree-util-scope "^1.0.0" estree-walker "^3.0.0" - hast-util-to-estree "^3.0.0" hast-util-to-jsx-runtime "^2.0.0" markdown-extensions "^2.0.0" - periscopic "^3.0.0" + recma-build-jsx "^1.0.0" + recma-jsx "^1.0.0" + recma-stringify "^1.0.0" + rehype-recma "^1.0.0" remark-mdx "^3.0.0" remark-parse "^11.0.0" remark-rehype "^11.0.0" @@ -1790,9 +1732,9 @@ vfile "^6.0.0" "@mdx-js/react@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.0.1.tgz#997a19b3a5b783d936c75ae7c47cfe62f967f746" - integrity sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A== + version "3.1.0" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-3.1.0.tgz#c4522e335b3897b9a845db1dbdd2f966ae8fb0ed" + integrity sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ== dependencies: "@types/mdx" "^2.0.0" @@ -1808,10 +1750,10 @@ outvariant "^1.2.1" strict-event-emitter "^0.5.1" -"@next/env@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.7.tgz#40fcd6ccdd53fd7e6788a0604f39032c84bea112" - integrity sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ== +"@next/env@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.17.tgz#bcb2c84fca50a38ec5b492d1029b8f41b0900594" + integrity sha512-MCgO7VHxXo8sYR/0z+sk9fGyJJU636JyRmkjc7ZJY8Hurl8df35qG5hoAh5KMs75FLjhlEo9bb2LGe89Y/scDA== "@next/eslint-plugin-next@14.2.8", "@next/eslint-plugin-next@^14.0.0": version "14.2.8" @@ -1820,50 +1762,50 @@ dependencies: glob "10.3.10" -"@next/swc-darwin-arm64@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.7.tgz#6cd39ba5d5f43705de44e389d4b4f5d2df391927" - integrity sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw== - -"@next/swc-darwin-x64@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.7.tgz#a1d191a293443cf8df9451b8f13a348caa718cb7" - integrity sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA== - -"@next/swc-linux-arm64-gnu@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.7.tgz#9da3f993b3754b900fe7b469de51898fc51112f2" - integrity sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ== - -"@next/swc-linux-arm64-musl@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.7.tgz#f75662bdedd2d91ad7e05778274fa17659f1f02f" - integrity sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw== - -"@next/swc-linux-x64-gnu@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.7.tgz#3c6c5b551a5af4fc8178bd5733c8063266034e79" - integrity sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw== - -"@next/swc-linux-x64-musl@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.7.tgz#16f92f00263d1fce91ae80e5f230eb1feea484e4" - integrity sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ== - -"@next/swc-win32-arm64-msvc@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.7.tgz#1224cb8a04cd9caad785a2187df9e85b49414a42" - integrity sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ== - -"@next/swc-win32-ia32-msvc@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.7.tgz#9494aaf9cc50ddef600f8c1b2ed0f216b19f9294" - integrity sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w== - -"@next/swc-win32-x64-msvc@14.2.7": - version "14.2.7" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.7.tgz#75e1d90758cb10a547e1cdfb878871da28123682" - integrity sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA== +"@next/swc-darwin-arm64@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.17.tgz#f1e38727eb94cf198f63fddcb19002bd4cc971dc" + integrity sha512-WiOf5nElPknrhRMTipXYTJcUz7+8IAjOYw3vXzj3BYRcVY0hRHKWgTgQ5439EvzQyHEko77XK+yN9x9OJ0oOog== + +"@next/swc-darwin-x64@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.17.tgz#e29a17ef28d97c347c7d021f391e13b6c8e4c813" + integrity sha512-29y425wYnL17cvtxrDQWC3CkXe/oRrdt8ie61S03VrpwpPRI0XsnTvtKO06XCisK4alaMnZlf8riwZIbJTaSHQ== + +"@next/swc-linux-arm64-gnu@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.17.tgz#10e99c7aa60cc33f8b7633e045f74be9a43e7b0c" + integrity sha512-SSHLZls3ZwNEHsc+d0ynKS+7Af0Nr8+KTUBAy9pm6xz9SHkJ/TeuEg6W3cbbcMSh6j4ITvrjv3Oi8n27VR+IPw== + +"@next/swc-linux-arm64-musl@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.17.tgz#9a5bb809d3c6aef96c409959aedae28b4e5db53d" + integrity sha512-VFge37us5LNPatB4F7iYeuGs9Dprqe4ZkW7lOEJM91r+Wf8EIdViWHLpIwfdDXinvCdLl6b4VyLpEBwpkctJHA== + +"@next/swc-linux-x64-gnu@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.17.tgz#64e0ce01870e6dc45ae48f676d7cce82aedcdc62" + integrity sha512-aaQlpxUVb9RZ41adlTYVQ3xvYEfBPUC8+6rDgmQ/0l7SvK8S1YNJzPmDPX6a4t0jLtIoNk7j+nroS/pB4nx7vQ== + +"@next/swc-linux-x64-musl@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.17.tgz#93114164b6ccfc533908193ab9065f0c3970abc3" + integrity sha512-HSyEiFaEY3ay5iATDqEup5WAfrhMATNJm8dYx3ZxL+e9eKv10XKZCwtZByDoLST7CyBmyDz+OFJL1wigyXeaoA== + +"@next/swc-win32-arm64-msvc@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.17.tgz#4b99dea02178c112e5c33c742f9ff2a49b3b2939" + integrity sha512-h5qM9Btqv87eYH8ArrnLoAHLyi79oPTP2vlGNSg4CDvUiXgi7l0+5KuEGp5pJoMhjuv9ChRdm7mRlUUACeBt4w== + +"@next/swc-win32-ia32-msvc@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.17.tgz#f1c23955405a259b6d45c65f918575b01bcf0106" + integrity sha512-BD/G++GKSLexQjdyoEUgyo5nClU7er5rK0sE+HlEqnldJSm96CIr/+YOTT063LVTT/dUOeQsNgp5DXr86/K7/A== + +"@next/swc-win32-x64-msvc@14.2.17": + version "14.2.17" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.17.tgz#44f5a4fcd8df1396a8d4326510ca2d92fb809cb3" + integrity sha512-vkQfN1+4V4KqDibkW2q0sJ6CxQuXq5l2ma3z0BRcfIqkAMZiiW67T9yCpwqJKP68QghBtPEFjPAlaqe38O6frw== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -2083,10 +2025,10 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz#427d5549943a9c6fce808e39ea64dbe60d4047f1" integrity sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA== -"@salt-ds/core@^1.33.0": - version "1.33.0" - resolved "https://registry.yarnpkg.com/@salt-ds/core/-/core-1.33.0.tgz#ccf0e5cec01f46b047fcde74d8efe956d7cf417a" - integrity sha512-L58s5qKp7crkRcPYvXOzyesswDGcSEy04DBvY4xjU/xeYmCd6AM0wS10ZxBJap/up1GFqqACbgV9W/xgZjvTxw== +"@salt-ds/core@^1.37.1": + version "1.37.1" + resolved "https://registry.yarnpkg.com/@salt-ds/core/-/core-1.37.1.tgz#2b6326e75089de1915e1bfe05a2824d8e8d00da9" + integrity sha512-ubg5el+es9PrvwhaXucx2lRnuAs2dgPKSga+EArcLZlCKMW+ufMztaPOjIvIx5sDfDMhJscxaTlL8Ci4JbhGPA== dependencies: "@floating-ui/react" "^0.26.5" "@salt-ds/icons" "^1.12.1" @@ -2103,15 +2045,15 @@ "@salt-ds/window" "^0.1.1" clsx "^2.0.0" -"@salt-ds/lab@1.0.0-alpha.50": - version "1.0.0-alpha.50" - resolved "https://registry.yarnpkg.com/@salt-ds/lab/-/lab-1.0.0-alpha.50.tgz#b414f258ba345eccf2a2c6c1ca43ff556d321840" - integrity sha512-KKy/H+1oSWNFFmdRT3BuijVJgALA94khRqAjbovPcMcsYC3Cmgfn0b7xxT1A+dzVbtHErFxyj2niUE5tlct0uw== +"@salt-ds/lab@1.0.0-alpha.54": + version "1.0.0-alpha.54" + resolved "https://registry.yarnpkg.com/@salt-ds/lab/-/lab-1.0.0-alpha.54.tgz#02ee0b5e9c6423b28f5ba5d2d525f0f3210813d3" + integrity sha512-0llS9rMiS7vDeEsxmqDDehVqm0Z6vXd/8rNxNhbJ7hP5jk6oKaBIi2MkuqPOUnevLOcG9s54AL+asy2nHouvvg== dependencies: "@floating-ui/react" "^0.26.5" "@fluentui/react-overflow" "^9.0.19" - "@internationalized/date" "^3.0.0" - "@salt-ds/core" "^1.33.0" + "@internationalized/date" "^3.5.5" + "@salt-ds/core" "^1.37.1" "@salt-ds/icons" "^1.12.1" "@salt-ds/styles" "^0.2.1" "@salt-ds/window" "^0.1.1" @@ -2133,62 +2075,62 @@ resolved "https://registry.yarnpkg.com/@salt-ds/styles/-/styles-0.2.1.tgz#d6fc1bee5a8d3931cba4ec8baa14f1ad3d7582a4" integrity sha512-/GYQLY+ILzGyd2/KndCmoEfLw/t3pcYwihJn3ofe4yd6nhLYHPkvl4TXXzq6NnfD3NHmQWnWh3jQicLsYcvdXg== -"@salt-ds/theme@^1.19.0": - version "1.19.0" - resolved "https://registry.yarnpkg.com/@salt-ds/theme/-/theme-1.19.0.tgz#aa619fe1e1248a3780b87d4341f4496bfb5fe84f" - integrity sha512-POe6ca1cHCQe3ehBrSdG79pKB+Zg0EAsNE4D292xxoSpoRSR0I6EtWQ8KM+nrscuZNu0FXHG9rLh8/y9zVBWcg== +"@salt-ds/theme@^1.23.0": + version "1.23.1" + resolved "https://registry.yarnpkg.com/@salt-ds/theme/-/theme-1.23.1.tgz#c2fe8eda0c4dc3602e0cbd5f6573c75e97028cb9" + integrity sha512-LSSlY/jrOQbYVptL92hC4gu8eyagLDFBWQwXeQVJrLIqGBYaEciUJqUdNh9QhAyGlXAWCE6JvT2bUKWRIsOKUw== "@salt-ds/window@^0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@salt-ds/window/-/window-0.1.1.tgz#1c26ab1b3e7457d271b2dd8a58bfbb423aee2cbe" integrity sha512-DKVRbu7YeHdqFECGhC4W3KOF1eWCyGkFyZUEUNZyK4bvPLK1NI8z5JoxGU70dLVsFgjhk4wj3i1MmAVhdXu4lA== -"@sindresorhus/is@^5.2.0": - version "5.3.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.3.0.tgz#0ec9264cf54a527671d990eb874e030b55b70dcc" - integrity sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw== - -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha1-OALd0hpQqUm2ch3dcto25n5/Gy0= +"@shikijs/core@1.22.2": + version "1.22.2" + resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.22.2.tgz#9c22bd4cc8a4d6c062461cfd35e1faa6c617ca25" + integrity sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg== dependencies: - type-detect "4.0.8" + "@shikijs/engine-javascript" "1.22.2" + "@shikijs/engine-oniguruma" "1.22.2" + "@shikijs/types" "1.22.2" + "@shikijs/vscode-textmate" "^9.3.0" + "@types/hast" "^3.0.4" + hast-util-to-html "^9.0.3" -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== +"@shikijs/engine-javascript@1.22.2": + version "1.22.2" + resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.22.2.tgz#62e90dbd2ed1d78b972ad7d0a1f8ffaaf5e43279" + integrity sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw== dependencies: - type-detect "4.0.8" + "@shikijs/types" "1.22.2" + "@shikijs/vscode-textmate" "^9.3.0" + oniguruma-to-js "0.4.3" -"@sinonjs/fake-timers@^10.0.2": - version "10.0.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz#d10549ed1f423d80639c528b6c7f5a1017747d0c" - integrity sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw== +"@shikijs/engine-oniguruma@1.22.2": + version "1.22.2" + resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.2.tgz#b12a44e3faf486e19fbcf8952f4b56b9b9b8d9b8" + integrity sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA== dependencies: - "@sinonjs/commons" "^2.0.0" + "@shikijs/types" "1.22.2" + "@shikijs/vscode-textmate" "^9.3.0" -"@sinonjs/fake-timers@^9.1.2": - version "9.1.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" - integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== +"@shikijs/types@1.22.2": + version "1.22.2" + resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.22.2.tgz#695a283f19963fe0638fc2646862ba5cfc4623a8" + integrity sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg== dependencies: - "@sinonjs/commons" "^1.7.0" + "@shikijs/vscode-textmate" "^9.3.0" + "@types/hast" "^3.0.4" -"@sinonjs/samsam@^7.0.1": - version "7.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-7.0.1.tgz#5b5fa31c554636f78308439d220986b9523fc51f" - integrity sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw== - dependencies: - "@sinonjs/commons" "^2.0.0" - lodash.get "^4.4.2" - type-detect "^4.0.8" +"@shikijs/vscode-textmate@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-9.3.0.tgz#b2f1776e488c1d6c2b6cd129bab62f71bbc9c7ab" + integrity sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA== -"@sinonjs/text-encoding@^0.7.1": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" - integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== +"@sindresorhus/is@^5.2.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-5.3.0.tgz#0ec9264cf54a527671d990eb874e030b55b70dcc" + integrity sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw== "@smithy/abort-controller@^3.1.1": version "3.1.1" @@ -2995,6 +2937,13 @@ dependencies: tslib "^2.4.0" +"@swc/helpers@^0.5.0": + version "0.5.13" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.13.tgz#33e63ff3cd0cade557672bd7888a39ce7d115a8c" + integrity sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w== + dependencies: + tslib "^2.4.0" + "@szmarczak/http-timer@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-5.0.1.tgz#c7c1bf1141cdd4751b0399c8fc7b8b664cd5be3a" @@ -3101,14 +3050,14 @@ dependencies: "@types/unist" "*" -"@types/hast@^3.0.0": +"@types/hast@^3.0.0", "@types/hast@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== dependencies: "@types/unist" "*" -"@types/hoist-non-react-statics@*", "@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -3169,13 +3118,6 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.190.tgz#d8e99647af141c63902d0ca53cf2b34d2df33545" integrity sha512-5iJ3FBJBvQHQ8sFhEhJfjUP+G+LalhavTkYyrAYqz5MEJG+erSv0k9KJLb6q7++17Lafk1scaTIFXcMJlwK8Mw== -"@types/mdast@^3.0.0": - version "3.0.10" - resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" - integrity sha1-RyQkSoKkWYiEy76bz9c9/5J+6K8= - dependencies: - "@types/unist" "*" - "@types/mdast@^4.0.0": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.3.tgz#1e011ff013566e919a4232d1701ad30d70cab333" @@ -3225,12 +3167,7 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@^16.0.0": - version "16.18.119" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.119.tgz#88443bb82119b7c0920e86949673876cbe1c3492" - integrity sha512-ia7V9a2FnhUFfetng4/sRPBMTwHZUkPFY736rb1cg9AgG7MZdR97q7/nLR9om+sq5f1la9C857E0l/nrI0RiFQ== - -"@types/node@^18.0.0", "@types/node@^18.7.19": +"@types/node@^18.0.0", "@types/node@^18.15.3", "@types/node@^18.7.19": version "18.19.64" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.64.tgz#122897fb79f2a9ec9c979bded01c11461b2b1478" integrity sha512-955mDqvO2vFf/oL7V3WiUtiz+BugyX8uVbaT2H8oj3+8dRyH2FLiNdowe7eNqRM7IOIZvzDH76EoAT+gwm6aIQ== @@ -3242,11 +3179,6 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha1-0zV0eaD9/dWQf+Z+F+CoXJBuEwE= -"@types/prismjs@^1.26.0": - version "1.26.4" - resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.4.tgz#1a9e1074619ce1d7322669e5b46fbe823925103a" - integrity sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg== - "@types/prop-types@*": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" @@ -3291,37 +3223,11 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== -"@types/sinon@^10.0.10": - version "10.0.13" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.13.tgz#60a7a87a70d9372d0b7b38cc03e825f46981fb83" - integrity sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ== - dependencies: - "@types/sinonjs__fake-timers" "*" - -"@types/sinonjs__fake-timers@*": - version "8.1.2" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz#bf2e02a3dbd4aecaf95942ecd99b7402e03fad5e" - integrity sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA== - "@types/statuses@^2.0.4": version "2.0.5" resolved "https://registry.yarnpkg.com/@types/statuses/-/statuses-2.0.5.tgz#f61ab46d5352fd73c863a1ea4e1cef3b0b51ae63" integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A== -"@types/styled-components@^5.1.26": - version "5.1.26" - resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.26.tgz#5627e6812ee96d755028a98dae61d28e57c233af" - integrity sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw== - dependencies: - "@types/hoist-non-react-statics" "*" - "@types/react" "*" - csstype "^3.0.2" - -"@types/supports-color@^8.0.0": - version "8.1.1" - resolved "https://registry.yarnpkg.com/@types/supports-color/-/supports-color-8.1.1.tgz#1b44b1b096479273adf7f93c75fc4ecc40a61ee4" - integrity sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw== - "@types/swagger-ui-react@^4.18.0": version "4.18.0" resolved "https://registry.yarnpkg.com/@types/swagger-ui-react/-/swagger-ui-react-4.18.0.tgz#6668a7f44c825f9dbd46e3ecb8a9a40f745ce861" @@ -3366,13 +3272,6 @@ resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== -"@types/ws@^8.5.7": - version "8.5.7" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.7.tgz#1ca585074fe5d2c81dec7a3d451f244a2a6d83cb" - integrity sha512-6UrLjiDUvn40CMrAubXuIVtj2PEfKDffJS7ychvnPU44j+KVeXmdHHTgqcM/dxLUTHxlXHiFM8Skmb8ozGdTnQ== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz#5a5fcad1a7baed85c10080d71ad901f98c38d5b7" @@ -3835,11 +3734,6 @@ ansi-styles@^6.0.0, ansi-styles@^6.1.0: resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== - anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -4108,15 +4002,6 @@ avvio@^8.2.1: debug "^4.0.0" fastq "^1.6.1" -aws-sdk-client-mock@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/aws-sdk-client-mock/-/aws-sdk-client-mock-2.0.1.tgz#c37ec569fa88688d8d88d7a7f028af26d6d2086e" - integrity sha512-Ib/AnI8ZdoIxOBbKSs28TUwJb7FI/AYVYn48PcXx6guk5fBs4GZJJEc+Ci9aImRtVmgO6jHN/6Etz17fr6j3qw== - dependencies: - "@types/sinon" "^10.0.10" - sinon "^14.0.2" - tslib "^2.1.0" - axe-core@^4.10.0: version "4.10.0" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" @@ -4221,15 +4106,15 @@ breakword@^1.0.5: dependencies: wcwidth "^1.0.1" -browserslist@^4.23.1: - version "4.23.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" - integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== +browserslist@^4.24.0: + version "4.24.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.2.tgz#f5845bc91069dbd55ee89faf9822e1d885d16580" + integrity sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg== dependencies: - caniuse-lite "^1.0.30001646" - electron-to-chromium "^1.5.4" + caniuse-lite "^1.0.30001669" + electron-to-chromium "^1.5.41" node-releases "^2.0.18" - update-browserslist-db "^1.1.0" + update-browserslist-db "^1.1.1" buffer@^5.5.0: version "5.7.1" @@ -4338,10 +4223,10 @@ camelcase@^6.3.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001646: - version "1.0.30001655" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz#0ce881f5a19a2dcfda2ecd927df4d5c1684b982f" - integrity sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg== +caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001669: + version "1.0.30001678" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001678.tgz#b930b04cd0b295136405634aa32ad540d7eeb71e" + integrity sha512-RR+4U/05gNtps58PEBDZcPWTgEO2MBeoPZ96aQcjmfkBWRIDfN451fW2qyDA9/+HohLLIL5GqiMwA+IB1pWarw== capital-case@^1.0.4: version "1.0.4" @@ -4368,7 +4253,7 @@ chai@^5.1.2: loupe "^3.1.0" pathval "^2.0.0" -chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha1-zUJUFnelQzPPVBpJEIwUMrRMlCQ= @@ -4502,7 +4387,7 @@ ci-info@^3.7.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== -classnames@^2.2.5, classnames@^2.3.1: +classnames@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== @@ -4562,7 +4447,7 @@ cli-width@^4.1.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5" integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ== -client-only@0.0.1: +client-only@0.0.1, client-only@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== @@ -4660,7 +4545,7 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@7, commander@^4.0.0, commander@^9.3.0, commander@^9.4.0, commander@^9.4.1: +commander@7, commander@^9.3.0, commander@^9.4.0, commander@^9.4.1: version "9.4.1" resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.1.tgz#d1dd8f2ce6faf93147295c0df13c7c21141cfbdd" integrity sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw== @@ -4713,16 +4598,6 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== -cookie-signature@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.2.0.tgz#4deed303f5f095e7a02c979e3fcb19157f5eaeea" - integrity sha512-R0BOPfLGTitaKhgKROKZQN6iyq2iDQcH1DOF8nJoaWapguX5bC2w+Q/I9NmmM5lfcvEarnLZr+cCvmEYYSXvYA== - -cookie@^0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" - integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== - cookie@^0.5.0, cookie@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" @@ -4735,15 +4610,10 @@ copy-to-clipboard@^3.3.1: dependencies: toggle-selection "^1.0.6" -core-js-pure@^3.25.1: - version "3.26.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.26.0.tgz#7ad8a5dd7d910756f3124374b50026e23265ca9a" - integrity sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA== - core-js-pure@^3.30.2: - version "3.31.0" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.31.0.tgz#052fd9e82fbaaf86457f5db1fadcd06f15966ff2" - integrity sha512-/AnE9Y4OsJZicCzIe97JP5XoPKQJfTuEG43aEVLFJGOJpyqELod+pE6LEl63DfG1Mp8wX97LDaDpy1GmLEUxlg== + version "3.39.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.39.0.tgz#aa0d54d70a15bdc13e7c853db87c10abc30d68f3" + integrity sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg== cors@^2.8.5: version "2.8.5" @@ -4767,13 +4637,6 @@ cross-fetch@^3.1.5: dependencies: node-fetch "2.6.7" -cross-fetch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" - integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== - dependencies: - node-fetch "^2.6.12" - cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -5273,11 +5136,6 @@ deep-object-diff@^1.1.9: resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.9.tgz#6df7ef035ad6a0caa44479c536ed7b02570f4595" integrity sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA== -deepmerge@^2.0.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" - integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== - deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" @@ -5387,11 +5245,6 @@ devlop@^1.0.0, devlop@^1.1.0: dependencies: dequal "^2.0.0" -diff@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" - integrity sha1-vFLSmMXqjfkZSAAiREXtQ//IfkA= - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -5423,14 +5276,6 @@ dom-accessibility-api@^0.6.3: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dompurify@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631" @@ -5478,10 +5323,10 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.5.4: - version "1.5.13" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" - integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== +electron-to-chromium@^1.5.41: + version "1.5.53" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.53.tgz#bbae15eb887d00e511e175eba5a7db7902377838" + integrity sha512-7F6qFMWzBArEFK4PLE+c+nWzhS1kIoNkQvGnNDogofxQAym+roQ0GUIdw6C/4YdJ6JKGp19c2a/DLcfKTi4wRQ== emoji-regex@^8.0.0: version "8.0.0" @@ -5515,7 +5360,7 @@ enquirer@^2.3.0, enquirer@^2.3.5: dependencies: ansi-colors "^4.1.1" -entities@^4.4.0: +entities@^4.4.0, entities@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== @@ -5723,6 +5568,26 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esast-util-from-estree@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz#8d1cfb51ad534d2f159dc250e604f3478a79f1ad" + integrity sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + devlop "^1.0.0" + estree-util-visit "^2.0.0" + unist-util-position-from-estree "^2.0.0" + +esast-util-from-js@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz#5147bec34cc9da44accf52f87f239a40ac3e8225" + integrity sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== + dependencies: + "@types/estree-jsx" "^1.0.0" + acorn "^8.0.0" + esast-util-from-estree "^2.0.0" + vfile-message "^4.0.0" + esbuild-node-externals@^1.0.2, esbuild-node-externals@^1.7.0: version "1.15.0" resolved "https://registry.yarnpkg.com/esbuild-node-externals/-/esbuild-node-externals-1.15.0.tgz#9b7c84877779e46a184115746c51ddd3854237e5" @@ -5761,12 +5626,7 @@ esbuild@0.23.1, esbuild@^0.21.3, "esbuild@npm:esbuild@>=0.17.6 <0.24.0": "@esbuild/win32-ia32" "0.23.1" "@esbuild/win32-x64" "0.23.1" -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha1-2M/ccACWXFoBdLSoLqpcBVJ0LkA= - -escalade@^3.1.2: +escalade@^3.1.1, escalade@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== @@ -6177,6 +6037,14 @@ estree-util-is-identifier-name@^3.0.0: resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== +estree-util-scope@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/estree-util-scope/-/estree-util-scope-1.0.0.tgz#9cbdfc77f5cb51e3d9ed4ad9c4adbff22d43e585" + integrity sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== + dependencies: + "@types/estree" "^1.0.0" + devlop "^1.0.0" + estree-util-to-js@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz#10a6fb924814e6abb62becf0d2bc4dea51d04f17" @@ -6186,13 +6054,6 @@ estree-util-to-js@^2.0.0: astring "^1.8.0" source-map "^0.7.0" -estree-util-value-to-estree@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/estree-util-value-to-estree/-/estree-util-value-to-estree-3.1.2.tgz#d2f0e5d350a6c181673eb7299743325b86a9bf5c" - integrity sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag== - dependencies: - "@types/estree" "^1.0.0" - estree-util-visit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/estree-util-visit/-/estree-util-visit-2.0.0.tgz#13a9a9f40ff50ed0c022f831ddf4b58d05446feb" @@ -6777,7 +6638,7 @@ glob@10.3.10: minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry "^1.10.1" -glob@^10.3.10, glob@^10.4.1: +glob@^10.4.1: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -6998,6 +6859,32 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hast-util-from-html@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz#485c74785358beb80c4ba6346299311ac4c49c82" + integrity sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.1.0" + hast-util-from-parse5 "^8.0.0" + parse5 "^7.0.0" + vfile "^6.0.0" + vfile-message "^4.0.0" + +hast-util-from-parse5@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz#654a5676a41211e14ee80d1b1758c399a0327651" + integrity sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^8.0.0" + property-information "^6.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + hast-util-heading-rank@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz#2d5c6f2807a7af5c45f74e623498dd6054d2aba8" @@ -7010,19 +6897,12 @@ hast-util-parse-selector@^2.0.0: resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== -hast-util-properties-to-mdx-jsx-attributes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hast-util-properties-to-mdx-jsx-attributes/-/hast-util-properties-to-mdx-jsx-attributes-1.0.0.tgz#c40f9f07b74f9b323c1cf8dc14beb17d4d79d12c" - integrity sha512-MZEdAYiXC8wDBfntAc7syyWHbcg/X1h03DQ7IQ6MKagMttpYhnKqOZR/nia0657Dt2v2vuXB8YuKNExw0Fljew== +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== dependencies: - "@types/estree" "^1.0.0" "@types/hast" "^3.0.0" - comma-separated-tokens "^2.0.0" - estree-util-value-to-estree "^3.0.0" - mdast-util-mdx-jsx "^3.0.0" - property-information "^6.0.0" - space-separated-tokens "^2.0.0" - style-to-js "^1.0.0" hast-util-to-estree@^3.0.0: version "3.1.0" @@ -7046,6 +6926,23 @@ hast-util-to-estree@^3.0.0: unist-util-position "^5.0.0" zwitch "^2.0.0" +hast-util-to-html@^9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz#a9999a0ba6b4919576a9105129fead85d37f302b" + integrity sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^3.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" + hast-util-to-jsx-runtime@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz#3ed27caf8dc175080117706bf7269404a0aa4f7c" @@ -7092,6 +6989,17 @@ hastscript@^6.0.0: property-information "^5.0.0" space-separated-tokens "^1.0.0" +hastscript@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-8.0.0.tgz#4ef795ec8dee867101b9f23cc830d4baf4fd781a" + integrity sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + header-case@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" @@ -7141,10 +7049,10 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha1-39YAJ9o2o238viNiYsAKWCJoFFM= -html-url-attributes@^3.0.0: +html-void-elements@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.0.tgz#fc4abf0c3fb437e2329c678b80abb3c62cff6f08" - integrity sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow== + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== http-cache-semantics@^4.1.1: version "4.1.1" @@ -7437,11 +7345,6 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" - integrity sha1-68JS5ADSL/jXf6CYiIIaJKZYwZE= - is-buffer@~1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -7651,13 +7554,6 @@ is-potential-custom-element-name@^1.0.1: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== -is-reference@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-3.0.0.tgz#b1380c03d96ddf7089709781e3208fceb0c92cd6" - integrity sha512-Eo1W3wUoHWoCoVM4GVl/a+K0IgiqE5aIo4kJABFyMum1ZORlPkC+UC357sSQUL5w5QCE5kCC9upl75b7+7CY/Q== - dependencies: - "@types/estree" "*" - is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -7791,11 +7687,6 @@ is-wsl@^2.1.1: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -7944,10 +7835,10 @@ jsdom@^25.0.0: ws "^8.18.0" xml-name-validator "^5.0.0" -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha1-gFZNLkg9rPbo7yCWUKZ98/DCg6Q= +jsesc@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" + integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== json-buffer@3.0.1: version "3.0.1" @@ -8020,16 +7911,6 @@ jsx-ast-utils@^3.3.5: object.assign "^4.1.4" object.values "^1.1.6" -just-extend@^4.0.2: - version "4.2.1" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" - integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== - -jwt-decode@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" - integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== - keyv@^4.5.2: version "4.5.2" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.2.tgz#0e310ce73bf7851ec702f2eaf46ec4e3805cce56" @@ -9176,7 +9057,7 @@ mlly@^1.4.2, mlly@^1.7.2: pkg-types "^1.2.0" ufo "^1.5.4" -mock-fs@^5.0.0, mock-fs@^5.2.0: +mock-fs@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.2.0.tgz#3502a9499c84c0a1218ee4bf92ae5bf2ea9b2b5e" integrity sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw== @@ -9238,15 +9119,6 @@ mute-stream@^1.0.0: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - nan@^2.14.0, nan@^2.14.1, nan@^2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" @@ -9310,11 +9182,11 @@ next-router-mock@^0.9.13: integrity sha512-906n2RRaE6Y28PfYJbaz5XZeJ6Tw8Xz1S6E31GGwZ0sXB6/XjldD1/2azn1ZmBmRk5PQRkzjg+n+RHZe5xQzWA== next@^14.0.0: - version "14.2.7" - resolved "https://registry.yarnpkg.com/next/-/next-14.2.7.tgz#e02d5d9622ff4b998e5c89adfd660c9bf6435970" - integrity sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ== + version "14.2.17" + resolved "https://registry.yarnpkg.com/next/-/next-14.2.17.tgz#77a38b11be983ddbbf6c7d1cea3c7e913e8a5445" + integrity sha512-hNo/Zy701DDO3nzKkPmsLRlDfNCtb1OJxFUvjGEl04u7SFa3zwC6hqsOUzMajcaEOEV8ey1GjvByvrg0Qr5AiQ== dependencies: - "@next/env" "14.2.7" + "@next/env" "14.2.17" "@swc/helpers" "0.5.5" busboy "1.6.0" caniuse-lite "^1.0.30001579" @@ -9322,32 +9194,21 @@ next@^14.0.0: postcss "8.4.31" styled-jsx "5.1.1" optionalDependencies: - "@next/swc-darwin-arm64" "14.2.7" - "@next/swc-darwin-x64" "14.2.7" - "@next/swc-linux-arm64-gnu" "14.2.7" - "@next/swc-linux-arm64-musl" "14.2.7" - "@next/swc-linux-x64-gnu" "14.2.7" - "@next/swc-linux-x64-musl" "14.2.7" - "@next/swc-win32-arm64-msvc" "14.2.7" - "@next/swc-win32-ia32-msvc" "14.2.7" - "@next/swc-win32-x64-msvc" "14.2.7" + "@next/swc-darwin-arm64" "14.2.17" + "@next/swc-darwin-x64" "14.2.17" + "@next/swc-linux-arm64-gnu" "14.2.17" + "@next/swc-linux-arm64-musl" "14.2.17" + "@next/swc-linux-x64-gnu" "14.2.17" + "@next/swc-linux-x64-musl" "14.2.17" + "@next/swc-win32-arm64-msvc" "14.2.17" + "@next/swc-win32-ia32-msvc" "14.2.17" + "@next/swc-win32-x64-msvc" "14.2.17" nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nise@^5.1.2: - version "5.1.4" - resolved "https://registry.yarnpkg.com/nise/-/nise-5.1.4.tgz#491ce7e7307d4ec546f5a659b2efe94a18b4bbc0" - integrity sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^10.0.2" - "@sinonjs/text-encoding" "^0.7.1" - just-extend "^4.0.2" - path-to-regexp "^1.7.0" - no-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" @@ -9368,15 +9229,6 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-cookie@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-cookie/-/node-cookie-2.1.2.tgz#4327c70e6a9bbe8c8ee86713bd9b534f655ec405" - integrity sha512-8/k8V9/2hCESYMmNVtJiHweKlP1ZqjrzG3bv+cSooiWurHB6N7KqVdX/s7ojbBXTwOJrLKJzC9rlPRvFRYuKRA== - dependencies: - cookie "^0.4.0" - cookie-signature "^1.1.0" - simple-encryptor "^3.0.0" - node-domexception@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" @@ -9389,13 +9241,6 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.12: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-fetch@^2.6.2: version "2.6.8" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" @@ -9494,7 +9339,7 @@ oauth@^0.9.15: resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" integrity sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA== -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -9669,6 +9514,13 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +oniguruma-to-js@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz#8d899714c21f5c7d59a3c0008ca50e848086d740" + integrity sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ== + dependencies: + regex "^4.3.2" + open@^7.4.2: version "7.4.2" resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" @@ -9875,6 +9727,18 @@ parse-json@^5.0.0, parse-json@^5.2.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-numeric-range@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz#7c63b61190d61e4d53a1197f0c83c47bb670ffa3" + integrity sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ== + +parse5@^7.0.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.1.tgz#8928f55915e6125f430cc44309765bf17556a33a" + integrity sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ== + dependencies: + entities "^4.5.0" + parse5@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" @@ -9956,13 +9820,6 @@ path-scurry@^1.10.1, path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-to-regexp@^6.1.0, path-to-regexp@^6.2.0: version "6.2.1" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" @@ -9990,15 +9847,7 @@ pathval@^2.0.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== -periscopic@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/periscopic/-/periscopic-3.0.4.tgz#b3fbed0d1bc844976b977173ca2cd4a0ef4fa8d1" - integrity sha512-SFx68DxCv0Iyo6APZuw/AKewkkThGwssmU0QWtTlvov3VAtPX+QJ4CadwSaz8nrT5jPIuxdvJWB4PnD2KNDxQg== - dependencies: - estree-walker "^3.0.0" - is-reference "^3.0.0" - -picocolors@^1.0.0, picocolors@^1.0.1, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -10058,11 +9907,6 @@ pino@^8.12.0: sonic-boom "^3.1.0" thread-stream "^2.0.0" -pirates@^4.0.1: - version "4.0.6" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" - integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== - pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" @@ -10178,20 +10022,7 @@ pretty-format@^3.8.0: resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385" integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew== -prism-react-renderer@^1.1.1: - version "1.3.5" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz#786bb69aa6f73c32ba1ee813fbe17a0115435085" - integrity sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg== - -prism-react-renderer@^2.0.6: - version "2.4.0" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz#c5ea692029c2f8b3fd04f63662d04ffd4eaf10a0" - integrity sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw== - dependencies: - "@types/prismjs" "^1.26.0" - clsx "^2.0.0" - -prismjs@^1.23.0, prismjs@^1.27.0: +prismjs@^1.27.0: version "1.29.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== @@ -10216,7 +10047,7 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha1-foz42PW48jnBvGi+tOt4Vn1XLvg= -prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -10394,20 +10225,13 @@ react-docgen-typescript@^2.2.2: resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" integrity sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg== -react-dom@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" - integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== +react-dom@^18.3.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== dependencies: loose-envify "^1.1.0" - scheduler "^0.23.0" - -react-easy-swipe@^0.0.21: - version "0.0.21" - resolved "https://registry.yarnpkg.com/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz#ce9384d576f7a8529dc2ca377c1bf03920bac8eb" - integrity sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg== - dependencies: - prop-types "^15.5.8" + scheduler "^0.23.2" react-error-boundary@^3.1.4: version "3.1.4" @@ -10416,13 +10240,6 @@ react-error-boundary@^3.1.4: dependencies: "@babel/runtime" "^7.12.5" -react-error-boundary@^4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c" - integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw== - dependencies: - "@babel/runtime" "^7.12.5" - react-immutable-proptypes@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz#cce96d68cc3c18e89617cbf3092d08e35126af4a" @@ -10455,31 +10272,6 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-live@^4.0.0: - version "4.1.7" - resolved "https://registry.yarnpkg.com/react-live/-/react-live-4.1.7.tgz#a451054bee78e6c9b4410dfc3940b318d9bc7880" - integrity sha512-NTzl0POOAW3dkp7+QL30duOrIu2Vzf2LHdx4TaQ0BqOAtQcSTKEXujfm9jR2VoCHko0oi35PYp38yKQBXz4mrg== - dependencies: - prism-react-renderer "^2.0.6" - sucrase "^3.31.0" - use-editable "^2.3.3" - -react-markdown@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-9.0.1.tgz#c05ddbff67fd3b3f839f8c648e6fb35d022397d1" - integrity sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg== - dependencies: - "@types/hast" "^3.0.0" - devlop "^1.0.0" - hast-util-to-jsx-runtime "^2.0.0" - html-url-attributes "^3.0.0" - mdast-util-to-hast "^13.0.0" - remark-parse "^11.0.0" - remark-rehype "^11.0.0" - unified "^11.0.0" - unist-util-visit "^5.0.0" - vfile "^6.0.0" - react-redux@^8.0.5: version "8.1.1" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a" @@ -10492,15 +10284,6 @@ react-redux@^8.0.5: react-is "^18.0.0" use-sync-external-store "^1.0.0" -react-responsive-carousel@3.2.10: - version "3.2.10" - resolved "https://registry.yarnpkg.com/react-responsive-carousel/-/react-responsive-carousel-3.2.10.tgz#2de13bdc131d78b8b92d3653d295a77d8c20cf2e" - integrity sha512-O8MV2LoR07BttvWaXesyWkE6s8xRW6p6HiMkelZ3TuPYQwKnlw+fYtZN+bQ3/1jg0D5JQGATY4Hnw/4WEXHnag== - dependencies: - classnames "^2.2.5" - prop-types "^15.5.8" - react-easy-swipe "^0.0.21" - react-split@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/react-split/-/react-split-2.0.14.tgz#ef198259bf43264d605f792fb3384f15f5b34432" @@ -10525,16 +10308,6 @@ react-table@^7.8.0: resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.8.0.tgz#07858c01c1718c09f7f1aed7034fcfd7bda907d2" integrity sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA== -react-transition-group@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react-window@^1.8.6: version "1.8.8" resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.8.tgz#1b52919f009ddf91970cbdb2050a6c7be44df243" @@ -10543,10 +10316,10 @@ react-window@^1.8.6: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@^18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" - integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== +react@^18.3.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== dependencies: loose-envify "^1.1.0" @@ -10667,6 +10440,46 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recma-build-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz#c02f29e047e103d2fab2054954e1761b8ea253c4" + integrity sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== + dependencies: + "@types/estree" "^1.0.0" + estree-util-build-jsx "^3.0.0" + vfile "^6.0.0" + +recma-jsx@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-jsx/-/recma-jsx-1.0.0.tgz#f7bef02e571a49d6ba3efdfda8e2efab48dbe3aa" + integrity sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q== + dependencies: + acorn-jsx "^5.0.0" + estree-util-to-js "^2.0.0" + recma-parse "^1.0.0" + recma-stringify "^1.0.0" + unified "^11.0.0" + +recma-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-parse/-/recma-parse-1.0.0.tgz#c351e161bb0ab47d86b92a98a9d891f9b6814b52" + integrity sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== + dependencies: + "@types/estree" "^1.0.0" + esast-util-from-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +recma-stringify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/recma-stringify/-/recma-stringify-1.0.0.tgz#54632030631e0c7546136ff9ef8fde8e7b44f130" + integrity sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== + dependencies: + "@types/estree" "^1.0.0" + estree-util-to-js "^2.0.0" + unified "^11.0.0" + vfile "^6.0.0" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -10717,21 +10530,16 @@ refractor@^3.6.0: parse-entities "^2.0.0" prismjs "~1.27.0" -regenerator-runtime@^0.13.10: - version "0.13.10" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" - integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== - -regenerator-runtime@^0.13.11: - version "0.13.11" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" - integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== - regenerator-runtime@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== +regex@^4.3.2: + version "4.4.0" + resolved "https://registry.yarnpkg.com/regex/-/regex-4.4.0.tgz#cb731e2819f230fad69089e1bd854fef7569e90a" + integrity sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ== + regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" @@ -10756,6 +10564,36 @@ regexpp@^3.1.0: resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== +rehype-parse@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-9.0.1.tgz#9993bda129acc64c417a9d3654a7be38b2a94c20" + integrity sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag== + dependencies: + "@types/hast" "^3.0.0" + hast-util-from-html "^2.0.0" + unified "^11.0.0" + +rehype-pretty-code@0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/rehype-pretty-code/-/rehype-pretty-code-0.13.0.tgz#e7de5217af907b2ff32ea441827c0b7ccdfc079b" + integrity sha512-+22dz1StXlF7dlMyOySNaVxgcGhMI4BCxq0JxJJPWYGiKsI6cu5jyuIKGHXHvH18D8sv1rdKtvsY9UEfN3++SQ== + dependencies: + "@types/hast" "^3.0.4" + hast-util-to-string "^3.0.0" + parse-numeric-range "^1.3.0" + rehype-parse "^9.0.0" + unified "^11.0.4" + unist-util-visit "^5.0.0" + +rehype-recma@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rehype-recma/-/rehype-recma-1.0.0.tgz#d68ef6344d05916bd96e25400c6261775411aa76" + integrity sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + hast-util-to-estree "^3.0.0" + rehype-slug@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/rehype-slug/-/rehype-slug-6.0.0.tgz#1d21cf7fc8a83ef874d873c15e6adaee6344eaf1" @@ -11134,18 +10972,13 @@ saxes@^6.0.0: dependencies: xmlchars "^2.2.0" -scheduler@^0.23.0: - version "0.23.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" - integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== dependencies: loose-envify "^1.1.0" -scmp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/scmp/-/scmp-2.0.0.tgz#247110ef22ccf897b13a3f0abddb52782393cd6a" - integrity sha512-FaHoAk75AYhT+rnBmMpkvHSIcQma4OHzYXOhn1XXtgNomi0FTV8YEXYuh2EIdCg5IKMVyFbXeJT4Cn96+fzABg== - section-matter@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" @@ -11197,6 +11030,11 @@ serialize-error@^8.1.0: dependencies: type-fest "^0.20.2" +server-only@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/server-only/-/server-only-0.0.1.tgz#0f366bb6afb618c37c9255a314535dc412cd1c9e" + integrity sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA== + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -11275,6 +11113,18 @@ shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" +shiki@^1.1.1: + version "1.22.2" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.22.2.tgz#ed109a3d0850504ad5a1edf8496470a2121c5b7b" + integrity sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA== + dependencies: + "@shikijs/core" "1.22.2" + "@shikijs/engine-javascript" "1.22.2" + "@shikijs/engine-oniguruma" "1.22.2" + "@shikijs/types" "1.22.2" + "@shikijs/vscode-textmate" "^9.3.0" + "@types/hast" "^3.0.4" + short-unique-id@^4.4.4: version "4.4.4" resolved "https://registry.yarnpkg.com/short-unique-id/-/short-unique-id-4.4.4.tgz#a45df68303bbd2dbb5785ed7708e891809c9cb7a" @@ -11319,13 +11169,6 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-encryptor@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/simple-encryptor/-/simple-encryptor-3.0.0.tgz#c88df52e336037e276e731cbe89c75846c1a89c9" - integrity sha512-xRgj9pU3Gfkl+6iBYRoXM4BdEwY4bLdL1W0tp7AjGTA7Hytv5iwmB5tvJh6K2iVszvPPYimQjLFV8jRZz3fJ1g== - dependencies: - scmp "2.0.0" - simple-get@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" @@ -11335,18 +11178,6 @@ simple-get@^4.0.0: once "^1.3.1" simple-concat "^1.0.0" -sinon@^14.0.2: - version "14.0.2" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-14.0.2.tgz#585a81a3c7b22cf950762ac4e7c28eb8b151c46f" - integrity sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w== - dependencies: - "@sinonjs/commons" "^2.0.0" - "@sinonjs/fake-timers" "^9.1.2" - "@sinonjs/samsam" "^7.0.1" - diff "^5.0.0" - nise "^5.1.2" - supports-color "^7.2.0" - slash@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" @@ -11730,20 +11561,6 @@ strnum@^1.0.5: resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== -style-to-js@^1.0.0: - version "1.1.13" - resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.13.tgz#8d17f2560a9cc9515b94834aaeb2305887d7ced5" - integrity sha512-+43kvxwjrW9n5gFR40Rv98A0/Mcjew7Lt+p5Nnw1KGR9SZf/ZaKqmMwl9Enj9EnYNcJ5VzuCjejC5KZzvH2lOA== - dependencies: - style-to-object "1.0.6" - -style-to-object@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.6.tgz#0c28aed8be1813d166c60d962719b2907c26547b" - integrity sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA== - dependencies: - inline-style-parser "0.2.3" - style-to-object@^0.4.0: version "0.4.4" resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.4.tgz#266e3dfd56391a7eefb7770423612d043c3f33ec" @@ -11775,19 +11592,6 @@ stylis@^4.2.0: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== -sucrase@^3.31.0: - version "3.35.0" - resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" - integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.2" - commander "^4.0.0" - glob "^10.3.10" - lines-and-columns "^1.1.6" - mz "^2.7.0" - pirates "^4.0.1" - ts-interface-checker "^0.1.9" - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -11795,7 +11599,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha1-G33NyzK4E4gBs+R4umpRyqiWSNo= @@ -11809,11 +11613,6 @@ supports-color@^8.1.0: dependencies: has-flag "^4.0.0" -supports-color@^9.0.0: - version "9.3.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.3.1.tgz#34e4ad3c71c9a39dae3254ecc46c9b74e89e15a6" - integrity sha512-knBY82pjmnIzK3NifMo3RxEIRD9E0kIzV4BKcyTZ9+9kWgLMxd4PrsTSMoFQUabgRBbF8KOLRDCyKgNV+iK44Q== - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -11882,13 +11681,6 @@ swagger-ui-react@^5.0.0: xml-but-prettier "^1.0.1" zenscroll "^4.0.2" -swr@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/swr/-/swr-2.1.2.tgz#15841cf5bbb8b20f24e2408193f616a41b6734a0" - integrity sha512-ocfaD2rnYZKqTDplCEX2bH5Z1++n2JSej9oYi7hVfXXWYm+0RP+H6fVrogWB0mtMclv1guk9kEnAzNLygOy9Hw== - dependencies: - use-sync-external-store "^1.2.0" - symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -11955,20 +11747,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.1" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" - integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== - dependencies: - any-promise "^1.0.0" - thread-stream@^2.0.0: version "2.4.0" resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.0.tgz#5def29598d1d4171ba3bace7e023a71d87d99c07" @@ -12025,11 +11803,6 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -12131,11 +11904,6 @@ ts-api-utils@^1.0.1, ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== -ts-interface-checker@^0.1.9: - version "0.1.13" - resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" - integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== - ts-toolbelt@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" @@ -12288,11 +12056,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8, type-detect@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw= - type-fest@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" @@ -12384,12 +12147,7 @@ types-ramda@^0.29.4: dependencies: ts-toolbelt "^9.6.0" -typescript@^5.0.0: - version "5.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" - integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== - -typescript@^5.2.2: +typescript@^5.0.0, typescript@^5.2.2: version "5.6.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.3.tgz#5f3449e31c9d94febb17de03cc081dd56d81db5b" integrity sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw== @@ -12429,7 +12187,7 @@ undici@^6.19.5: resolved "https://registry.yarnpkg.com/undici/-/undici-6.19.7.tgz#7d4cf26dc689838aa8b6753a3c5c4288fc1e0216" integrity sha512-HR3W/bMGPSr90i8AAp2C4DM3wChFdJPLrWYpIS++LxS8K+W535qftjt+4MyjNYHeWabMj1nvtmLIi7l++iq91A== -unified@^11.0.0: +unified@^11.0.0, unified@^11.0.4: version "11.0.5" resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== @@ -12450,9 +12208,11 @@ unionfs@^4.4.0: fs-monkey "^1.0.0" unist-util-is@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236" - integrity sha1-6K7OCxAvqbwJew/vj4cMSW1KYjY= + version "5.2.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.2.1.tgz#b74960e145c18dcb6226bc57933597f5486deae9" + integrity sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw== + dependencies: + "@types/unist" "^2.0.0" unist-util-is@^6.0.0: version "6.0.0" @@ -12484,13 +12244,6 @@ unist-util-remove@^3.1.0: unist-util-is "^5.0.0" unist-util-visit-parents "^5.0.0" -unist-util-stringify-position@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz#5c6aa07c90b1deffd9153be170dce628a869a447" - integrity sha1-XGqgfJCx3v/ZFTvhcNzmKKhppEc= - dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" @@ -12499,9 +12252,9 @@ unist-util-stringify-position@^4.0.0: "@types/unist" "^3.0.0" unist-util-visit-parents@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz#44bbc5d25f2411e7dfc5cecff12de43296aa8521" - integrity sha1-RLvF0l8kEeffxc7P8S3kMpaqhSE= + version "5.1.3" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz#b4520811b0ca34285633785045df7a8d6776cfeb" + integrity sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg== dependencies: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" @@ -12548,13 +12301,13 @@ unraw@^2.0.1: resolved "https://registry.yarnpkg.com/unraw/-/unraw-2.0.1.tgz#7b51dcdfb1e43d59d5e52cdb44d349d029edbaba" integrity sha512-tdOvLfRzHolwYcHS6HIX860MkK9LQ4+oLuNwFYL7bpgTEO64PZrcQxkisgwJYCfF8sKiWLwwu1c83DvMkbefIQ== -update-browserslist-db@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" - integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== +update-browserslist-db@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz#80846fba1d79e82547fb661f8d141e0945755fe5" + integrity sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A== dependencies: - escalade "^3.1.2" - picocolors "^1.0.1" + escalade "^3.2.0" + picocolors "^1.1.0" upper-case-first@^2.0.2: version "2.0.2" @@ -12593,20 +12346,20 @@ url@~0.11.0: punycode "1.3.2" querystring "0.2.0" -use-editable@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/use-editable/-/use-editable-2.3.3.tgz#a292fe9ba4c291cd28d1cc2728c75a5fc8d9a33f" - integrity sha512-7wVD2JbfAFJ3DK0vITvXBdpd9JAz5BcKAAolsnLBuBn6UDDwBGuCIAGvR3yA2BNKm578vAMVHFCWaOcA+BhhiA== +use-debounce@^10.0.0: + version "10.0.4" + resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.4.tgz#2135be498ad855416c4495cfd8e0e130bd33bb24" + integrity sha512-6Cf7Yr7Wk7Kdv77nnJMf6de4HuDE4dTxKij+RqE9rufDsI6zsbjyAxcH5y2ueJCQAnfgKbzXbZHYlkFwmBlWkw== use-memo-one@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== -use-sync-external-store@1.2.0, use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" - integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +use-sync-external-store@1.2.2, use-sync-external-store@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== util-deprecate@^1.0.1: version "1.0.2" @@ -12646,6 +12399,14 @@ vary@^1: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + vfile-matter@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/vfile-matter/-/vfile-matter-5.0.0.tgz#4f8d6476a432f9556784a8b538f7da0ba25e053d" @@ -12654,14 +12415,6 @@ vfile-matter@^5.0.0: vfile "^6.0.0" yaml "^2.0.0" -vfile-message@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-3.1.2.tgz#a2908f64d9e557315ec9d7ea3a910f658ac05f7d" - integrity sha1-opCPZNnlVzFeydfqOpEPZYrAX30= - dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position "^3.0.0" - vfile-message@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" @@ -12670,46 +12423,6 @@ vfile-message@^4.0.0: "@types/unist" "^3.0.0" unist-util-stringify-position "^4.0.0" -vfile-reporter@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/vfile-reporter/-/vfile-reporter-7.0.5.tgz#a0cbf3922c08ad428d6db1161ec64a53b5725785" - integrity sha512-NdWWXkv6gcd7AZMvDomlQbK3MqFWL1RlGzMn++/O2TI+68+nqxCPTvLugdOtfSzXmjh+xUyhp07HhlrbJjT+mw== - dependencies: - "@types/supports-color" "^8.0.0" - string-width "^5.0.0" - supports-color "^9.0.0" - unist-util-stringify-position "^3.0.0" - vfile "^5.0.0" - vfile-message "^3.0.0" - vfile-sort "^3.0.0" - vfile-statistics "^2.0.0" - -vfile-sort@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/vfile-sort/-/vfile-sort-3.0.1.tgz#4b06ec63e2946749b0bb514e736554cd75e441a2" - integrity sha512-1os1733XY6y0D5x0ugqSeaVJm9lYgj0j5qdcZQFyxlZOSy1jYarL77lLyb5gK4Wqr1d5OxmuyflSO3zKyFnTFw== - dependencies: - vfile "^5.0.0" - vfile-message "^3.0.0" - -vfile-statistics@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-2.0.1.tgz#2e1adae1cd3a45c1ed4f2a24bd103c3d71e4bce3" - integrity sha512-W6dkECZmP32EG/l+dp2jCLdYzmnDBIw6jwiLZSER81oR5AHRcVqL+k3Z+pfH1R73le6ayDkJRMk0sutj1bMVeg== - dependencies: - vfile "^5.0.0" - vfile-message "^3.0.0" - -vfile@^5.0.0: - version "5.3.4" - resolved "https://registry.yarnpkg.com/vfile/-/vfile-5.3.4.tgz#bbb8c96b956693bbf70b2c67fdb5781dff769b93" - integrity sha1-u7jJa5Vmk7v3Cyxn/bV4Hf92m5M= - dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^3.0.0" - vfile-message "^3.0.0" - vfile@^6.0.0, vfile@^6.0.1: version "6.0.3" resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" @@ -12750,13 +12463,6 @@ vite@^5.0.0, vite@^5.0.11: optionalDependencies: fsevents "~2.3.3" -vitest-fetch-mock@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/vitest-fetch-mock/-/vitest-fetch-mock-0.3.0.tgz#a519293b61be1ce43377d720500bbfa89861e509" - integrity sha512-g6upWcL8/32fXL43/5f4VHcocuwQIi9Fj5othcK9gPO8XqSEGtnIZdenr2IaipDr61ReRFt+vaOEgo8jiUUX5w== - dependencies: - cross-fetch "^4.0.0" - vitest@^2.0.0: version "2.1.4" resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.4.tgz#ba8f4589fb639cf5a9e6af54781667312b3e8230" @@ -12804,6 +12510,11 @@ wcwidth@^1.0.1: dependencies: defaults "^1.0.3" +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + web-streams-polyfill@4.0.0-beta.3: version "4.0.0-beta.3" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz#2898486b74f5156095e473efe989dcf185047a38" @@ -13190,14 +12901,19 @@ zod@^3.22.3: resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.3.tgz#2fbc96118b174290d94e8896371c95629e87a060" integrity sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug== -zustand@^4.1.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.3.2.tgz#bb121fcad84c5a569e94bd1a2695e1a93ba85d39" - integrity sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw== +zustand@^4.5.5: + version "4.5.5" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.5.5.tgz#f8c713041543715ec81a2adda0610e1dc82d4ad1" + integrity sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q== dependencies: - use-sync-external-store "1.2.0" + use-sync-external-store "1.2.2" zwitch@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" integrity sha1-kfjQ6QH/o9ZlmXVt3n9XsXyV3OE= + +zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==