Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: improve code preview #798

Merged
merged 31 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4480922
docs(app/components/code-preview): add basic markup and styles
zoltanszogyenyi May 31, 2023
38e2d94
docs(app/components/code-preview): improve box spacing, bg color
zoltanszogyenyi Jun 1, 2023
3d1a71b
docs(app/components/code-preview): add copy to clipboard
zoltanszogyenyi Jun 1, 2023
70c0c45
docs(app/components/code-preview): add Github link template
zoltanszogyenyi Jun 1, 2023
70b9970
docs(app/components/code-preview): add typings for `react-copy-clipbo…
zoltanszogyenyi Jun 1, 2023
d13c2a7
docs(app/components/code-preview): add expand button
zoltanszogyenyi Jun 1, 2023
abe74ec
docs(app/docs.css): style code preview text markup
robert1508 Jun 2, 2023
6aa89c7
docs(app/docs.css): remove underlines from code previews
zoltanszogyenyi Jun 2, 2023
38a5643
docs(app/docs.css): fix custom CSS wasn't applied to code previews
tulup-conner Jun 7, 2023
f5323e2
docs(app/components/code-preview): remove pointless React package to …
tulup-conner Jun 7, 2023
27c053a
docs(app/components/code-preview): allow toggle dark mode on each cod…
tulup-conner Jun 7, 2023
714220b
docs(app/components/code-preview): remove vert margin from code previ…
tulup-conner Jun 7, 2023
c2e57ba
docs(app/components/code-preview): add `Next.js`-style context to eac…
tulup-conner Jun 7, 2023
979e918
docs(app/style.css): remove redundant Inter font
tulup-conner Jun 7, 2023
697c80c
docs(app/docs/components/*): add missing `import`s in component exmaples
tulup-conner Jun 7, 2023
9537b7a
build(fathom): use `<script defer>` to load instead of `npm` package
tulup-conner Jun 8, 2023
526c492
docs(app/components/navbar): fix Quickstart link shouldn't go to flow…
tulup-conner Jun 8, 2023
c10d297
docs(app/docs/getting-started/introduction): replace nested `<p>`
tulup-conner Jun 8, 2023
3f7323b
docs(app/docs/customize/theme): fix Option 3 code sample type
tulup-conner Jun 8, 2023
415c827
docs(app/components/code-preview): add functionality for Collapse cod…
tulup-conner Jun 8, 2023
0f53b20
build(next.config): disable minified builds in production
tulup-conner Jun 8, 2023
45e4c25
docs(app/docs/components/*): add missing external `import`s in code e…
tulup-conner Jun 8, 2023
535c2b3
build(next.config): enable minification for prod with specific options
tulup-conner Jun 8, 2023
5ff6c04
docs(app/docs/components/*): update Edit on Github Button links
tulup-conner Jun 8, 2023
47d9ab5
docs(app/docs/components/modal): input code previews manually
tulup-conner Jun 8, 2023
eae2559
docs(app/components/code-preview): fix code isn't highlighted after i…
tulup-conner Jun 8, 2023
0e88165
docs(code-preview): remove vertical scroll for code preview
zoltanszogyenyi Jun 9, 2023
14b5542
docs(cards): fix e-commerce card image path
zoltanszogyenyi Jun 9, 2023
c587389
fix(theme): add cyan color to checkboxes and radios
zoltanszogyenyi Jun 9, 2023
750a9e4
docs(sidebar): improve sidebar with CTA UI/UX
zoltanszogyenyi Jun 9, 2023
fe3404b
docs(spinner): fix alignment spinner example imports
zoltanszogyenyi Jun 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 196 additions & 16 deletions app/components/code-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,233 @@
'use client';

import classNames from 'classnames';
import { usePathname } from 'next/navigation';
import prismjs from 'prismjs';
import type { ComponentProps, FC, PropsWithChildren } from 'react';
import { Children } from 'react';
import { Children, useEffect, useState } from 'react';
import type { Options } from 'react-element-to-jsx-string';
import reactElementToJSXString from 'react-element-to-jsx-string';
import { BsCheckLg, BsFillClipboardFill } from 'react-icons/bs';
import { HiMoon, HiSun } from 'react-icons/hi';
import { Tooltip } from '~/src';

const reactElementToJSXStringOptions: Options = {
filterProps: ['as', 'key'],
functionValue: (fn) => fn.name,
filterProps: ['key', 'ref'],
showFunctions: true,
sortProps: true,
};

interface CodePreviewProps extends PropsWithChildren, ComponentProps<'div'> {
code?: string;
functionBody?: string[];
githubPage?: string;
importExternal?: string;
importFlowbiteReact?: string;
title: string;
}

export const CodePreview: FC<CodePreviewProps> = function ({ children, className }) {
interface CodePreviewState {
isDarkMode?: boolean;
isExpanded?: boolean;
isJustCopied?: boolean;
}

export const CodePreview: FC<CodePreviewProps> = function ({
children,
className,
code = '',
functionBody,
githubPage,
importExternal,
importFlowbiteReact,
title,
}) {
const [isDarkMode, setDarkMode] = useState(false);
const [isExpanded, setExpanded] = useState(false);
const [isJustCopied, setJustCopied] = useState(false);

const copyToClipboard = (toCopy: string) => {
setJustCopied(true);
navigator.clipboard.writeText(toCopy);
setTimeout(() => setJustCopied(false), 2000);
};

const childrenList = Children.toArray(children);
let code = childrenList.map((child) => reactElementToJSXString(child, reactElementToJSXStringOptions)).join('\n');
const isFragment = childrenList.length > 1;

if (code === '') {
code = childrenList.map((child) => reactElementToJSXString(child, reactElementToJSXStringOptions)).join('\n');
}

code = deleteJSXSpaces(code);
code = deleteSVGs(code);
code = replaceWebpackImportsOnComponents(code);
code = replaceWebpackImportsOnFunctions(code);
code = `'use client';

