import React, { ComponentProps, FunctionComponent, MouseEvent, useState } from 'react'; import { styled } from '@storybook/theming'; import { document, window } from 'global'; import memoize from 'memoizerific'; import jsx from 'react-syntax-highlighter/dist/cjs/languages/prism/jsx'; import bash from 'react-syntax-highlighter/dist/cjs/languages/prism/bash'; import css from 'react-syntax-highlighter/dist/cjs/languages/prism/css'; import html from 'react-syntax-highlighter/dist/cjs/languages/prism/markup'; import tsx from 'react-syntax-highlighter/dist/cjs/languages/prism/tsx'; import typescript from 'react-syntax-highlighter/dist/cjs/languages/prism/typescript'; import { PrismLight as ReactSyntaxHighlighter } from 'react-syntax-highlighter'; // @ts-ignore import createElement from 'react-syntax-highlighter/dist/cjs/create-element'; import { ActionBar } from '../ActionBar/ActionBar'; import { ScrollArea } from '../ScrollArea/ScrollArea'; import { formatter } from './formatter'; export { createElement as createSyntaxHighlighterElement }; ReactSyntaxHighlighter.registerLanguage('jsx', jsx); ReactSyntaxHighlighter.registerLanguage('bash', bash); ReactSyntaxHighlighter.registerLanguage('css', css); ReactSyntaxHighlighter.registerLanguage('html', html); ReactSyntaxHighlighter.registerLanguage('tsx', tsx); ReactSyntaxHighlighter.registerLanguage('typescript', typescript); const themedSyntax = memoize(2)(theme => Object.entries(theme.code || {}).reduce((acc, [key, val]) => ({ ...acc, [`* .${key}`]: val }), {}) ); export interface WrapperProps { bordered?: boolean; padded?: boolean; } const Wrapper = styled.div( ({ theme }) => ({ position: 'relative', overflow: 'hidden', color: theme.color.defaultText, }), ({ theme, bordered }) => bordered ? { border: `1px solid ${theme.appBorderColor}`, borderRadius: theme.borderRadius, background: theme.background.content, } : {} ); const Scroller = styled(({ children, className }) => ( {children} ))( { position: 'relative', }, ({ theme }) => ({ '& code': { paddingRight: theme.layoutMargin, }, }), ({ theme }) => themedSyntax(theme) ); export interface PreProps { padded?: boolean; } const Pre = styled.pre(({ theme, padded }) => ({ display: 'flex', justifyContent: 'flex-start', margin: 0, padding: padded ? theme.layoutMargin : 0, })); const Code = styled.code({ flex: 1, paddingRight: 0, opacity: 1, }); export interface SyntaxHighlighterRendererProps { rows: any[]; stylesheet: string; useInlineStyles: boolean; } export interface SyntaxHighlighterProps { language: string; copyable?: boolean; bordered?: boolean; padded?: boolean; format?: boolean; className?: string; renderer?: (props: SyntaxHighlighterRendererProps) => React.ReactNode; } export interface SyntaxHighlighterState { copied: boolean; } type ReactSyntaxHighlighterProps = ComponentProps; type Props = SyntaxHighlighterProps & ReactSyntaxHighlighterProps; export const SyntaxHighlighter: FunctionComponent = ({ children, language = 'jsx', copyable = false, bordered = false, padded = false, format = true, className = null, ...rest }) => { const [copied, setCopied] = useState(false); const onClick = (e: MouseEvent) => { e.preventDefault(); const tmp = document.createElement('TEXTAREA'); const focus = document.activeElement; tmp.value = children; document.body.appendChild(tmp); tmp.select(); document.execCommand('copy'); document.body.removeChild(tmp); focus.focus(); setCopied(true); window.setTimeout(() => setCopied(false), 1500); }; return children ? ( {format ? formatter((children as string).trim()) : (children as string).trim()} {copyable ? ( ) : null} ) : null; };