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 ;
}