diff --git a/docs/docs/about.mdx b/docs/docs/about.mdx index e83de5c..59f07c7 100644 --- a/docs/docs/about.mdx +++ b/docs/docs/about.mdx @@ -1,3 +1,5 @@ +import { Cards } from '#components/cards/cards.tsx'; + export const meta = { title: 'About', category: '', @@ -27,3 +29,7 @@ Some utilities are created for training and educational purposes. - Well tested by unit/e2e - No dependencies (except optional peers) - Tree Shaking ready + +## Sections + + diff --git a/docs/src/components/cards/cards.m.css b/docs/src/components/cards/cards.m.css new file mode 100644 index 0000000..16ab1ca --- /dev/null +++ b/docs/src/components/cards/cards.m.css @@ -0,0 +1,27 @@ +.root { + display: flex; + gap: 12px; + flex-wrap: wrap; +} + +.item { + width: 0; + flex-grow: 1; + flex-basis: calc(50% - 6px); + transition: scale 200ms; +} + +.item:hover { + scale: 1.025; +} + +a.item { + color: inherit !important; + text-decoration: none !important; +} + +@media (max-width: 1024px) { + .item { + flex-basis: 100%; + } +} diff --git a/docs/src/components/cards/cards.tsx b/docs/src/components/cards/cards.tsx new file mode 100644 index 0000000..a0810be --- /dev/null +++ b/docs/src/components/cards/cards.tsx @@ -0,0 +1,59 @@ +import { Callout } from '#components/callout/callout.tsx'; +import { Link } from '#components/link/link.tsx'; +import { withPublicPath } from '../../utils.ts'; +import styles from './cards.m.css'; + +export function Cards() { + return ( +
+ + + React + SSR ready performant components and hooks + + + + + Rspack + Plugins to define configs easy + + + + + Typings + TypeScript type declarations + + + + + DI + Dependency injection toolkit + + + + + Router + Router implementation and React bindings + + + + + Math + Math and geometry functions + + + + + Misc + Non specific helpers + + + + + DOM + Browser utilities + + +
+ ); +} diff --git a/docs/src/components/code-block/code-block.tsx b/docs/src/components/code-block/code-block.tsx index 046c754..b06c442 100644 --- a/docs/src/components/code-block/code-block.tsx +++ b/docs/src/components/code-block/code-block.tsx @@ -1,7 +1,7 @@ /* eslint-disable no-console */ import { ReactNode, isValidElement, useMemo, useRef, useState } from 'react'; import { useIsomorphicLayoutEffect } from '@krutoo/utils/react'; -import { codeToHtml } from 'shiki'; +import { getProcessedLang, useHighlighter } from './utils.ts'; import styles from './code-block.m.css'; export interface CodeBlockProps { @@ -15,6 +15,7 @@ interface CodeProps { } export function CodeBlock({ title, children }: CodeBlockProps) { + const highlighter = useHighlighter(); const [content, setContent] = useState(''); const [background, setBackground] = useState(undefined); const blockRef = useRef(null); @@ -38,32 +39,30 @@ export function CodeBlock({ title, children }: CodeBlockProps) { return null; } - let lang = /^language-(.+)$/g.exec(children.props.className ?? '')?.[1]; - - if (!lang) { - lang = 'plaintext'; - } - - // traffic economy - load one bundle of shiki syntax for multiple syntaxes - if (['js', 'jsx', 'ts', 'javascript', 'typescript'].includes(lang.toLowerCase())) { - lang = 'tsx'; - } + const lang = /^language-(.+)$/g.exec(children.props.className ?? '')?.[1] ?? 'plaintext'; return { code: children.props.children, - lang, + lang: getProcessedLang(lang), }; }, [children]); useIsomorphicLayoutEffect(() => { - if (!sourceCode) { + if (!highlighter || !sourceCode) { return; } - codeToHtml(sourceCode.code, { lang: sourceCode.lang, theme: 'poimandres' }) - .then(setContent) - .catch(console.error); - }, [sourceCode]); + try { + const html = highlighter.codeToHtml(sourceCode.code, { + lang: sourceCode.lang, + theme: 'poimandres', + }); + + setContent(html); + } catch (error) { + console.error(error); + } + }, [highlighter, sourceCode]); return (
diff --git a/docs/src/components/code-block/utils.ts b/docs/src/components/code-block/utils.ts new file mode 100644 index 0000000..909f0f6 --- /dev/null +++ b/docs/src/components/code-block/utils.ts @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react'; +import { once } from '@krutoo/utils'; +import { type HighlighterCore, createHighlighterCore, createJavaScriptRegexEngine } from 'shiki'; +import themeOneDarkPro from 'shiki/themes/poimandres.mjs'; + +// IMPORTANT: make singleton by using `once` according to "shiki" docs +export const getHighlighterCore = once((): Promise => { + return createHighlighterCore({ + engine: createJavaScriptRegexEngine(), + langs: [ + // supported langs: + import('shiki/langs/sh.mjs'), + import('shiki/langs/tsx.mjs'), + import('shiki/langs/css.mjs'), + ], + themes: [ + // themes: + themeOneDarkPro, + ], + }); +}); + +export function useHighlighter(): HighlighterCore | null { + const [highlighter, setHighlighter] = useState(null); + + useEffect(() => { + let mount = true; + + getHighlighterCore() + .then(result => { + if (!mount) { + return; + } + + setHighlighter(result); + }) + // eslint-disable-next-line no-console + .catch(console.error); + + return () => { + mount = false; + }; + }, []); + + return highlighter; +} + +export function getProcessedLang(lang: string): string { + // make lang from ext (remove first dot if exist) + switch (lang.replace(/^\./, '')) { + case 'sh': + case 'bash': + case 'shell': + case 'shellscript': + return 'sh'; + + case 'js': + case 'jsx': + case 'ts': + case 'tsx': + return 'tsx'; + + default: + return lang; + } +} diff --git a/docs/src/components/link/link.tsx b/docs/src/components/link/link.tsx index e40e649..fa08ec1 100644 --- a/docs/src/components/link/link.tsx +++ b/docs/src/components/link/link.tsx @@ -8,14 +8,14 @@ export function Link({ ...restProps }: AnchorHTMLAttributes) { const navigate = useNavigate(); - const [internal, setInternal] = useState(false); + const [external, setExternal] = useState(false); useEffect(() => { if (!href) { return; } - setInternal(new URL(new Request(href).url).hostname === new URL(window.location.href).hostname); + setExternal(new URL(new Request(href).url).hostname !== new URL(window.location.href).hostname); }, [href]); const handleClick = (event: MouseEvent) => { @@ -29,7 +29,7 @@ export function Link({ navigate(event.currentTarget.href); }; - const resultTarget = target ?? (!internal ? '_blank' : undefined); + const resultTarget = target ?? (external ? '_blank' : undefined); return ; }