import { ${importFlowbiteReact ?? firstComponentDisplayName(code)} } from 'flowbite-react';
${importExternal ? `${importExternal}\n` : ''}
export default function ${titleCaseToUpperCamelCase(title)}() {${
functionBody ? `${functionBody.map((line) => `\n ${line}`).join('')}\n` : ''
}
return (
<div className="mb-12 flex w-full flex-col gap-2">
<div className={classNames('py-4', className)}>{children}</div>
<pre className="language-tsx">
<code>{code}</code>
</pre>
${isFragment ? '<>\n ' : ''}${code.replaceAll(/\n/g, isFragment ? '\n ' : '\n ')}${
isFragment ? '\n </>' : ''
}
)
}\n\n\n`;

useEffect(() => {
prismjs.highlightAll();
}, [code]);

return (
<div className="code-example mt-8">
<div className="w-full rounded-t-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-600 dark:bg-gray-700">
<div className="grid grid-cols-2">
<EditOnGithubButton githubPage={githubPage} title={title} />
<div className="ml-auto">
<ToggleDarkModeButton isDarkMode={isDarkMode} onClick={() => setDarkMode(!isDarkMode)} />
</div>
</div>
</div>
<div className={classNames('code-preview-wrapper', isDarkMode && 'dark')}>
<div className="code-preview flex border-x border-gray-200 bg-white bg-gradient-to-r p-0 dark:border-gray-600 dark:bg-gray-900">
<div className="code-responsive-wrapper w-full">
<div className="mx-auto w-full bg-white bg-gradient-to-r p-2 dark:bg-gray-900 sm:p-6">
<div className={classNames('py-4', className)}>{children}</div>
</div>
</div>
</div>
</div>
<div className="code-syntax-wrapper">
<div className="code-syntax relative border-x border-y border-gray-200 dark:border-gray-600">
<div className="grid w-full grid-cols-2 rounded-t-md border-b border-gray-200 bg-gray-50 dark:border-gray-600 dark:bg-gray-700">
<ul className="flex text-center text-sm font-medium text-gray-500 dark:text-gray-400">
<li>
<span className="inline-block w-full border-r border-gray-200 bg-gray-100 p-2 px-3 text-gray-800 dark:border-gray-600 dark:bg-gray-800 dark:text-white">
React TypeScript
</span>
</li>
</ul>
<div className="flex justify-end">
<CopyToClipboardButton isJustCopied={isJustCopied} onClick={() => copyToClipboard(code)} />
</div>
</div>
<pre className={classNames('language-tsx !overflow-hidden', !isExpanded && 'max-h-72')}>
<code>{code}</code>
</pre>
<CollapseExpandButton isExpanded={isExpanded} onClick={() => setExpanded(!isExpanded)} />
</div>
</div>
</div>
);
};

const EditOnGithubButton: FC<CodePreviewProps> = ({ githubPage }) => {
const pathname = usePathname();

const githubSrcHref = 'https://github.com/themesberg/flowbite-react/blob/main/src/components/';
const page = pathname.split('/').pop() ?? '';
const component = slugToUpperCamelCase(page);
const href = `${githubSrcHref}${githubPage ?? `${component}/${component}`}.tsx`;

return (
<a
href={href}
rel="noreferrer nofollow noopener"
className="inline-flex w-fit items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-3 py-2 text-center text-xs font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 focus:text-primary-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:border-gray-600 dark:bg-gray-800 dark:bg-transparent dark:text-gray-400 dark:hover:border-gray-700 dark:hover:text-white dark:focus:text-white dark:focus:ring-gray-700"
>
<svg
aria-hidden
fill="currentColor"
viewBox="0 0 24 24"
focusable="false"
data-icon="github"
role="img"
className="h-4 w-4"
>
<path
fillRule="evenodd"
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
clipRule="evenodd"
/>
</svg>
Edit on GitHub
</a>
);
};

const ToggleDarkModeButton: FC<ComponentProps<'button'> & CodePreviewState> = ({ isDarkMode, onClick }) => {
return (
<Tooltip content={isDarkMode ? 'Toggle light mode' : 'Toggle dark mode'}>
<button
onClick={onClick}
className="flex items-center rounded-lg border border-gray-200 bg-white p-2 text-xs font-medium text-gray-700 hover:bg-gray-100 hover:text-primary-700 focus:z-10 focus:outline-none focus:ring-2 focus:ring-gray-300 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-500"
>
<span className="sr-only">Toggle dark/light mode</span>
{isDarkMode ? <HiSun className="h-4 w-4" /> : <HiMoon className="h-4 w-4" />}
</button>
</Tooltip>
);
};

const CopyToClipboardButton: FC<ComponentProps<'button'> & CodePreviewState> = ({ isJustCopied, onClick }) => {
return (
<button
onClick={onClick}
className="copy-to-clipboard-button flex items-center border-l border-gray-200 bg-gray-100 px-3 py-2 text-xs font-medium text-gray-600 hover:text-primary-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:text-white"
>
{isJustCopied ? (
<BsCheckLg className="mr-2 h-4 w-4 text-green-500 dark:text-green-400" />
) : (
<BsFillClipboardFill className="mr-2 h-3 w-3" />
)}

{isJustCopied ? 'Code copied!' : 'Copy code'}
</button>
);
};

const CollapseExpandButton: FC<ComponentProps<'button'> & CodePreviewState> = ({ isExpanded, onClick }) => {
return (
<button
onClick={onClick}
className="absolute bottom-0 left-0 w-full border-t border-gray-200 bg-gray-100 px-5 py-2.5 text-sm font-medium text-gray-900 hover:bg-gray-100 hover:text-primary-700 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
>
{isExpanded ? 'Collapse code' : 'Expand code'}
</button>
);
};

const firstComponentDisplayName = (str: string) => {
const firstClosingTag = str.indexOf('>');
const firstSpace = str.indexOf(' ');

return str.substring(1, firstSpace > firstClosingTag ? firstClosingTag : firstSpace).replaceAll(/\s/g, '');
};

const deleteJSXSpaces = (str: string) => {
return str.replaceAll(/{' '}/g, '');
};

const deleteSVGs = (str: string) => {
return str.replaceAll(/<svg[\s\S]*?<\/svg>/g, '<SeeSourceCodeForSVG />');
};

const replaceWebpackImportsOnFunctions = (str: string) => {
return str.replaceAll(/function (.*?) \(props\)(.*?);}/g, '<$1 />');
return str.replaceAll(/function (.*?)\s?\(props\)\s?{(.*)}/g, '$1}');
};

const replaceWebpackImportsOnComponents = (str: string) => {
return str.replaceAll(/(.*?)\(props\)=>\/\*__PURE__(.*)\}(?!.*\})/g, 'SeeSourceCodeForBrokenComponent');
const titleCaseToUpperCamelCase = (str: string) => {
return str
.replaceAll(/(?:^\w|[A-Z]|\b\w)/g, (word) => word.toUpperCase())
.replaceAll(/\s+/g, '')
.replaceAll(/-/g, '');
};

const deleteSVGs = (str: string) => {
return str.replaceAll(/<svg[\s\S]*?<\/svg>/g, '<SeeSourceCodeForSVG />');
const slugToUpperCamelCase = (str: string) => {
return str
.replaceAll(/-/g, ' ')
.replaceAll(/\b[a-z]/g, (letter) => letter.toUpperCase())
.replaceAll(/\s/g, '');
};
26 changes: 0 additions & 26 deletions app/components/fathom.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion app/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const NavbarLinks: FC = () => {
Docs
</Link>
<a
href="https://flowbite.com/docs/getting-started/react/"
href="/docs/getting-started/quickstart"
className="rounded-lg p-2.5 text-sm font-medium text-gray-900 hover:text-cyan-700 dark:text-gray-300 dark:hover:text-cyan-500"
>
Quickstart
Expand Down
Loading