From 65f827888d13053b8f3e6e82ee0ad673731bb67e Mon Sep 17 00:00:00 2001 From: Ryan Huellen Date: Wed, 22 Jan 2025 22:10:32 -0600 Subject: [PATCH 1/6] feat: ability to copy colors in v4 docs --- package.json | 1 + pnpm-lock.yaml | 8 +++ src/components/color-palette.tsx | 94 +++++++++++++++++--------------- src/components/color.tsx | 88 ++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 45 deletions(-) create mode 100644 src/components/color.tsx diff --git a/package.json b/package.json index 24e747e48..8535b899c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@tailwindcss/postcss": "4.0.0", "@types/mdx": "^2.0.13", "clsx": "^2.1.1", + "colorizr": "^3.0.7", "dedent": "^1.5.3", "fathom-client": "^3.7.2", "feed": "^4.2.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9271272e8..927a9572d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + colorizr: + specifier: ^3.0.7 + version: 3.0.7 dedent: specifier: ^1.5.3 version: 1.5.3 @@ -1349,6 +1352,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} + colorizr@3.0.7: + resolution: {integrity: sha512-CIXYuY/LtF2J0UiWcj7y9RjQBPNwdkawgJIyHrtqzsj2hohsC7c/BBC+9o1NjjxgX59zIoTsE8IO02r3MOIN7Q==} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -3616,6 +3622,8 @@ snapshots: color-string: 1.9.1 optional: true + colorizr@3.0.7: {} + comma-separated-tokens@2.0.3: {} commander@7.2.0: {} diff --git a/src/components/color-palette.tsx b/src/components/color-palette.tsx index 5355e460a..b7739f43a 100644 --- a/src/components/color-palette.tsx +++ b/src/components/color-palette.tsx @@ -3,60 +3,64 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import React from "react"; +import { Color } from "./color"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const styles = await fs.readFile(path.join(__dirname, "../../node_modules/tailwindcss/theme.css"), "utf-8"); +const styles = await fs.readFile( + path.join(__dirname, "../../node_modules/tailwindcss/theme.css"), + "utf-8", +); -let colors: Record> = {}; -for (let line of styles.split("\n")) { - if (line.startsWith(" --color-")) { - const [key, value] = line.split(":").map((part) => part.trim().replace(";", "")); - const match = key.match(/^--color-([a-z]+)-(\d+)$/); +const colors: Record> = {}; +for (const line of styles.split("\n")) { + if (line.startsWith(" --color-")) { + const [key, value] = line + .split(":") + .map((part) => part.trim().replace(";", "")); + const match = key.match(/^--color-([a-z]+)-(\d+)$/); - if (match) { - const [, group, shade] = match; + if (match) { + const [, group, shade] = match; - if (!colors[group]) { - colors[group] = {}; - } + if (!colors[group]) { + colors[group] = {}; + } - colors[group][shade] = value; - } - } + colors[group][shade] = value; + } + } } export function ColorPalette() { - return ( -
-
-
50
-
100
-
200
-
300
-
400
-
500
-
600
-
700
-
800
-
900
-
950
-
- {Object.entries(colors).map(([key, shades]) => ( - -

{key}

-
- {Object.keys(shades).map((shade, i) => ( -
- ))} -
- - ))} -
- ); + return ( +
+
+
50
+
100
+
200
+
300
+
400
+
500
+
600
+
700
+
800
+
900
+
950
+
+ {Object.entries(colors).map(([key, shades]) => ( + +

+ {key} +

+
+ {Object.keys(shades).map((shade, i) => ( + + ))} +
+
+ ))} +
+ ); } diff --git a/src/components/color.tsx b/src/components/color.tsx new file mode 100644 index 000000000..2c8f55bf4 --- /dev/null +++ b/src/components/color.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { oklch2hex } from "colorizr"; +import { useEffect, useRef, useState } from "react"; +import clsx from "clsx"; +import { + Button, + Tooltip, + TooltipPanel, + TooltipTrigger, +} from "@headlessui/react"; + +export function Color({ name, shade }: { name: string; shade: string }) { + const [justCopied, setJustCopied] = useState(false); + + const buttonRef = useRef(null); + + const colorVariableName = `--color-${name}-${shade}`; + + const copyHexToClipboard = () => { + if (!buttonRef.current) { + return; + } + + const styleValue = buttonRef.current + .computedStyleMap() + .get(colorVariableName); + + if (!styleValue) { + return; + } + + const oklchWithCSSFunctionalNotation = styleValue.toString(); + + // oklch(0.808 0.114 19.571) to 0.808 0.114 19.571 + const oklch = oklchWithCSSFunctionalNotation.slice(6, -1); + + // 0.808 0.114 19.571 to [0.808, 0.114, 19.571] + const oklchTuple = oklch.split(" ").map(Number) as [number, number, number]; + + const hex = oklch2hex(oklchTuple); + + navigator.clipboard.writeText(hex); + + setJustCopied(true); + }; + + useEffect(() => { + const timeout = setTimeout(() => { + if (!justCopied) { + return; + } + + setJustCopied(false); + }, 1300); + + return () => clearTimeout(timeout); + }, [justCopied]); + + const whiteHasContrastAgainstShade = Number(shade) > 400; + + return ( + + +
{Object.entries(colors).map(([key, shades]) => ( -

- {key} -

+

{key}

{Object.keys(shades).map((shade, i) => ( - + ))}
diff --git a/src/components/color.tsx b/src/components/color.tsx index 8b176a5af..41c9f20bb 100644 --- a/src/components/color.tsx +++ b/src/components/color.tsx @@ -1,86 +1,49 @@ "use client"; -import { oklch2hex } from "colorizr"; -import { useEffect, useRef, useState } from "react"; +import { useRef } from "react"; import clsx from "clsx"; -import { - Button, - Tooltip, - TooltipPanel, - TooltipTrigger, -} from "@headlessui/react"; +import { Button, Tooltip, TooltipPanel, TooltipTrigger } from "@headlessui/react"; -export function Color({ name, shade }: { name: string; shade: string }) { - const [justCopied, setJustCopied] = useState(false); - - const buttonRef = useRef(null); +export function Color({ name, shade, value }: { name: string; shade: string; value: string }) { + const panelRef = useRef(null); const colorVariableName = `--color-${name}-${shade}`; - const copyHexToClipboard = () => { - if (!buttonRef.current) { + function copyHexToClipboard(e: React.MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + let panel = panelRef.current; + if (!panel) { return; } - - const styleValue = buttonRef.current - .computedStyleMap() - .get(colorVariableName); - - if (!styleValue) { - return; - } - - const oklchWithCSSFunctionalNotation = styleValue.toString(); - - // oklch(0.808 0.114 19.571) to 0.808 0.114 19.571 - const oklch = oklchWithCSSFunctionalNotation.slice(6, -1); - - // 0.808 0.114 19.571 to [0.808, 0.114, 19.571] - const oklchTuple = oklch.split(" ").map(Number) as [number, number, number]; - - const hex = oklch2hex(oklchTuple); - - navigator.clipboard.writeText(hex); - - setJustCopied(true); - }; - - useEffect(() => { - const timeout = setTimeout(() => { - if (!justCopied) { - return; - } - - setJustCopied(false); + let prevValue = panel.innerText; + navigator.clipboard.writeText(value); + panel.innerText = "Copied to clipboard!"; + setTimeout(() => { + panel.innerText = prevValue; }, 1300); - - return () => clearTimeout(timeout); - }, [justCopied]); + } return ( - +