(null!)
+ const [isExpanded, setIsExpanded] = useState(false)
const [copied, setCopied] = useState(false)
const Icon = copied ? ClipboardDocumentCheckIcon : ClipboardDocumentListIcon
@@ -28,12 +37,38 @@ export default function MarkdownPre(
return (
<>
-
+ {hasFocus ? (
+ setIsExpanded(!isExpanded)}
+ type="button"
+ >
+ {isExpanded ? "Collapse code" : "Expand code"}
+
+ ) : null}
+
+
+ {cloneElement(children, {
+ className: clsx(
+ children.props.className,
+ "grid [font-size:inherit] p-8 overflow-x-auto [font-weight:inherit] bg-transparent",
+ "focus:outline-none focus-visible:ring-inset focus-visible:ring focus-visible:ring-indigo-500",
+ ),
+ })}
+
{
- if (node.tagName !== "pre") {
- return
- }
+ return (tree) =>
+ visit(
+ tree,
+ (node) => node.type === "element" && node.tagName === "pre",
+ (node) => {
+ const code = Array.isArray(node.children)
+ ? node.children[0]
+ : node.children
- // Add `tabindex="0"` to the code element to make it scrollable
- const code = Array.isArray(node.children) ? node.children[0] : node.children
- code.properties.tabindex = "0"
- }
-
- return (tree) => visit(tree, "element", visitor)
+ // Add `tabindex="0"` to the code element to make it scrollable
+ code.properties.tabindex = "0"
+ },
+ )
}
diff --git a/config/rehype-code-meta.mjs b/config/rehype-code-meta.mjs
index 5e515172..c6a25ed5 100644
--- a/config/rehype-code-meta.mjs
+++ b/config/rehype-code-meta.mjs
@@ -1,9 +1,9 @@
import rangeParser from "parse-numeric-range"
import { SKIP, visit } from "unist-util-visit"
-function calculateLinesToHighlight(meta) {
+function calculateLines(meta, char) {
const [range] = meta
- .filter((item) => item.startsWith("{"))
+ .filter((item) => item.startsWith(char))
.map((item) => item.slice(1, -1))
if (range) {
@@ -15,33 +15,43 @@ function calculateLinesToHighlight(meta) {
}
export default function rehypeCodeMeta() {
- const visitor = (pre, _, parent) => {
- const shouldHighlight = calculateLinesToHighlight(parent.data.meta)
-
- visit(
- pre,
- (t) => t.type === "element" && t.properties?.className?.includes("line"),
- (line, index, parent) => {
- if (!line.children.length && index !== parent.children.length - 1) {
- // Add a zero-width space to allow highlighting over empty lines
- line.children.push({ type: "text", value: "\u200b" })
- }
-
- if (shouldHighlight(index / 2)) {
- line.properties.className ??= []
- line.properties.className.push("highlight")
- }
- },
- )
-
- return SKIP
- }
-
return (tree) => {
visit(
tree,
(node) => node.type === "element" && node.tagName === "pre",
- visitor,
+ (node) => {
+ const attrs = node.data?.attributes ?? []
+ const shouldHighlight = calculateLines(attrs, "{")
+ const shouldFocus = calculateLines(attrs, "[")
+
+ visit(
+ node,
+ (t) =>
+ t.type === "element" && t.properties?.className?.includes("line"),
+ (line, index, parent) => {
+ if (!line.children.length && index !== parent.children.length - 1) {
+ // Add a zero-width space to allow highlighting over empty lines
+ line.children.push({ type: "text", value: "\u200b" })
+ }
+
+ if (shouldHighlight(index)) {
+ line.properties.className ??= []
+ line.properties.className.push("highlight")
+ }
+
+ if (shouldFocus(index)) {
+ line.properties.className ??= []
+ line.properties.className.push("focus")
+
+ // Add a prop that indicates that this code block has focused
+ // so we can display an expand/collapse button.
+ node.properties.hasFocus = true
+ }
+ },
+ )
+
+ return SKIP
+ },
)
}
}
diff --git a/config/rehype-code-titles.mjs b/config/rehype-code-titles.mjs
index b8d537a3..b9cd0de0 100644
--- a/config/rehype-code-titles.mjs
+++ b/config/rehype-code-titles.mjs
@@ -26,30 +26,27 @@ function createTitle(title) {
export default function rehypeCodeTitles() {
const visitor = (node, index, parent) => {
const code = Array.isArray(node.children) ? node.children[0] : node.children
- const [lang, ...meta] = (code.properties.className || [])
- .filter((cls) => cls.startsWith("language-"))
- .join("")
- .split(":")
+ const meta = code.data?.meta
const regex = /{[\d,-]+}/
- const [title] = meta.filter((cls) => !regex.test(cls))
- const rest = meta.filter((cls) => cls !== title)
+ const parsed = (typeof meta === "string" ? meta : "")
+ .split(" ")
+ .filter(Boolean)
+ const [title] = parsed.filter((cls) => !regex.test(cls))
+ const rest = parsed?.filter((cls) => cls !== title)
- // Add the language to the code block so Shiki can highlight it
- code.properties.className = [lang]
+ // Add remaining metadata to the pre element
+ node.data ??= {}
+ node.data.attributes = rest
// Wrap the code block in a div with the title
parent.children[index] = {
children: title ? [createTitle(title), node] : [node],
- data: {
- // Add the remaining metadata to the parent div. This is required
- // since rehype-shiki replaces the pre and code elements.
- meta: rest,
- },
properties: {
className: [
- "group relative mx-0 sm:-mx-8",
- title ? "has-title pt-12" : "",
+ "code-block group",
+ title ? "has-title" : "",
+ meta?.demo && "demo",
].filter(Boolean),
},
tagName: "div",
diff --git a/config/rehype-shiki.mjs b/config/rehype-shiki.mjs
new file mode 100644
index 00000000..ba147767
--- /dev/null
+++ b/config/rehype-shiki.mjs
@@ -0,0 +1,60 @@
+import { toText } from "hast-util-to-text"
+import { SKIP, visit } from "unist-util-visit"
+
+export default function rehypeShiki({ highlighter }) {
+ return (ast) => {
+ visit(
+ ast,
+ (node) => {
+ return (
+ node.tagName === "pre" &&
+ Array.isArray(node.children) &&
+ node.children.length === 1 &&
+ node.children[0].tagName === "code" &&
+ typeof node.children[0].properties === "object" &&
+ node.children[0].properties !== null &&
+ Array.isArray(node.children[0].properties.className) &&
+ typeof node.children[0].properties.className[0] === "string" &&
+ node.children[0].properties.className[0].startsWith("language-")
+ )
+ },
+ (node) => {
+ const source = toText(node).slice(0, -1)
+ const language = node.children[0].properties.className[0]
+ .split("language-")
+ .at(-1)
+
+ let output = []
+ try {
+ output = highlighter.codeToThemedTokens(source, language)
+ } catch (error) {
+ return
+ }
+
+ // Add properties to the pre tag
+ node.properties ??= {}
+ node.properties.className ??= []
+ node.properties.className.push("shiki")
+
+ const code = node.children[0]
+ code.children = output.map((line) => ({
+ type: "element",
+ tagName: "span",
+ properties: {
+ className: ["line"],
+ },
+ children: line.map((token) => ({
+ type: "element",
+ tagName: "span",
+ properties: {
+ style: `color: ${token.color};`,
+ },
+ children: [{ type: "text", value: token.content }],
+ })),
+ }))
+
+ return SKIP
+ },
+ )
+ }
+}
diff --git a/config/remark-code-block.mjs b/config/remark-code-block.mjs
new file mode 100644
index 00000000..c14a546e
--- /dev/null
+++ b/config/remark-code-block.mjs
@@ -0,0 +1,45 @@
+import fs from "node:fs/promises"
+import path from "node:path"
+import { SKIP, visit } from "unist-util-visit"
+
+const getAttr = (node, name) => {
+ return node.attributes.find((attr) => attr.name === name)?.value
+}
+
+export default function remarkCodeBlock() {
+ return async (ast, vfile) => {
+ const filename = vfile.history[0]
+ const dir = path.dirname(filename)
+ const nodes = []
+
+ // Collect all the code block nodes
+ visit(
+ ast,
+ (node) => node.type === "mdxJsxFlowElement" && node.name === "CodeBlock",
+ (node) => {
+ nodes.push(node)
+ return SKIP
+ },
+ )
+
+ const promises = nodes.map(async (node) => {
+ const filename = getAttr(node, "filename")
+ const meta = getAttr(node, "meta")
+ if (!filename) return
+
+ const raw = await fs.readFile(path.join(dir, filename), "utf8")
+
+ // Add the code block as a child of the JSX element
+ node.children = [
+ {
+ type: "code",
+ lang: path.extname(filename).slice(1),
+ value: raw.trim(),
+ meta: `${filename} ${meta ?? ""}`,
+ },
+ ]
+ })
+
+ await Promise.all(promises)
+ }
+}
diff --git a/config/remark-code-meta.mjs b/config/remark-code-meta.mjs
deleted file mode 100644
index e0d15a04..00000000
--- a/config/remark-code-meta.mjs
+++ /dev/null
@@ -1,24 +0,0 @@
-import { visit } from "unist-util-visit"
-
-/**
- * Combine the code block language and meta into a single string so it is passed
- * through as the class name. That way we can parse it via the rehype plugin.
- */
-export default function remarkCodeMeta() {
- const visitor = (node) => {
- if (node.lang && node.meta) {
- // Sanitize the meta into a string without spaces to properly pass through
- // as a class.
- const meta = node.meta
- ?.split(",")
- .map((str) => str.trim())
- .join()
- .split(" ")
- .join(":")
-
- node.lang = `${node.lang}:${meta}`
- }
- }
-
- return (ast) => visit(ast, "code", visitor)
-}
diff --git a/config/remark-demo.mjs b/config/remark-demo.mjs
new file mode 100644
index 00000000..bdbf121c
--- /dev/null
+++ b/config/remark-demo.mjs
@@ -0,0 +1,88 @@
+import fs from "node:fs/promises"
+import path from "node:path"
+import { SKIP, visit } from "unist-util-visit"
+
+export default function remarkDemo() {
+ return async (ast, vfile) => {
+ const filename = vfile.history[0]
+ const dir = path.dirname(filename)
+ const nodes = []
+
+ // Collect all the demo nodes
+ visit(
+ ast,
+ (node) =>
+ node.type === "mdxJsxFlowElement" &&
+ node.name === "Demo" &&
+ node.children?.[0]?.name,
+ (node) => {
+ nodes.push(node)
+ return SKIP
+ },
+ )
+
+ const promises = nodes.map(async (node) => {
+ const name = node.children?.[0]?.name
+ const raw = await fs.readFile(path.join(dir, `${name}.tsx`), "utf8")
+
+ // Replace the child nodes with the source code
+ node.children = [
+ {
+ type: "code",
+ lang: "tsx",
+ meta: { demo: true },
+ value: raw,
+ },
+ ]
+
+ // Add the raw source for copying
+ node.attributes.push({
+ type: "mdxJsxAttribute",
+ name: "raw",
+ value: raw.trim(),
+ })
+
+ // Add the component name as an attribute so we can display the GitHub path
+ node.attributes.push({
+ type: "mdxJsxAttribute",
+ name: "name",
+ value: `${path.basename(dir)}/${name}.tsx`,
+ })
+
+ // Add the component as an attribute. Using children makes more sense, but
+ // that complicates the parsing of the source code.
+ node.attributes.push({
+ type: "mdxJsxAttribute",
+ name: "component",
+ value: {
+ data: {
+ type: "mdxJsxAttributeValueExpression",
+ estree: {
+ type: "Program",
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "JSXElement",
+ openingElement: {
+ type: "JSXOpeningElement",
+ selfClosing: true,
+ attributes: [],
+ name: {
+ name,
+ type: "JSXIdentifier",
+ },
+ },
+ children: [],
+ },
+ },
+ ],
+ },
+ },
+ },
+ })
+ })
+
+ await Promise.all(promises)
+ }
+}
diff --git a/next.config.mjs b/next.config.mjs
index 6cb0799a..251c6e9a 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,5 +1,4 @@
import nextMDX from "@next/mdx"
-import rehypeShiki from "@stefanprobst/rehype-shiki"
import { fileURLToPath } from "node:url"
import rehypeSlug from "rehype-slug"
import remarkFrontmatter from "remark-frontmatter"
@@ -14,8 +13,10 @@ import rehypeCodeMeta from "./config/rehype-code-meta.mjs"
import rehypeCodeTitles from "./config/rehype-code-titles.mjs"
import rehypeHeaderId from "./config/rehype-header-id.mjs"
import rehypeHeadings from "./config/rehype-headings.mjs"
+import rehypeShiki from "./config/rehype-shiki.mjs"
import remarkAutoImagePath from "./config/remark-auto-image-path.mjs"
-import remarkCodeMeta from "./config/remark-code-meta.mjs"
+import remarkCodeBlock from "./config/remark-code-block.mjs"
+import remarkDemo from "./config/remark-demo.mjs"
import remarkFrontmatterMetadata from "./config/remark-frontmatter-metadata.mjs"
/** @type {import("next").NextConfig} */
@@ -47,8 +48,8 @@ const withMDX = nextMDX({
rehypeHeaderId,
rehypeCodeTitles,
[rehypeShiki, { highlighter }],
- rehypeCodeMeta,
rehypeCodeA11y,
+ rehypeCodeMeta,
rehypeCallout,
],
remarkPlugins: [
@@ -57,8 +58,9 @@ const withMDX = nextMDX({
remarkFrontmatterMetadata,
remarkFrontmatter,
[remarkMdxFrontmatter, { name: "meta" }],
- remarkCodeMeta,
remarkAutoImagePath,
+ remarkDemo,
+ remarkCodeBlock,
],
},
})
diff --git a/package.json b/package.json
index 35434472..dca968ba 100644
--- a/package.json
+++ b/package.json
@@ -26,15 +26,16 @@
"@next/mdx": "^13.5.2",
"@octokit/rest": "^20.0.1",
"@prisma/client": "^5.2.0",
- "@stefanprobst/rehype-shiki": "^2.2.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/typography": "^0.5.9",
"autoprefixer": "^10.4.14",
+ "change-case": "^4.1.2",
"clsx": "^2.0.0",
"fast-glob": "^3.3.0",
"feed": "^4.2.2",
"hast-util-has-property": "^2.0.1",
"hast-util-heading-rank": "^2.1.1",
+ "hast-util-to-text": "^4.0.0",
"next": "^13.5.2",
"next-mdx-remote": "^4.4.1",
"parse-numeric-range": "^1.3.0",
@@ -81,6 +82,7 @@
"gray-matter": "^4.0.3",
"lariat": "^2.0.1",
"prettier": "^3.0.3",
+ "prettier-plugin-css-order": "^2.0.0",
"prettier-plugin-jsdoc": "^1.0.1",
"prettier-plugin-tailwindcss": "^0.5.4",
"prisma": "^5.2.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index eeb01464..85cdb7bd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,9 +32,6 @@ importers:
'@prisma/client':
specifier: ^5.2.0
version: 5.2.0(prisma@5.2.0)
- '@stefanprobst/rehype-shiki':
- specifier: ^2.2.1
- version: 2.2.1(shiki@0.14.3)
'@tailwindcss/container-queries':
specifier: ^0.1.1
version: 0.1.1(tailwindcss@3.3.3)
@@ -44,6 +41,9 @@ importers:
autoprefixer:
specifier: ^10.4.14
version: 10.4.14(postcss@8.4.26)
+ change-case:
+ specifier: ^4.1.2
+ version: 4.1.2
clsx:
specifier: ^2.0.0
version: 2.0.0
@@ -59,6 +59,9 @@ importers:
hast-util-heading-rank:
specifier: ^2.1.1
version: 2.1.1
+ hast-util-to-text:
+ specifier: ^4.0.0
+ version: 4.0.0
next:
specifier: ^13.5.2
version: 13.5.2(@babel/core@7.22.11)(react-dom@18.2.0)(react@18.2.0)
@@ -192,12 +195,15 @@ importers:
prettier:
specifier: ^3.0.3
version: 3.0.3
+ prettier-plugin-css-order:
+ specifier: ^2.0.0
+ version: 2.0.0(postcss@8.4.26)(prettier@3.0.3)
prettier-plugin-jsdoc:
specifier: ^1.0.1
version: 1.0.1(prettier@3.0.3)
prettier-plugin-tailwindcss:
specifier: ^0.5.4
- version: 0.5.4(prettier-plugin-jsdoc@1.0.1)(prettier@3.0.3)
+ version: 0.5.4(prettier-plugin-css-order@2.0.0)(prettier-plugin-jsdoc@1.0.1)(prettier@3.0.3)
prisma:
specifier: ^5.2.0
version: 5.2.0
@@ -911,25 +917,6 @@ packages:
resolution: {integrity: sha512-0xd7qez0AQ+MbHatZTlI1gu5vkG8r7MYRUJAHPAHJBmGLs16zpkrpAVLvjQKQOqaXPDUBwOiJzNc00znHSCVBw==}
dev: true
- /@stefanprobst/rehype-shiki@2.2.1(shiki@0.14.3):
- resolution: {integrity: sha512-5riyvOc6x9iw4obQxuxMZIOP5gAtym5Pp1vghVXkRXgSTdh8aE14viZL1ooXw76hAmi+rXr2HQdnvEUvt1foBA==}
- engines: {node: '>=14.17', yarn: 1.x}
- peerDependencies:
- shiki: '*'
- dependencies:
- hast-util-to-string: 2.0.0
- json5: 2.2.3
- parse-numeric-range: 1.3.0
- remark-parse: 10.0.2
- remark-rehype: 10.1.0
- shiki: 0.14.3
- shiki-renderer-hast: 1.1.5(shiki@0.14.3)
- unified: 10.1.2
- unist-util-visit: 4.1.2
- transitivePeerDependencies:
- - supports-color
- dev: false
-
/@swc/helpers@0.5.2:
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
dependencies:
@@ -1001,6 +988,12 @@ packages:
'@types/unist': 2.0.7
dev: false
+ /@types/hast@3.0.1:
+ resolution: {integrity: sha512-hs/iBJx2aydugBQx5ETV3ZgeSS0oIreQrFJ4bjBl0XvM4wAmDjFEALY7p0rTSLt2eL+ibjRAAs9dTPiCLtmbqQ==}
+ dependencies:
+ '@types/unist': 3.0.0
+ dev: false
+
/@types/js-yaml@4.0.5:
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
dev: false
@@ -1707,6 +1700,13 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ /camel-case@4.1.2:
+ resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+ dependencies:
+ pascal-case: 3.1.2
+ tslib: 2.6.2
+ dev: false
+
/camelcase-css@2.0.1:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
@@ -1719,6 +1719,14 @@ packages:
resolution: {integrity: sha512-HWJnhnID+0YMtGlzcp3T9drmBJUVDchPJ08tpUGFLs9CYlwWPH2uLgpHn8fND5pCgXVtnGS3H4QR9XLMHVNkHw==}
dev: false
+ /capital-case@1.0.4:
+ resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+ upper-case-first: 2.0.2
+ dev: false
+
/ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
dev: false
@@ -1738,6 +1746,23 @@ packages:
ansi-styles: 4.3.0
supports-color: 7.2.0
+ /change-case@4.1.2:
+ resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==}
+ dependencies:
+ camel-case: 4.1.2
+ capital-case: 1.0.4
+ constant-case: 3.0.4
+ dot-case: 3.0.4
+ header-case: 2.0.4
+ no-case: 3.0.4
+ param-case: 3.0.4
+ pascal-case: 3.1.2
+ path-case: 3.0.4
+ sentence-case: 3.0.4
+ snake-case: 3.0.4
+ tslib: 2.6.2
+ dev: false
+
/character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
dev: false
@@ -1850,6 +1875,14 @@ packages:
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ /constant-case@3.0.4:
+ resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+ upper-case: 2.0.2
+ dev: false
+
/convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
@@ -1861,6 +1894,15 @@ packages:
shebang-command: 2.0.0
which: 2.0.2
+ /css-declaration-sorter@7.1.0(postcss@8.4.26):
+ resolution: {integrity: sha512-suZ/qn6r4iXWLGOlD34EfYCCZxq18KXC/OYIyPHUnV3MHl3JO9LmcaVFHpgbHwfg+SayGOpXURJIs3aJpxcjyA==}
+ engines: {node: ^14 || ^16 || >=18}
+ peerDependencies:
+ postcss: ^8.0.9
+ dependencies:
+ postcss: 8.4.26
+ dev: true
+
/cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@@ -1975,6 +2017,13 @@ packages:
dependencies:
esutils: 2.0.3
+ /dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+ dev: false
+
/ejs@3.1.9:
resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==}
engines: {node: '>=0.10.0'}
@@ -2864,10 +2913,10 @@ packages:
'@types/unist': 2.0.7
dev: false
- /hast-util-parse-selector@3.1.1:
- resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
+ /hast-util-is-element@3.0.0:
+ resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
dependencies:
- '@types/hast': 2.3.5
+ '@types/hast': 3.0.1
dev: false
/hast-util-to-estree@2.3.3:
@@ -2898,18 +2947,24 @@ packages:
'@types/hast': 2.3.5
dev: false
+ /hast-util-to-text@4.0.0:
+ resolution: {integrity: sha512-EWiE1FSArNBPUo1cKWtzqgnuRQwEeQbQtnFJRYV1hb1BWDgrAlBU0ExptvZMM/KSA82cDpm2sFGf3Dmc5Mza3w==}
+ dependencies:
+ '@types/hast': 3.0.1
+ '@types/unist': 3.0.0
+ hast-util-is-element: 3.0.0
+ unist-util-find-after: 5.0.0
+ dev: false
+
/hast-util-whitespace@2.0.1:
resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==}
dev: false
- /hastscript@7.2.0:
- resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==}
+ /header-case@2.0.4:
+ resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==}
dependencies:
- '@types/hast': 2.3.5
- comma-separated-tokens: 2.0.3
- hast-util-parse-selector: 3.1.1
- property-information: 6.2.0
- space-separated-tokens: 2.0.2
+ capital-case: 1.0.4
+ tslib: 2.6.2
dev: false
/ieee754@1.2.1:
@@ -3388,6 +3443,12 @@ packages:
dependencies:
js-tokens: 4.0.0
+ /lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
/lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
dependencies:
@@ -4013,7 +4074,6 @@ packages:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- dev: false
/napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
@@ -4089,6 +4149,13 @@ packages:
'@types/nlcst': 1.0.1
dev: false
+ /no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.6.2
+ dev: false
+
/node-abi@3.45.0:
resolution: {integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==}
engines: {node: '>=10'}
@@ -4212,6 +4279,13 @@ packages:
dependencies:
p-limit: 3.1.0
+ /param-case@3.0.4:
+ resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.2
+ dev: false
+
/parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -4243,6 +4317,20 @@ packages:
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
dev: false
+ /pascal-case@3.1.2:
+ resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+ dev: false
+
+ /path-case@3.0.4:
+ resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.2
+ dev: false
+
/path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -4326,6 +4414,15 @@ packages:
postcss: 8.4.26
dev: false
+ /postcss-less@6.0.0(postcss@8.4.26):
+ resolution: {integrity: sha512-FPX16mQLyEjLzEuuJtxA8X3ejDLNGGEG503d2YGZR5Ask1SpDN8KmZUMpzCvyalWRywAn1n1VOA5dcqfCLo5rg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ postcss: ^8.3.5
+ dependencies:
+ postcss: 8.4.26
+ dev: true
+
/postcss-load-config@4.0.1(postcss@8.4.26):
resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==}
engines: {node: '>= 14'}
@@ -4353,6 +4450,15 @@ packages:
postcss-selector-parser: 6.0.13
dev: false
+ /postcss-scss@4.0.9(postcss@8.4.26):
+ resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.4.29
+ dependencies:
+ postcss: 8.4.26
+ dev: true
+
/postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
@@ -4389,7 +4495,6 @@ packages:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
- dev: false
/prebuild-install@7.1.1:
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
@@ -4414,6 +4519,20 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ /prettier-plugin-css-order@2.0.0(postcss@8.4.26)(prettier@3.0.3):
+ resolution: {integrity: sha512-BCstZZ78G6FH/Ms1hl5ZLnSMso3ModM+SEZY8QrJzgyxjuCjSrS2bveeLq9wJMn20h9tAb6+C2FZPh5qdZ2qsw==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ prettier: 3.x
+ dependencies:
+ css-declaration-sorter: 7.1.0(postcss@8.4.26)
+ postcss-less: 6.0.0(postcss@8.4.26)
+ postcss-scss: 4.0.9(postcss@8.4.26)
+ prettier: 3.0.3
+ transitivePeerDependencies:
+ - postcss
+ dev: true
+
/prettier-plugin-jsdoc@1.0.1(prettier@3.0.3):
resolution: {integrity: sha512-07q74MfX9m+xHK2+Lr4c+igiEzAKVDWhqkvlm65WoYJUlRiaV6STXcEtcZMhrPPYgNeQRgb9FJmgE/n+OI4MpQ==}
engines: {node: '>=14.13.1 || >=16.0.0'}
@@ -4428,7 +4547,7 @@ packages:
- supports-color
dev: true
- /prettier-plugin-tailwindcss@0.5.4(prettier-plugin-jsdoc@1.0.1)(prettier@3.0.3):
+ /prettier-plugin-tailwindcss@0.5.4(prettier-plugin-css-order@2.0.0)(prettier-plugin-jsdoc@1.0.1)(prettier@3.0.3):
resolution: {integrity: sha512-QZzzB1bID6qPsKHTeA9qPo1APmmxfFrA5DD3LQ+vbTmAnY40eJI7t9Q1ocqel2EKMWNPLJqdTDWZj1hKYgqSgg==}
engines: {node: '>=14.21.3'}
peerDependencies:
@@ -4481,6 +4600,7 @@ packages:
optional: true
dependencies:
prettier: 3.0.3
+ prettier-plugin-css-order: 2.0.0(postcss@8.4.26)(prettier@3.0.3)
prettier-plugin-jsdoc: 1.0.1(prettier@3.0.3)
dev: true
@@ -4866,6 +4986,14 @@ packages:
dependencies:
lru-cache: 6.0.0
+ /sentence-case@3.0.4:
+ resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==}
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+ upper-case-first: 2.0.2
+ dev: false
+
/serialize-javascript@6.0.1:
resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==}
dependencies:
@@ -4907,15 +5035,6 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- /shiki-renderer-hast@1.1.5(shiki@0.14.3):
- resolution: {integrity: sha512-XOHX0/mIRJZnqPNGoyyJ9bUrqUwj/tLUJQ5SuU/UkQLTiSJjiHaCP17HRB7/VSQoor4+WJF3QkT8BXbipyJN0Q==}
- peerDependencies:
- shiki: '*'
- dependencies:
- hastscript: 7.2.0
- shiki: 0.14.3
- dev: false
-
/shiki@0.14.3:
resolution: {integrity: sha512-U3S/a+b0KS+UkTyMjoNojvTgrBHjgp7L6ovhFVZsXmBGnVdQ4K4U9oK0z63w538S91ATngv1vXigHCSWOwnr+g==}
dependencies:
@@ -4956,10 +5075,16 @@ packages:
engines: {node: '>=8'}
dev: true
+ /snake-case@3.0.4:
+ resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.2
+ dev: false
+
/source-map-js@1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
- dev: false
/source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
@@ -5424,6 +5549,13 @@ packages:
vfile: 6.0.1
dev: false
+ /unist-util-find-after@5.0.0:
+ resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
+ dependencies:
+ '@types/unist': 3.0.0
+ unist-util-is: 6.0.0
+ dev: false
+
/unist-util-generated@2.0.1:
resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==}
dev: false
@@ -5527,6 +5659,18 @@ packages:
escalade: 3.1.1
picocolors: 1.0.0
+ /upper-case-first@2.0.2:
+ resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
+ /upper-case@2.0.2:
+ resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==}
+ dependencies:
+ tslib: 2.6.2
+ dev: false
+
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
diff --git a/prettier.config.js b/prettier.config.js
index 96509c0e..a7a26573 100644
--- a/prettier.config.js
+++ b/prettier.config.js
@@ -1,7 +1,9 @@
const config = {
+ cssDeclarationSorterOrder: "alphabetical",
plugins: [
"./node_modules/prettier-plugin-jsdoc/dist/index.js",
"prettier-plugin-tailwindcss",
+ "prettier-plugin-css-order",
],
proseWrap: "always",
semi: false,
diff --git a/scripts/generateRssFeed.ts b/scripts/generateRssFeed.ts
deleted file mode 100644
index c0357ad5..00000000
--- a/scripts/generateRssFeed.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import glob from "fast-glob"
-import { Feed } from "feed"
-import matter from "gray-matter"
-import fs from "node:fs/promises"
-import path from "node:path"
-import { fileURLToPath } from "url"
-import { PostMeta } from "../app/components/layouts/PostLayout.jsx"
-import { siteMeta } from "../app/lib/siteMeta.js"
-
-const baseURL = new URL("../app/(main)/blog/posts/", import.meta.url)
-
-async function readFrontmatter(filename: string) {
- const content = await fs.readFile(new URL(filename, baseURL), "utf8")
- const frontmatter = matter(content)
-
- return {
- ...(frontmatter.data as PostMeta),
- slug: path.basename(filename, path.extname(filename)),
- }
-}
-
-async function getAllPosts() {
- const cwd = fileURLToPath(baseURL)
- const filenames = await glob("*/content.{md,mdx}", { cwd })
- const posts = await Promise.all(filenames.map(readFrontmatter))
-
- return posts.sort(
- (a, z) => new Date(z.date).getTime() - new Date(a.date).getTime(),
- )
-}
-
-const posts = await getAllPosts()
-const author = { email: siteMeta.email, name: "Mark Skelton" }
-
-const feed = new Feed({
- author,
- copyright: `All rights reserved ${new Date().getFullYear()}`,
- description: siteMeta.tagline,
- favicon: `${siteMeta.url}/favicon.ico`,
- feedLinks: {
- json: `${siteMeta.url}/rss/feed.json`,
- rss2: `${siteMeta.url}/rss/feed.xml`,
- },
- id: siteMeta.url,
- image: `${siteMeta.url}/favicon.ico`,
- link: siteMeta.url,
- title: author.name,
-})
-
-for (const post of posts) {
- const url = `${siteMeta.url}/blog/${post.slug}`
- // const html = ReactDOMServer.renderToStaticMarkup(
- // createElement(post.component, { isRssFeed: true })
- // )
-
- feed.addItem({
- author: [author],
- // content: html,
- contributor: [author],
- date: new Date(post.date),
- description: post.description,
- id: post.slug,
- link: url,
- title: post.title,
- })
-}
-
-await fs.mkdir("./public/rss", { recursive: true })
-await Promise.all([
- fs.writeFile("./public/rss/feed.xml", feed.rss2(), "utf8"),
- fs.writeFile("./public/rss/feed.json", feed.json1(), "utf8"),
-])
diff --git a/tailwind.config.cjs b/tailwind.config.cjs
index b30b53ae..e4bcb879 100644
--- a/tailwind.config.cjs
+++ b/tailwind.config.cjs
@@ -1,4 +1,6 @@
/* eslint-disable sort/object-properties */
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const plugin = require("tailwindcss/plugin")
/** @type {import("tailwindcss").Config} */
module.exports = {
@@ -12,6 +14,17 @@ module.exports = {
plugins: [
require("@tailwindcss/typography"),
require("@tailwindcss/container-queries"),
+ plugin(({ matchUtilities, theme }) => {
+ matchUtilities(
+ {
+ s: (value) => ({
+ height: value,
+ width: value,
+ }),
+ },
+ { values: theme("width") },
+ )
+ }),
],
theme: {
extend: {
@@ -20,6 +33,8 @@ module.exports = {
},
animation: {
"draw-stroke": "300ms ease-in-out 700ms forwards draw-stroke",
+ heartbeat:
+ "1s ease-in-out 0s infinite alternate none running heartbeat",
},
keyframes: {
"draw-stroke": {
@@ -39,6 +54,13 @@ module.exports = {
strokeDasharray: "90, 150",
},
},
+ halfspin: {
+ to: { transform: "rotate(180deg)" },
+ },
+ heartbeat: {
+ from: { transform: "scale(1)" },
+ to: { transform: "scale(1.1)" },
+ },
show: {
from: { opacity: 0 },
to: { opacity: 1 },
@@ -46,6 +68,7 @@ module.exports = {
},
},
fontSize: {
+ "2xs": ["0.75rem", { lineHeight: "1rem" }],
xs: ["0.8125rem", { lineHeight: "1.5rem" }],
sm: ["0.875rem", { lineHeight: "1.5rem" }],
base: ["1rem", { lineHeight: "1.75rem" }],
@@ -84,8 +107,6 @@ module.exports = {
"--tw-prose-captions": theme("colors.zinc.500"),
"--tw-prose-code": theme("colors.zinc.300"),
"--tw-prose-code-bg": theme("colors.zinc.200 / 0.05"),
- "--tw-prose-pre-code": theme("colors.zinc.100"),
- "--tw-prose-pre-bg": theme("colors.zinc.950"),
"--tw-prose-kbd": theme("colors.zinc.300"),
"--tw-prose-kbd-bg": theme("colors.zinc.800"),
"--tw-prose-kbd-borders": theme("colors.zinc.700"),
@@ -126,6 +147,7 @@ module.exports = {
marginBottom: theme("spacing.10"),
},
p: {
+ ...transition,
marginTop: theme("spacing.7"),
marginBottom: theme("spacing.7"),
},
@@ -201,7 +223,7 @@ module.exports = {
borderRadius: theme("borderRadius.lg"),
paddingInline: theme("spacing.2"),
},
- ":is(p, li) > code": {
+ ":is(p, li, aside) code": {
fontSize: theme("fontSize.xs")[0],
lineHeight: theme("lineHeight.6"),
},
@@ -278,36 +300,50 @@ module.exports = {
},
// Code blocks
- pre: {
- color: "var(--tw-prose-pre-code)",
- fontSize: theme("fontSize.sm")[0],
- fontWeight: theme("fontWeight.medium"),
- backgroundColor: "var(--tw-prose-pre-bg) !important",
- borderRadius: theme("borderRadius.xl"),
+ ".code-block": {
+ position: "relative",
},
- "pre code": {
- display: "grid",
- fontSize: "inherit",
- padding: theme("spacing.8"),
- overflowX: "auto",
- fontWeight: "inherit",
- backgroundColor: "transparent",
- borderRadius: 0,
+ ".code-block.has-title": {
+ paddingTop: theme("spacing.12"),
},
- ".has-title pre": {
- borderRadius: "0 0 1rem 1rem",
+ ".code-block:not(.demo)": {
+ marginInline: 0,
+ "@screen sm": {
+ marginInline: `calc(${theme("spacing.8")} * -1)`,
+ },
},
- "pre code .line.highlight": {
- backgroundColor: "var(--tw-prose-hl-bg)",
- borderColor: "var(--tw-prose-hl-border)",
+ ":is(.has-title, .demo) :is(pre, code)": {
+ borderRadius: `0 0 ${theme("borderRadius.xl")} ${theme(
+ "borderRadius.xl",
+ )}`,
+ },
+ ".code-block:not(:is(.has-title, .demo)) :is(pre, code)": {
+ borderRadius: theme("borderRadius.xl"),
+ },
+ "pre code .line": {
+ ...transition,
+ transitionDuration: theme("transitionDuration.300"),
+ transitionProperty:
+ "height, border-color, background-color, clip-path",
+ borderColor: "transparent",
borderLeftWidth: theme("borderWidth.4"),
+ clipPath: "inset(0 0 0 0)",
display: "inline-block",
+ height: theme("spacing.7"),
marginInline: `calc(${theme("spacing.8")} * -1)`,
- paddingRight: theme("spacing.8"),
paddingLeft: `calc(${theme("spacing.8")} - ${theme(
"borderWidth.4",
)})`,
+ paddingRight: theme("spacing.8"),
+ },
+ "pre:not(.collapsed) code .line:is(.highlight, .focus)": {
width: `calc(100% + ${theme("spacing.16")})`,
+ backgroundColor: "var(--tw-prose-hl-bg)",
+ borderColor: "var(--tw-prose-hl-border)",
+ },
+ "pre.collapsed code .line:not(.focus)": {
+ clipPath: "inset(100% 0 0 0)",
+ height: 0,
},
// Horizontal rules