diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-13 19:32:56 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:14:41 +0100 |
| commit | 006b15b467a5cd835a6eab1b49023100bdc8f2e6 (patch) | |
| tree | 949c7295c2e206f42357f135bab4696ddf6576ec /src/components/molecules/code | |
| parent | 00f147a7a687d5772bcc538bc606cfff972178cd (diff) | |
refactor(components): rewrite Code component and usePrism hook
* move Prism styles to Sass placeholders to avoid repeats
* let usePrism consumer define its plugins (remove default ones)
* remove `plugins` prop from Code component
* add new props to Code component to let consumer configure plugins
(and handle plugin list from the given options)
However there are some problems with Prism plugins: line-highlight and
treeview does not seems to be loaded. I don't want to use Babel instead
of SWC so I have no solution for now.
Diffstat (limited to 'src/components/molecules/code')
| -rw-r--r-- | src/components/molecules/code/code.module.scss | 13 | ||||
| -rw-r--r-- | src/components/molecules/code/code.stories.tsx | 192 | ||||
| -rw-r--r-- | src/components/molecules/code/code.test.tsx | 14 | ||||
| -rw-r--r-- | src/components/molecules/code/code.tsx | 183 | ||||
| -rw-r--r-- | src/components/molecules/code/index.ts | 1 |
5 files changed, 403 insertions, 0 deletions
diff --git a/src/components/molecules/code/code.module.scss b/src/components/molecules/code/code.module.scss new file mode 100644 index 0000000..b551040 --- /dev/null +++ b/src/components/molecules/code/code.module.scss @@ -0,0 +1,13 @@ +@use "../../../styles/abstracts/placeholders"; + +.wrapper { + width: 100%; + + :global { + @extend %prism; + } + + figcaption { + margin-top: calc(var(--spacing-sm) * -1); + } +} diff --git a/src/components/molecules/code/code.stories.tsx b/src/components/molecules/code/code.stories.tsx new file mode 100644 index 0000000..1127839 --- /dev/null +++ b/src/components/molecules/code/code.stories.tsx @@ -0,0 +1,192 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Code } from './code'; + +/** + * Code - Storybook Meta + */ +export default { + title: 'Molecules/Code', + component: Code, + argTypes: { + children: { + control: { + type: 'text', + }, + description: 'The code sample.', + type: { + name: 'string', + required: true, + }, + }, + filterPattern: { + control: { + type: 'text', + }, + description: 'Define a pattern to filter the command line output.', + table: { + category: 'Options', + }, + type: { + name: 'string', + required: false, + }, + }, + language: { + control: { + type: 'text', + }, + description: 'The code sample language.', + type: { + name: 'string', + required: true, + }, + }, + plugins: { + description: 'An array of Prism plugins to activate.', + type: { + name: 'object', + required: false, + value: {}, + }, + }, + }, +} as ComponentMeta<typeof Code>; + +const Template: ComponentStory<typeof Code> = (args) => <Code {...args} />; + +const javascriptCodeSample = ` +const foo = () => { + return 'bar'; +} +`; + +/** + * Code Stories - Code sample + */ +export const CodeSample = Template.bind({}); +CodeSample.args = { + children: javascriptCodeSample, + language: 'javascript', +}; + +/** + * Code Stories - Highlighting + * + * @todo Find a way to make it working: line-highlight plugin is not loaded. + */ +export const Highlighting = Template.bind({}); +Highlighting.args = { + children: javascriptCodeSample, + highlight: '3', + language: 'javascript', +}; + +// cSpell:ignore xinitrc +const commandLineCode = ` +ls -lah +#output#drwxr-x---+ 42 armand armand 4,0K 17 april 11:15 . +#output#drwxr-xr-x 4 root root 4,0K 30 mai 2021 .. +#output#-rw-r--r-- 1 armand armand 2,0K 21 jul. 2021 .xinitrc +`; + +/** + * Code Stories - Command Line + */ +export const CommandLine = Template.bind({}); +CommandLine.args = { + children: commandLineCode, + cmdOutputFilter: '#output#', + isCmd: true, + language: 'bash', +}; + +// cSpell:ignore lcov +const treeSample = ` +. +├── bin +│ └── deploy.sh +├── CHANGELOG.md +├── commitlint.config.cjs +├── coverage +│ ├── clover.xml +│ ├── coverage-final.json +│ ├── lcov-report +│ └── lcov.info +├── cspell.json +├── cypress.config.js +├── docker-compose.yml +├── Dockerfile +├── jest.config.js +├── jest.setup.js +├── lang +│ ├── en.json +│ └── fr.json +├── LICENSE +├── lint-staged.config.js +├── mdx.d.ts +├── next-env.d.ts +├── next-sitemap.config.cjs +├── next.config.js +├── package.json +├── public +│ ├── apple-touch-icon.png +│ ├── armand-philippot.jpg +│ ├── favicon.ico +│ ├── icon-192.png +│ ├── icon-512.png +│ ├── icon.svg +│ ├── manifest.webmanifest +│ ├── prism +│ ├── projects +│ ├── robots.txt +│ ├── sitemap-0.xml +│ ├── sitemap.xml +│ └── vercel.svg +├── README.md +├── src +│ ├── assets +│ ├── components +│ ├── content +│ ├── i18n +│ ├── pages +│ ├── services +│ ├── styles +│ ├── types +│ └── utils +├── tests +│ ├── cypress +│ ├── jest +│ └── utils +├── tsconfig.eslint.json +├── tsconfig.json +├── tsconfig.tsbuildinfo +└── yarn.lock`; + +/** + * Code Stories - Tree view + * + * @todo Find a way to make it working: treeview plugin is not loaded. + */ +export const TreeView = Template.bind({}); +TreeView.args = { + children: treeSample, + language: 'treeview', +}; + +const diffSample = ` +--- file1.js 2023-10-13 19:17:05.540644084 +0200 ++++ file2.js 2023-10-13 19:17:15.310564281 +0200 +@@ -1,2 +1 @@ +-let foo = bar.baz([1, 2, 3]); +-foo = foo + 1; ++const foo = bar.baz([1, 2, 3]) + 1;`; + +/** + * Code Stories - Diff + */ +export const Diff = Template.bind({}); +Diff.args = { + children: diffSample, + isDiff: true, + language: 'diff', +}; diff --git a/src/components/molecules/code/code.test.tsx b/src/components/molecules/code/code.test.tsx new file mode 100644 index 0000000..5b946b3 --- /dev/null +++ b/src/components/molecules/code/code.test.tsx @@ -0,0 +1,14 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../tests/utils'; +import { Code } from './code'; + +describe('Code', () => { + it('renders a code block', () => { + const language = 'javascript'; + const code = 'nam'; + + render(<Code language={language}>{code}</Code>); + + expect(rtlScreen.getByText(code)).toBeInTheDocument(); + }); +}); diff --git a/src/components/molecules/code/code.tsx b/src/components/molecules/code/code.tsx new file mode 100644 index 0000000..0168fd6 --- /dev/null +++ b/src/components/molecules/code/code.tsx @@ -0,0 +1,183 @@ +import { forwardRef, type ForwardRefRenderFunction } from 'react'; +import { + usePrism, + type PrismLanguage, + type PrismAvailablePlugin, +} from '../../../utils/hooks'; +import { Figure, type FigureProps } from '../../atoms'; +import styles from './code.module.scss'; + +export type CodeProps = Omit<FigureProps, 'children'> & { + /** + * The code to highlight. + */ + children: string; + /** + * Define a pattern to automatically present some lines as continuation lines + * when using command line. + * + * @default undefined + */ + cmdContinuationFilter?: string; + /** + * Define the prompt to be displayed when the command has continued beyond + * the first line. Only used with command line. + * + * @default '>' + */ + cmdContinuationPrompt?: string; + /** + * Define the line continuation string or character when using command line. + */ + cmdContinuationStr?: string; + /** + * Define the host when using command line. + */ + cmdHost?: string; + /** + * Define a custom prompt when using command line. By default, `#` will be + * used for the root user and `$` for all other users. + */ + cmdPrompt?: string; + /** + * Define the line(s) that must be presented as output when using command + * line. + * + * @example '6' // a single line + * @example '2-7' // a range + * @example '3,9-11' // multiple lines with a range + * + * @default undefined + */ + cmdOutput?: string; + /** + * Define a pattern to automatically present some lines as output when using + * command line. + * + * @default undefined + */ + cmdOutputFilter?: string; + /** + * Specify the user when using command line. + */ + cmdUser?: string; + /** + * Define the line(s) that must be highlighted. + * + * DON'T USE: it seems the plugin is not correctly loaded. + * + * @example '6' // a single line + * @example '2-7' // a range + * @example '3,9-11' // multiple lines with a range + * + * @default undefined + */ + highlight?: string; + /** + * Should the code be treated as command lines? + * + * @default false + */ + isCmd?: boolean; + /** + * Should the code be treated as a diff block? + * + * @default false + */ + isDiff?: boolean; + /** + * The code language. + */ + language: PrismLanguage; + /** + * Define the starting line number. It will be ignored with command lines. + * + * @default undefined // Starts with 1. + */ + start?: string; +}; + +const CodeWithRef: ForwardRefRenderFunction<HTMLElement, CodeProps> = ( + { + children, + className = '', + cmdContinuationFilter, + cmdContinuationPrompt, + cmdContinuationStr, + cmdHost, + cmdOutput, + cmdOutputFilter, + cmdPrompt, + cmdUser, + highlight, + isCmd = false, + isDiff = false, + language, + start, + ...props + }, + ref +) => { + const wrapperClass = `${styles.wrapper} ${className}`; + const codeClass = isDiff + ? `language-diff-${language}` + : `language-${language}`; + const plugins: PrismAvailablePlugin[] = [ + 'toolbar', + 'autoloader', + 'show-language', + 'color-scheme', + 'copy-to-clipboard', + 'inline-color', + 'match-braces', + 'normalize-whitespace', + ]; + + if (isDiff || language === 'diff') plugins.push('diff-highlight'); + + if (language.endsWith('treeview')) plugins.push('treeview'); + else plugins.push(isCmd ? 'command-line' : 'line-numbers'); + + const { attributes: prismAttributes, className: prismClass } = usePrism({ + attributes: { + 'data-continuation-prompt': cmdContinuationPrompt, + 'data-continuation-str': cmdContinuationStr, + 'data-filter-continuation': cmdContinuationFilter, + 'data-filter-output': cmdOutputFilter, + 'data-host': cmdHost, + 'data-line': highlight, + 'data-output': cmdOutput, + 'data-prompt': cmdPrompt, + 'data-start': start, + 'data-toolbar-order': 'show-language,copy-to-clipboard,color-scheme', + 'data-user': cmdUser, + }, + language, + plugins, + }); + + return ( + <Figure {...props} className={wrapperClass} ref={ref}> + <pre + {...prismAttributes} + className={prismClass} + // cSpell:ignore noninteractive + // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex + tabIndex={0} + > + <code className={codeClass}>{children}</code> + </pre> + </Figure> + ); +}; + +/** + * Code component + * + * Render a code block with syntax highlighting. + * + * @todo Find a way to load Prism plugins without Babel (Next uses SWC). It + * seems some plugins are not loaded correctly (`line-highlight` or `treeview` + * for example). + */ +export const Code = forwardRef(CodeWithRef); diff --git a/src/components/molecules/code/index.ts b/src/components/molecules/code/index.ts new file mode 100644 index 0000000..d18a4e0 --- /dev/null +++ b/src/components/molecules/code/index.ts @@ -0,0 +1 @@ +export * from './code'; |
