feat: Update Button component with size prop

This commit is contained in:
Gauthier Daniels 2025-04-13 14:40:42 +02:00
parent dcf128a2d8
commit 7c83d3b37e
8 changed files with 152 additions and 9 deletions

View File

@ -3,17 +3,24 @@ import clsx from "clsx";
const variantStyles = { const variantStyles = {
primary: primary:
"rounded-full bg-violet-300 py-2 px-4 text-sm font-semibold text-slate-900 hover:bg-violet-200 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-300/50 active:bg-violet-500", "bg-violet-300 font-semibold text-slate-900 hover:bg-violet-200 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-violet-300/50 active:bg-violet-500",
secondary: secondary:
"rounded-full bg-slate-800 py-2 px-4 text-sm font-medium text-white hover:bg-slate-700 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400", "bg-slate-800 font-medium text-white hover:bg-slate-700 focus:outline-hidden focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white/50 active:text-slate-400",
};
const sizeStyles = {
sm: "rounded-md py-1 px-2 text-xs",
md: "rounded-full py-2 px-4 text-sm",
lg: "rounded-full py-3 px-6 text-base",
}; };
type ButtonProps = { type ButtonProps = {
variant?: keyof typeof variantStyles; variant?: keyof typeof variantStyles;
size?: keyof typeof sizeStyles;
} & (React.ComponentPropsWithoutRef<typeof Link> | (React.ComponentPropsWithoutRef<"button"> & { href?: undefined })); } & (React.ComponentPropsWithoutRef<typeof Link> | (React.ComponentPropsWithoutRef<"button"> & { href?: undefined }));
export function Button({ variant = "primary", className, ...props }: ButtonProps) { export function Button({ variant = "primary", size = "md", className, ...props }: ButtonProps) {
className = clsx(variantStyles[variant], "cursor-pointer", className); className = clsx(variantStyles[variant], sizeStyles[size], "cursor-pointer", className);
return typeof props.href === "undefined" ? ( return typeof props.href === "undefined" ? (
<button className={className} {...props} /> <button className={className} {...props} />

View File

@ -0,0 +1,52 @@
import { ClipboardDocumentIcon } from "@heroicons/react/24/outline";
import { prismThemes } from "@/data/themes/prism";
import { Highlight } from "prism-react-renderer";
import { useTheme } from "@/hooks/useTheme";
import { Fragment, useMemo } from "react";
import { Button } from "./Button";
import Prism from "prismjs";
import { toast } from "react-toastify";
export default function CSRFence({ children, language }: { children: string; language: string }) {
const { theme } = useTheme();
const prismTheme = useMemo(() => {
return prismThemes[theme];
}, [theme]);
const copyToClipboard = () => {
navigator.clipboard.writeText(children.trimEnd());
toast.success("Code copied to clipboard!");
};
return (
<>
<Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
{({ className, style, tokens, getTokenProps }) => (
<pre className={className} style={style}>
<code>
{tokens.map((line, lineIndex) => (
<Fragment key={lineIndex}>
{line
.filter((token) => !token.empty)
.map((token, tokenIndex) => (
<span key={tokenIndex} {...getTokenProps({ token })} />
))}
{"\n"}
</Fragment>
))}
</code>
</pre>
)}
</Highlight>
<Button
className="absolute top-2 right-2 w-8 h-8 aspect-square opacity-0 group-hover:opacity-50 hover:opacity-100 transition-opacity"
size="sm"
variant="secondary"
onClick={copyToClipboard}
>
<ClipboardDocumentIcon className="w-full" />
</Button>
</>
);
}

View File

@ -1,17 +1,20 @@
import { Highlight, Prism } from "prism-react-renderer";
import { prismThemes } from "@/data/themes/prism"; import { prismThemes } from "@/data/themes/prism";
import { Highlight } from "prism-react-renderer";
import { useTheme } from "@/hooks/useTheme"; import { useTheme } from "@/hooks/useTheme";
import { Fragment, useMemo } from "react"; import { Fragment, useMemo } from "react";
export function Fence({ children, language }: { children: string; language: string }) { import { clientOnly } from "vike-react/clientOnly";
const { theme } = useTheme();
const CSRFence = clientOnly(() => import("./CSRFence"));
function SSRFence({ children, language }: { children: string; language: string }) {
const { theme } = useTheme();
const prismTheme = useMemo(() => { const prismTheme = useMemo(() => {
return prismThemes[theme]; return prismThemes[theme];
}, [theme]); }, [theme]);
return ( return (
<Highlight code={children.trimEnd()} language={language} theme={prismTheme}> <Highlight code={children.trimEnd()} language={language} theme={prismTheme} prism={Prism}>
{({ className, style, tokens, getTokenProps }) => ( {({ className, style, tokens, getTokenProps }) => (
<pre className={className} style={style}> <pre className={className} style={style}>
<code> <code>
@ -31,3 +34,13 @@ export function Fence({ children, language }: { children: string; language: stri
</Highlight> </Highlight>
); );
} }
export function Fence({ children, language }: { children: string; language: string }) {
return (
<div className="relative group">
<CSRFence language={language} fallback={<SSRFence language={language} children={children} />}>
{children}
</CSRFence>
</div>
);
}

View File

@ -5,6 +5,10 @@ import { fileURLToPath } from "node:url";
import { dirname } from "node:path"; import { dirname } from "node:path";
import Fastify from "fastify"; import Fastify from "fastify";
import { Prism } from "prism-react-renderer";
(typeof global !== "undefined" ? global : window).Prism = Prism;
require("prismjs/components/prism-bash");
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename); const __dirname = dirname(__filename);

View File

@ -45,3 +45,11 @@ pre[class*="language-"] {
.token.combinator { .token.combinator {
color: var(--color-slate-400); color: var(--color-slate-400);
} }
.prism-code {
margin: 0;
}
.prism-code + .prism-code {
margin-bottom: 1rem;
}

View File

@ -27,21 +27,25 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prism-react-renderer": "^2.4.1", "prism-react-renderer": "^2.4.1",
"prismjs": "^1.30.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-highlight-words": "^0.21.0", "react-highlight-words": "^0.21.0",
"react-toastify": "^11.0.5",
"reading-time-estimator": "^1.12.0", "reading-time-estimator": "^1.12.0",
"simple-functional-loader": "^1.2.1", "simple-functional-loader": "^1.2.1",
"telefunc": "^0.1.87", "telefunc": "^0.1.87",
"unplugin-fonts": "^1.3.1", "unplugin-fonts": "^1.3.1",
"vike": "^0.4.224", "vike": "^0.4.224",
"vike-react": "^0.5.13" "vike-react": "^0.5.13",
"vite-plugin-prismjs": "^0.0.11"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.22.0", "@eslint/js": "^9.22.0",
"@tailwindcss/vite": "^4.0.12", "@tailwindcss/vite": "^4.0.12",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/node": "^18.19.76", "@types/node": "^18.19.76",
"@types/prismjs": "^1.26.5",
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"@types/react-highlight-words": "^0.20.0", "@types/react-highlight-words": "^0.20.0",

51
app/pnpm-lock.yaml generated
View File

@ -68,6 +68,9 @@ importers:
prism-react-renderer: prism-react-renderer:
specifier: ^2.4.1 specifier: ^2.4.1
version: 2.4.1(react@19.1.0) version: 2.4.1(react@19.1.0)
prismjs:
specifier: ^1.30.0
version: 1.30.0
react: react:
specifier: ^19.0.0 specifier: ^19.0.0
version: 19.1.0 version: 19.1.0
@ -77,6 +80,9 @@ importers:
react-highlight-words: react-highlight-words:
specifier: ^0.21.0 specifier: ^0.21.0
version: 0.21.0(react@19.1.0) version: 0.21.0(react@19.1.0)
react-toastify:
specifier: ^11.0.5
version: 11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
reading-time-estimator: reading-time-estimator:
specifier: ^1.12.0 specifier: ^1.12.0
version: 1.12.0 version: 1.12.0
@ -95,6 +101,9 @@ importers:
vike-react: vike-react:
specifier: ^0.5.13 specifier: ^0.5.13
version: 0.5.13(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vike@0.4.228(react-streaming@0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3))) version: 0.5.13(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vike@0.4.228(react-streaming@0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)))
vite-plugin-prismjs:
specifier: ^0.0.11
version: 0.0.11(prismjs@1.30.0)
devDependencies: devDependencies:
'@eslint/js': '@eslint/js':
specifier: ^9.22.0 specifier: ^9.22.0
@ -108,6 +117,9 @@ importers:
'@types/node': '@types/node':
specifier: ^18.19.76 specifier: ^18.19.76
version: 18.19.86 version: 18.19.86
'@types/prismjs':
specifier: ^1.26.5
version: 1.26.5
'@types/react': '@types/react':
specifier: ^19.0.10 specifier: ^19.0.10
version: 19.1.1 version: 19.1.1
@ -1237,6 +1249,11 @@ packages:
avvio@9.1.0: avvio@9.1.0:
resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==} resolution: {integrity: sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==}
babel-plugin-prismjs@2.1.0:
resolution: {integrity: sha512-ehzSKYfeAz4U78zi/sfwsjDPlq0LvDKxNefcZTJ/iKBu+plsHsLqZhUeGf1+82LAcA35UZGbU6ksEx2Utphc/g==}
peerDependencies:
prismjs: ^1.18.0
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -2322,6 +2339,10 @@ packages:
peerDependencies: peerDependencies:
react: '>=16.0.0' react: '>=16.0.0'
prismjs@1.30.0:
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
engines: {node: '>=6'}
process-warning@4.0.1: process-warning@4.0.1:
resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==} resolution: {integrity: sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==}
@ -2368,6 +2389,12 @@ packages:
react: '>=18' react: '>=18'
react-dom: '>=18' react-dom: '>=18'
react-toastify@11.0.5:
resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==}
peerDependencies:
react: ^18 || ^19
react-dom: ^18 || ^19
react@19.1.0: react@19.1.0:
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2822,6 +2849,10 @@ packages:
vite: vite:
optional: true optional: true
vite-plugin-prismjs@0.0.11:
resolution: {integrity: sha512-20NBQxg/zH+3FTrlU6BQTob720xkuXNYtrx7psAQ4E6pMcRDeLEK77QU9kXURU587+f2To7ASH1JVTGbXVV/vQ==}
engines: {node: '>=12.0.0'}
vite@6.2.6: vite@6.2.6:
resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==} resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@ -3911,6 +3942,10 @@ snapshots:
'@fastify/error': 4.1.0 '@fastify/error': 4.1.0
fastq: 1.19.1 fastq: 1.19.1
babel-plugin-prismjs@2.1.0(prismjs@1.30.0):
dependencies:
prismjs: 1.30.0
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
big.js@5.2.2: {} big.js@5.2.2: {}
@ -5118,6 +5153,8 @@ snapshots:
clsx: 2.1.1 clsx: 2.1.1
react: 19.1.0 react: 19.1.0
prismjs@1.30.0: {}
process-warning@4.0.1: {} process-warning@4.0.1: {}
process-warning@5.0.0: {} process-warning@5.0.0: {}
@ -5165,6 +5202,12 @@ snapshots:
react: 19.1.0 react: 19.1.0
react-dom: 19.1.0(react@19.1.0) react-dom: 19.1.0(react@19.1.0)
react-toastify@11.0.5(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
clsx: 2.1.1
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react@19.1.0: {} react@19.1.0: {}
read-pkg@3.0.0: read-pkg@3.0.0:
@ -5693,6 +5736,14 @@ snapshots:
react-streaming: 0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-streaming: 0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
vite: 6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3) vite: 6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)
vite-plugin-prismjs@0.0.11(prismjs@1.30.0):
dependencies:
'@babel/core': 7.26.10
babel-plugin-prismjs: 2.1.0(prismjs@1.30.0)
transitivePeerDependencies:
- prismjs
- supports-color
vite@6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3): vite@6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3):
dependencies: dependencies:
esbuild: 0.25.2 esbuild: 0.25.2

View File

@ -1,3 +1,4 @@
import prismjsVitePlugin from "vite-plugin-prismjs";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import Unfonts from "unplugin-fonts/vite"; import Unfonts from "unplugin-fonts/vite";
import { telefunc } from "telefunc/vite"; import { telefunc } from "telefunc/vite";
@ -7,6 +8,9 @@ import vike from "vike/plugin";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
prismjsVitePlugin({
languages: ["javascript", "typescript", "tsx", "jsx", "css", "html", "bash"],
}),
Unfonts({ Unfonts({
fontsource: { fontsource: {
families: ["Inter Variable", "Lexend Variable"], families: ["Inter Variable", "Lexend Variable"],