.ts
TypeScript
(application/typescript)
// 3rd-party
import React, { VFC, useMemo } from "react";
import Prism from "prismjs";
import loadLanguages from "prismjs/components/";
import styled, { css } from "styled-components";
// app
import type { AppThemeScheme, WithThemeSchemeProp } from "../types";
import { NamedColors } from "../utils/style";

interface CodeProps {
  code: string;
  language: string;
  [x: string]: unknown;
}

if (typeof window === "undefined") {
  loadLanguages();
}

export const Code: VFC<CodeProps & WithThemeSchemeProp> = ({
  code,
  language,
  themeScheme,
  ...props
}) => {
  const innerHtml = useMemo(
    () => ({
      __html:
        language in Prism.languages
          ? Prism.highlight(code, Prism.languages[language], language)
          : Prism.highlight(code, Prism.languages["sh"], language),
    }),
    [code, language]
  );

  // the extra space before language is important so SSR matches CSR
  return (
    <StylePreTag
      data-language={language}
      className={` language-${language} line-numbers`}
      themeScheme={themeScheme}
    >
      <StyledCodeTag {...props} dangerouslySetInnerHTML={innerHtml} />
    </StylePreTag>
  );
};

export const getThemedCodeCss = (themeScheme: AppThemeScheme): JSX.Element =>
  themeScheme === "light" ? (
    <style>{`code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}`}</style>
  ) : (
    <style>{`code[class*=language-],pre[class*=language-]{color:#fff;background:0 0;text-shadow:0 -.1em .2em #000;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}:not(pre)>code[class*=language-],pre[class*=language-]{background:#4c3f33}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border:.3em solid #7a6651;border-radius:.5em;box-shadow:1px 1px .5em #000 inset}:not(pre)>code[class*=language-]{padding:.15em .2em .05em;border-radius:.3em;border:.13em solid #7a6651;box-shadow:1px 1px .3em -.1em #000 inset;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#997f66}.token.punctuation{opacity:.7}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.number,.token.property,.token.symbol,.token.tag{color:#d1939e}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#bce051}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f4b73d}.token.atrule,.token.attr-value,.token.keyword{color:#d1939e}.token.important,.token.regex{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.deleted{color:red}`}</style>
  );

const StylePreTag = styled.pre<WithThemeSchemeProp>`
  width: 100%;
  margin: 0 !important;
  padding: 0 !important;

  box-shadow: none !important;
  text-shadow: none !important;

  ${({ themeScheme }) => css`
    background-color: ${NamedColors.CARD[themeScheme]} !important;
    border: 1px solid ${NamedColors.BORDER_DEFAULT[themeScheme]} !important;
  `};
`;

export const StyledCodeTag = styled.code`
  display: block;

  min-height: 20px;
  width: 100%;
  padding: 4px 8px;

  font-size: 16px;
  border-radius: 4px !important;
`;