mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
* docs: optimize route higtlight (#4520) * docs: optimize home display (#4519) * docs: optimize home display and route highlight * docs: optimize home display * fix(alert): propagate className (#4535) * fix(alert): propagate className * chore(alert): remove className from alert theme * fix(avatar): title type in Avatar (#4529) * fix(avatar): title type in Avatar * fix(alert): apply isEmpty check on title * fix(alert): alert interface props type * refactor: remove unnecessary props types (#4530) * refactor(docs): remove string type as it is included in ReactNode * refactor: remove unnecessary types * feat(changeset): add changeset * chore: remove changeset * refactor: remove null since ReactNode unions it already * fix(input): use onPress for wrapper click focus (#4483) * fix(input): use onPress for wrapper click focus * test(input): wrapper click focus test * chore(changeset): input onPress for wrapper click focus * chore(changeset): minor wording * Refactor/rebrand (#4532) * chore: rebrand in progress * chore: update docs to use heroui * chore: components renbranded * chore: figma moved to the docs files * fix: posthog config * fix(docs): extra classname in form example (#4465) * chore: clean git * chore: make heroui private * chore: new logo * chore: node env var renamed * chore: public robots txt deleted * chore: wrangler installed * chore: wrangler renamed * chore: cloudlfare workers removed * chore: force vercel deploy * refactor: first migration and provider * refactor: rename nextui plugin * refactor: rename github site * refactor: rename CONTRIBUTING * refactor: rename package name * refactor: nextjs image hostname * refactor: mdx repo nextui-org rename frontio-ai * refactor: nextui.org rename heroui.com * refactor: add heroui to missing places * fix: heroui plugin name * fix: update docs * docs: nextui to heroui add npmrc pnpm migratation * chore: rename all packages with new org name * chore: replace frontio-ai by frontioai * chore: revert previous changes * chore: small adjustment * chore: doc updated * feat: blog * chore: avatar updated * fix: url * chore: add new ogimage * fix: ogimage command * fix: heroui name and storybook welcome page * fix: og image url * feat: favicon and icon changed --------- Co-authored-by: աӄա <wingkwong.code@gmail.com> Co-authored-by: winches <329487092@qq.com> * fix: postbuild script * chore: core package updates * ci(changesets): version packages (#4569) Co-authored-by: Junior Garcia <jrgarciadev@gmail.com> * feat: contributors added to the blog --------- Co-authored-by: winches <329487092@qq.com> Co-authored-by: աӄա <wingkwong.code@gmail.com> Co-authored-by: Peterl561 <76144929+Peterl561@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
166 lines
4.4 KiB
TypeScript
166 lines
4.4 KiB
TypeScript
// Inspired by https://github.dev/modulz/stitches-site code demo
|
|
import React from "react";
|
|
import refractor from "refractor/core";
|
|
import js from "refractor/lang/javascript";
|
|
import jsx from "refractor/lang/jsx";
|
|
import bash from "refractor/lang/bash";
|
|
import css from "refractor/lang/css";
|
|
import diff from "refractor/lang/diff";
|
|
import {toHtml} from "hast-util-to-html";
|
|
import rangeParser from "parse-numeric-range";
|
|
import {clsx} from "@heroui/shared-utils";
|
|
|
|
import {Pre} from "./pre";
|
|
import {WindowActions} from "./window-actions";
|
|
|
|
import highlightLine from "@/libs/rehype-highlight-line";
|
|
import highlightWord from "@/libs/rehype-highlight-word";
|
|
|
|
refractor.register(js);
|
|
refractor.register(jsx);
|
|
refractor.register(bash);
|
|
refractor.register(css);
|
|
refractor.register(diff);
|
|
|
|
type PreProps = Omit<React.ComponentProps<typeof Pre>, "css">;
|
|
|
|
export type CodeBlockProps = PreProps & {
|
|
language: "js" | "jsx" | "bash" | "css" | "diff";
|
|
title?: string;
|
|
value?: string;
|
|
highlightLines?: string;
|
|
mode?: "static" | "typewriter";
|
|
showLineNumbers?: boolean;
|
|
showWindowIcons?: boolean;
|
|
className?: string;
|
|
};
|
|
|
|
/**
|
|
* recursively get all text nodes as an array for a given element
|
|
*/
|
|
function getTextNodes(node: any): any[] {
|
|
let childTextNodes: React.ReactNode[] = [];
|
|
|
|
if (!node.hasChildNodes()) return [];
|
|
|
|
const childNodes = node.childNodes;
|
|
|
|
for (let i = 0; i < childNodes.length; i++) {
|
|
if (childNodes[i].nodeType == Node.TEXT_NODE) {
|
|
childTextNodes.push(childNodes[i]);
|
|
} else if (childNodes[i].nodeType == Node.ELEMENT_NODE) {
|
|
Array.prototype.push.apply(childTextNodes, getTextNodes(childNodes[i]));
|
|
}
|
|
}
|
|
|
|
return childTextNodes;
|
|
}
|
|
|
|
/**
|
|
* given a text node, wrap each character in the
|
|
* given tag.
|
|
*/
|
|
function wrapEachCharacter(textNode: any, tag: string, count: number) {
|
|
const text = textNode.nodeValue;
|
|
const parent = textNode.parentNode;
|
|
|
|
const characters = text.split("");
|
|
|
|
characters.forEach(function (character: any, letterIndex: any) {
|
|
const delay = (count + letterIndex) * 50;
|
|
var element = document.createElement(tag);
|
|
var characterNode = document.createTextNode(character);
|
|
|
|
element.appendChild(characterNode);
|
|
element.style.opacity = "0";
|
|
element.style.transition = `all ease 0ms ${delay}ms`;
|
|
|
|
parent.insertBefore(element, textNode);
|
|
|
|
// skip a couple of frames to trigger transition
|
|
requestAnimationFrame(() => requestAnimationFrame(() => (element.style.opacity = "1")));
|
|
});
|
|
|
|
parent.removeChild(textNode);
|
|
}
|
|
|
|
function CodeTypewriter({value, className, css, ...props}: any) {
|
|
const wrapperRef = React.useRef(null);
|
|
|
|
React.useEffect(() => {
|
|
const wrapper = wrapperRef.current as any;
|
|
|
|
if (wrapper) {
|
|
var allTextNodes = getTextNodes(wrapper);
|
|
|
|
let count = 0;
|
|
|
|
allTextNodes?.forEach((textNode) => {
|
|
wrapEachCharacter(textNode, "span", count);
|
|
count = count + textNode.nodeValue.length;
|
|
});
|
|
wrapper.style.opacity = "1";
|
|
}
|
|
|
|
return () => (wrapper.innerHTML = value);
|
|
}, []);
|
|
|
|
return (
|
|
<Pre className={className} css={css} {...props}>
|
|
<code
|
|
dangerouslySetInnerHTML={{__html: value}}
|
|
ref={wrapperRef}
|
|
className={className}
|
|
style={{opacity: 0}}
|
|
/>
|
|
</Pre>
|
|
);
|
|
}
|
|
|
|
const CodeBlock = React.forwardRef<HTMLPreElement, CodeBlockProps>((_props, forwardedRef) => {
|
|
const {
|
|
language,
|
|
value,
|
|
title,
|
|
highlightLines = "0",
|
|
className = "",
|
|
mode,
|
|
showLineNumbers,
|
|
showWindowIcons,
|
|
...props
|
|
} = _props;
|
|
|
|
let result: any = refractor.highlight(value || "", language);
|
|
|
|
result = highlightLine(result, rangeParser(highlightLines));
|
|
|
|
result = highlightWord(result);
|
|
|
|
// convert to html
|
|
result = toHtml(result);
|
|
|
|
// TODO reset theme
|
|
const classes = `language-${language}`;
|
|
const codeClasses = clsx("absolute w-full px-4 pb-6", showWindowIcons ? "top-10" : "top-0");
|
|
|
|
if (mode === "typewriter") {
|
|
return <CodeTypewriter className={classes} css={css} value={result} {...props} />;
|
|
}
|
|
|
|
return (
|
|
<Pre
|
|
ref={forwardedRef}
|
|
className={clsx("code-block", classes, className)}
|
|
data-line-numbers={showLineNumbers}
|
|
{...props}
|
|
>
|
|
{showWindowIcons && <WindowActions title={title} />}
|
|
<code dangerouslySetInnerHTML={{__html: result}} className={clsx(classes, codeClasses)} />
|
|
</Pre>
|
|
);
|
|
});
|
|
|
|
CodeBlock.displayName = "HeroUI - CodeBlock";
|
|
|
|
export default CodeBlock;
|