diff --git a/src/components/Codeblock/CodeblockClient.tsx b/src/components/Codeblock/CodeblockClient.tsx index f769ab5cc00..3c26263ba32 100644 --- a/src/components/Codeblock/CodeblockClient.tsx +++ b/src/components/Codeblock/CodeblockClient.tsx @@ -1,11 +1,9 @@ "use client" import { useState } from "react" -import { Clipboard, ClipboardCheck } from "lucide-react" +import { Check, ChevronDown, ChevronUp, Copy } from "lucide-react" import CopyToClipboard from "@/components/CopyToClipboard" -import { Button } from "@/components/ui/buttons/Button" -import { Flex } from "@/components/ui/flex" import { cn } from "@/lib/utils/cn" @@ -16,6 +14,7 @@ import { useTranslation } from "@/hooks/useTranslation" type CodeblockClientProps = { html: string codeText: string + languageLabel: string allowCollapse: boolean shouldShowLineNumbers: boolean totalLines: number @@ -26,6 +25,7 @@ type CodeblockClientProps = { const CodeblockClient = ({ html, codeText, + languageLabel, allowCollapse, shouldShowLineNumbers, totalLines, @@ -33,63 +33,90 @@ const CodeblockClient = ({ className, }: CodeblockClientProps) => { const { t } = useTranslation("common") - const [isCollapsed, setIsCollapsed] = useState(allowCollapse) - const isCollapsable = totalLines - 1 > LINES_BEFORE_COLLAPSABLE - const showButtons = !fromHomepage + const codeLineCount = Math.max(totalLines - 1, 1) + const isCollapsable = + !fromHomepage && allowCollapse && codeLineCount > LINES_BEFORE_COLLAPSABLE + const [isCollapsed, setIsCollapsed] = useState(isCollapsable) + + const showLanguageLabel = !fromHomepage && languageLabel.length > 0 + const showCopy = !fromHomepage + const showCornerCollapse = isCollapsable && !isCollapsed + const hasCornerUi = showLanguageLabel || showCopy || showCornerCollapse return ( - /* Overwrites codeblocks inheriting RTL styling in Right-To-Left script languages (e.g., Arabic) */ - /* Context: https://github.com/ethereum/ethereum-org-website/issues/6202 */ -
+ /* Force LTR — codeblocks shouldn't inherit RTL from Arabic/Urdu pages. + Context: https://github.com/ethereum/ethereum-org-website/issues/6202 */ +
- {showButtons && ( - - {allowCollapse && isCollapsable && ( - + + )} + {showLanguageLabel && ( + + )} +
+ )} + + {isCollapsable && isCollapsed && ( + )}
) diff --git a/src/components/Codeblock/index.tsx b/src/components/Codeblock/index.tsx index 5a4c83c2a28..04b38dc8f0e 100644 --- a/src/components/Codeblock/index.tsx +++ b/src/components/Codeblock/index.tsx @@ -23,6 +23,29 @@ export type CodeblockProps = React.HTMLAttributes & { fromHomepage?: boolean } +const LANGUAGE_LABELS: Record = { + js: "JS", + javascript: "JS", + ts: "TS", + typescript: "TS", + jsx: "JSX", + tsx: "TSX", + json: "JSON", + python: "Python", + py: "Python", + solidity: "Solidity", + sol: "Solidity", + bash: "Shell", + sh: "Shell", + shell: "Shell", + yaml: "YAML", + yml: "YAML", + html: "HTML", + css: "CSS", + rust: "Rust", + go: "Go", +} + const Codeblock = async ({ children, allowCollapse = true, @@ -52,11 +75,13 @@ const Codeblock = async ({ const shouldShowLineNumbers = resolvedLang !== "bash" const totalLines = codeText.split("\n").length + const languageLabel = LANGUAGE_LABELS[language] ?? "" return ( (server-rendered via - shiki with dual themes: one-light + one-dark-pro). Shiki emits CSS - variables (--shiki-light / --shiki-dark) — we wire them up to `color` - so the theme switch works without re-highlighting on the client. */ + Drives the highlighted output from . Shiki emits CSS variables + (--shiki-light / --shiki-dark) so the theme switch happens without + re-highlighting on the client. */ .codeblock-shiki .shiki, .codeblock-shiki .shiki span { @@ -16,20 +15,35 @@ background-color: transparent; } +.codeblock-shiki .shiki, +.codeblock-shiki .shiki code { + font-size: 0.8125rem; + line-height: 1.55; +} + .codeblock-shiki .shiki { margin: 0; - padding: 1.5rem 1rem; + padding: 0.875rem 1rem; width: fit-content; min-width: 100%; overflow: visible; + tab-size: 2; +} + +/* When corner UI is overlaid, give the first line headroom so it never sits + underneath the language watermark / copy button. */ +.codeblock-shiki.has-corner .shiki { + padding-top: 1.75rem; } -.codeblock-shiki.has-top-bar .shiki { - padding-top: 2.75rem; +/* Trim shiki's trailing empty line. */ +.codeblock-shiki .shiki .line:last-child:has(> span:empty:only-child), +.codeblock-shiki .shiki .line:last-child:empty { + display: none; } -/* Line numbers via CSS counter — keeps shiki's output - intact and avoids server-side post-processing. */ +/* Line numbers via CSS counter. Real disabled-color column with a hairline + inner separator — readable for keyboard users, still recedes visually. */ .codeblock-shiki.line-numbers .shiki code { counter-reset: shiki-line; } @@ -38,9 +52,64 @@ counter-increment: shiki-line; content: counter(shiki-line); display: inline-block; - width: 2rem; - margin-inline-end: 2rem; + width: 1.5rem; + padding-inline-end: 0.75rem; + margin-inline-end: 1rem; + border-inline-end: 1px solid hsla(var(--border-low-contrast)); text-align: end; - opacity: 0.4; + color: hsla(var(--disabled)); user-select: none; + font-variant-numeric: tabular-nums; +} + +/* Collapsed surface: cap the code surface and flatten its bottom corners so + the expander row can sit flush beneath it. */ +.codeblock-shiki.is-collapsed > [data-code-surface] { + max-height: calc((1.5rem * 8) + 1.75rem); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +/* Fade overlay riding just above the expander row: dissolves the cut line of + code into the surface tint instead of hard-clipping it. */ +.codeblock-shiki.is-collapsed::before { + content: ""; + position: absolute; + inset-inline: 0; + bottom: 1.875rem; + height: 2.75rem; + background: linear-gradient( + to bottom, + transparent, + hsla(var(--background-highlight)) + ); + pointer-events: none; + z-index: 1; +} + +/* Bottom inline expander — replaces the corner "Show all" button. Shares the + code surface tint so it reads as one continuous region. */ +.codeblock-shiki .codeblock-expander { + display: flex; + align-items: center; + justify-content: center; + gap: 0.375rem; + width: 100%; + padding-block: 0.5rem 0.625rem; + font-size: 0.75rem; + line-height: 1; + color: hsla(var(--body-medium)); + background: hsla(var(--background-highlight)); + border-radius: 0 0 0.375rem 0.375rem; + cursor: pointer; + transition: color 120ms ease-out; +} + +.codeblock-shiki .codeblock-expander:hover { + color: hsla(var(--primary)); +} + +.codeblock-shiki .codeblock-expander:focus-visible { + outline: 2px solid hsla(var(--primary)); + outline-offset: 2px; }