@serenis/shared-components

v0.29.0

Published

Cross-application shared components for the Serenis design system. Higher-level components built on top of `@serenis/ui` and `@serenis/cdk` that are used across multiple Serenis applications.

Readme

@serenis/shared-components

Cross-application shared components for the Serenis design system. Higher-level components built on top of @serenis/ui and @serenis/cdk that are used across multiple Serenis applications.

Install

npm install @serenis/shared-components

What's included

Components:

  • Nav — Responsive navigation bar with mobile overlay menu. Accepts a list of MenuItem entries, each with up to three columns of links.
  • TableOfContent — Collapsible index of the headings of a long-form article, built from the Heading list returned by extractHeadings.
  • LazyVideoWrapper — Click-to-play façade for embedded videos: the iframe is mounted only on user intent, so the third-party player is not requested on page load.
  • Bibliography — The printed sources of an editorial article, with the citation emitted as structured data.
  • Webliography — The online sources of an editorial article, each with an outbound link and the citation emitted as structured data.
  • Share — The share block of an editorial article: copy the link (or open the native share sheet) plus one outbound link per social network.
  • Faqs — The FAQ accordions of an editorial article, with the set emitted as FAQPage structured data. Real headings only when the title was written editorially.
  • HtmlScript — Renders a CMS-authored HTML snippet, decoding the base64 body carried by the htmlscript short code. The markup is injected as-is, so only trusted editorial content may reach it.
  • JsonLinkedData — Renders JSON-LD structured data (<script type="application/ld+json">) for SEO, with typed schemas via schema-dts. @context is added for every item.

Utilities:

  • extractHeadings — Reads the headings out of an HTML string and returns them as Heading[] (depth, slug, text).
  • slugifyChildren — Turns the text content of a React node into a slug, so rendered headings get the id anchors TableOfContent links to.

All components take their user-facing strings as props: the package holds no translations, so the consuming application passes text already localized.

Usage

import { Nav, type MenuItem } from '@serenis/shared-components'

const isExternal = (url: string) => url.startsWith('http')

const Header = ({ menuItems }: { menuItems: MenuItem[] }) => (
  <Nav
    getUrlAttributes={(url) => (isExternal(url) ? { rel: 'noreferrer', target: '_blank' } : {})}
    menuItems={menuItems}
    withBannerPromo={false}
  />
)
import { extractHeadings, TableOfContent } from '@serenis/shared-components'

const ArticleIndex = ({ html }: { html: string }) => (
  <TableOfContent headings={extractHeadings(html)} title="In this article" />
)
import { LazyVideoWrapper } from '@serenis/shared-components'

const Video = () => (
  <LazyVideoWrapper
    data-thumbnail="https://example.com/cover.webp"
    playAriaLabel="Play the video"
    src="https://player.vimeo.com/video/76979871"
    title="How the therapy path works"
  />
)
import { Bibliography, Webliography } from '@serenis/shared-components'

const Sources = ({ bibliography, webliography }) => (
  <>
    <Webliography
      getOpenReferenceLabel={(title) => `Open reference: ${title}`}
      items={webliography}
      title="Online sources"
    />
    <Bibliography items={bibliography} title="Printed sources" />
  </>
)
import { Share } from '@serenis/shared-components'

const ArticleShare = ({ article, url }) => (
  <Share
    articleTitle={article.title}
    body="Share this article with whoever needs it."
    copiedLabel="Copied!"
    copyLinkLabel="copy link"
    getShareOnLabel={(network) => `share on ${network}`}
    getUrlAttributes={(url) => (url.startsWith('/') ? {} : { rel: 'noopener nofollow', target: '_blank' })}
    title="Did you learn something new?"
    url={url}
  />
)
import { Faqs } from '@serenis/shared-components'

// A written title is promoted to an h2 and the questions to h3; the generic fallback stays a span.
const ArticleFaqs = ({ article, faqs }) => (
  <Faqs
    customTitle={article.titleFaq}
    defaultTitle="Some questions you might have"
    items={faqs}
    renderAnswer={(html) => <HtmlToReact htmlString={html} />}
  />
)
import { HtmlScript } from '@serenis/shared-components'

// base64body comes from the parsed article content, already encoded as UTF-8.
const EmbeddedSnippet = ({ base64body }: { base64body: string }) => <HtmlScript base64body={base64body} />
import { JsonLinkedData } from '@serenis/shared-components'

const Page = () => (
  <JsonLinkedData
    items={[
      {
        '@type': 'MedicalOrganization',
        name: 'Serenis',
        url: 'https://www.serenis.it',
      },
    ]}
  />
)

License

PolyForm Noncommercial 1.0.0 — see LICENSE for the full text.