Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 10 additions & 4 deletions audit/component_map/codeblock.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
shadcn: code-block
shadcn_sha: 4aaea19d9cff3e0908e12a8c1d504b8c5ec34610
shadcn_sha: 8a3536678a528cc012b9e62484290e79d0be410f
kind: static
docs_view: codeblock
parts:
Expand All @@ -14,18 +14,24 @@ parts:
CodeblockTheme: null
selectors:
Codeblock: '[data-audit-fixture] [data-slot="code-block"]'
CodeblockHeader: '[data-audit-fixture] [data-slot="code-block-header"]'
CodeblockTitle: '[data-audit-fixture] [data-slot="code-block-title"]'
CodeblockActions: '[data-audit-fixture] [data-slot="code-block-actions"]'
CodeblockCopyButton: '[data-audit-fixture] [data-slot="code-block-copy-button"]'
CodeblockContent: '[data-audit-fixture] [data-slot="code-block-content"]'
CodeblockLine: '[data-audit-fixture] [data-slot="code-block-line"]'
CodeblockFooter: '[data-audit-fixture] [data-slot="code-block-footer"]'
props:
# Codeblock(code, ...) is a shorthand that also takes CodeblockContent's props.
Codeblock: {variant: variant, size: size}
CodeblockContent: {wrap: wrap, lineNumbers: line_numbers, startLine: start_line}
CodeblockCopyButton: {value: value, variant: variant, size: size}
CodeblockContent: {style: style, wrap: wrap, lineNumbers: line_numbers, startLine: start_line}
# onClick: React event prop; in RubyUI a data-action passes through attrs.
CodeblockCopyButton: {value: value, variant: variant, size: size, onClick: null}
CodeblockLine: {variant: variant}
rubyui_only:
# syntax: Rouge highlighting (shadcn's base ships no highlighter).
# clipboard*: the shorthand's copy button on/off and its labels.
# clipboard*: the shorthand's copy button on/off and its labels (clipboard_error: deprecated, ignored).
Codeblock: [syntax, clipboard, clipboard_success, clipboard_error, wrap, line_numbers, start_line]
CodeblockContent: [syntax]
# error_label: deprecated, ignored (a failed copy shows nothing, as in shadcn).
CodeblockCopyButton: [label, copied_label, error_label]
6 changes: 6 additions & 0 deletions audit/divergences/codeblock.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- component: codeblock
dimension: Props
rubyui: "no onClick on CodeblockCopyButton; a data-action passed through attrs runs alongside the copy"
shadcn: "onClick on CodeBlockCopyButton, called before the copy"
reason: "Server-rendered component: there is no React event prop. Extra behavior hooks in through Stimulus with data: {action: \"click->my-controller#method\"}, merged with the button's own copy action."
since: "2026-11-05"
19 changes: 14 additions & 5 deletions audit/fixtures/codeblock/content.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
# One code block with a copy button, as shadcn's code-block example without
# header/footer. Plain text on both sides (shadcn's base has no highlighter;
# RubyUI uses Rouge's plaintext lexer) so syntax colors stay out of Look.
# The root carries data-audit-trigger (static kind: only the closed state is
# collected; the copy flow is driven by hand).
# Three code blocks, the same on both sides:
# 1. Floating copy button (CodeBlockActions as a direct child), no header/footer.
# 2. Header with a title and the copy button, then a footer.
# 3. Numbered lines with the default, added, removed and highlighted variants.
# Plain text everywhere (shadcn's base has no highlighter; RubyUI uses Rouge's
# plaintext lexer) so syntax colors stay out of Look. The first root carries
# data-audit-trigger (static kind: only the closed state is collected; hover
# and the copy flow are driven by hand).
lines:
- "$ pnpm add ai zod"
- "dependencies:"
- "+ ai 6.0.12"
- "+ zod 4.1.12"
- "Done in 2.8s"
title: "install.sh"
footer: "Runs in the project root."
diff:
- "const answer = 41"
- "const answer = 42"
- "console.log(answer)"
21 changes: 19 additions & 2 deletions audit/fixtures/codeblock/rubyui.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# frozen_string_literal: true

module ShadcnAuditFixtures
# RubyUI side of audit/fixtures/codeblock (strings in content.yml).
# RubyUI side of audit/fixtures/codeblock (strings in content.yml): a
# floating copy button, a header + footer, and numbered diff lines.
class Codeblock < Phlex::HTML
include RubyUI

Expand All @@ -14,8 +15,24 @@ class Codeblock < Phlex::HTML
CODE

def view_template
div(class: "w-[32rem] p-6") do
div(class: "flex w-[32rem] flex-col gap-4 p-6") do
Codeblock(CODE, syntax: :plaintext, data: {audit_trigger: true})
Codeblock do
CodeblockHeader do
CodeblockTitle { "install.sh" }
CodeblockActions { CodeblockCopyButton(value: CODE) }
end
CodeblockContent(CODE, syntax: :plaintext)
CodeblockFooter { "Runs in the project root." }
end
Codeblock do
CodeblockContent(line_numbers: true) do
CodeblockLine(variant: :removed) { "const answer = 41" }
CodeblockLine(variant: :added) { "const answer = 42" }
CodeblockLine()
CodeblockLine(variant: :highlighted) { "console.log(answer)" }
end
end
end
end
end
Expand Down
28 changes: 25 additions & 3 deletions audit/fixtures/codeblock/shadcn.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
// One code block with a copy button (CodeBlockActions as a direct child, so
// it floats top-right like RubyUI's), no header/footer. Plain text, no highlighter.
// Three code blocks (strings in content.yml): a floating copy button, a
// header + footer, and numbered diff lines. Plain text, no highlighter.
import {
CodeBlock,
CodeBlockActions,
CodeBlockContent,
CodeBlockCopyButton,
CodeBlockFooter,
CodeBlockHeader,
CodeBlockLine,
CodeBlockTitle,
} from "@/components/ui/code-block"

const code = `$ pnpm add ai zod
Expand All @@ -16,13 +20,31 @@ Done in 2.8s

export default function CodeblockFixture() {
return (
<div className="w-[32rem] p-6">
<div className="flex w-[32rem] flex-col gap-4 p-6">
<CodeBlock data-audit-trigger="">
<CodeBlockActions>
<CodeBlockCopyButton value={code} />
</CodeBlockActions>
<CodeBlockContent>{code}</CodeBlockContent>
</CodeBlock>
<CodeBlock>
<CodeBlockHeader>
<CodeBlockTitle>install.sh</CodeBlockTitle>
<CodeBlockActions>
<CodeBlockCopyButton value={code} />
</CodeBlockActions>
</CodeBlockHeader>
<CodeBlockContent>{code}</CodeBlockContent>
<CodeBlockFooter>Runs in the project root.</CodeBlockFooter>
</CodeBlock>
<CodeBlock>
<CodeBlockContent lineNumbers>
<CodeBlockLine variant="removed">const answer = 41</CodeBlockLine>
<CodeBlockLine variant="added">const answer = 42</CodeBlockLine>
<CodeBlockLine />
<CodeBlockLine variant="highlighted">console.log(answer)</CodeBlockLine>
</CodeBlockContent>
</CodeBlock>
</div>
)
}
93 changes: 93 additions & 0 deletions docs/app/assets/stylesheets/application.tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,99 @@
}
}

/* Scroll fade (Codeblock): scroll-fade-x fades the start/end edges while there
is content to that side; scroll-fade-y the top/bottom edges. From shadcn's
scroll-fade-x / scroll-fade-y (MIT). */
@property --scroll-fade-t {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}

@property --scroll-fade-s {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}

@property --scroll-fade-e {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}

@keyframes scroll-fade-reveal-t {
from {
--scroll-fade-t: 0px;
}
to {
--scroll-fade-t: var(--_scroll-fade-size-t);
}
}

@keyframes scroll-fade-reveal-s {
from {
--scroll-fade-s: 0px;
}
to {
--scroll-fade-s: var(--_scroll-fade-size-s);
}
}

@keyframes scroll-fade-reveal-e {
from {
--scroll-fade-e: var(--_scroll-fade-size-e);
}
to {
--scroll-fade-e: 0px;
}
}

@utility scroll-fade-x {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
--_scroll-fade-size-s: var(--scroll-fade-s-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--_scroll-fade-size-e: var(--scroll-fade-e-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--scroll-fade-inline: linear-gradient(to right, transparent 0, #000 var(--scroll-fade-s, 0px), #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
&:where([dir="rtl"], [dir="rtl"] *) {
--scroll-fade-inline: linear-gradient(to left, transparent 0, #000 var(--scroll-fade-s, 0px), #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
}
mask-image: var(--scroll-fade-inline);
mask-composite: intersect;
mask-repeat: no-repeat;

@supports (animation-timeline: scroll()) {
animation: scroll-fade-reveal-s 1ms ease-in-out, scroll-fade-reveal-e 1ms ease-in-out;
animation-timeline: scroll(self inline), scroll(self inline);
animation-range: 0 calc(var(--spacing) * 24), calc(100% - calc(var(--spacing) * 24)) 100%;
animation-fill-mode: both;
}

@supports not (animation-timeline: scroll()) {
--scroll-fade-s: var(--_scroll-fade-size-s);
--scroll-fade-e: var(--_scroll-fade-size-e);
}
}

@utility scroll-fade-y {
--_scroll-fade-size-t: var(--scroll-fade-t-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--_scroll-fade-size-b: var(--scroll-fade-b-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--scroll-fade-block: linear-gradient(to bottom, transparent 0, #000 var(--scroll-fade-t, 0px), #000 calc(100% - var(--scroll-fade-b, 0px)), transparent 100%);
mask-image: var(--scroll-fade-block);
mask-composite: intersect;
mask-repeat: no-repeat;

@supports (animation-timeline: scroll()) {
animation: scroll-fade-reveal-t 1ms ease-in-out, scroll-fade-reveal-b 1ms ease-in-out;
animation-timeline: scroll(self y), scroll(self y);
animation-range: 0 calc(var(--spacing) * 24), calc(100% - calc(var(--spacing) * 24)) 100%;
animation-fill-mode: both;
}

@supports not (animation-timeline: scroll()) {
--scroll-fade-t: var(--_scroll-fade-size-t);
--scroll-fade-b: var(--_scroll-fade-size-b);
}
}

@layer base {
* {
@apply border-border outline-ring/50;
Expand Down
92 changes: 92 additions & 0 deletions docs/app/views/docs/installation/rails_bundler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,98 @@ def default_attrs
}


/* Scroll fade (Codeblock): scroll-fade-x fades the start/end edges while there
is content to that side; scroll-fade-y the top/bottom edges. From shadcn's
scroll-fade-x / scroll-fade-y (MIT). */
@property --scroll-fade-t {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}

@property --scroll-fade-s {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}

@property --scroll-fade-e {
syntax: "<length-percentage>";
inherits: false;
initial-value: 0px;
}

@keyframes scroll-fade-reveal-t {
from {
--scroll-fade-t: 0px;
}
to {
--scroll-fade-t: var(--_scroll-fade-size-t);
}
}

@keyframes scroll-fade-reveal-s {
from {
--scroll-fade-s: 0px;
}
to {
--scroll-fade-s: var(--_scroll-fade-size-s);
}
}

@keyframes scroll-fade-reveal-e {
from {
--scroll-fade-e: var(--_scroll-fade-size-e);
}
to {
--scroll-fade-e: 0px;
}
}

@utility scroll-fade-x {
--_scroll-fade-size-s: var(--scroll-fade-s-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--_scroll-fade-size-e: var(--scroll-fade-e-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--scroll-fade-inline: linear-gradient(to right, transparent 0, #000 var(--scroll-fade-s, 0px), #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
&:where([dir="rtl"], [dir="rtl"] *) {
--scroll-fade-inline: linear-gradient(to left, transparent 0, #000 var(--scroll-fade-s, 0px), #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
}
mask-image: var(--scroll-fade-inline);
mask-composite: intersect;
mask-repeat: no-repeat;

@supports (animation-timeline: scroll()) {
animation: scroll-fade-reveal-s 1ms ease-in-out, scroll-fade-reveal-e 1ms ease-in-out;
animation-timeline: scroll(self inline), scroll(self inline);
animation-range: 0 calc(var(--spacing) * 24), calc(100% - calc(var(--spacing) * 24)) 100%;
animation-fill-mode: both;
}

@supports not (animation-timeline: scroll()) {
--scroll-fade-s: var(--_scroll-fade-size-s);
--scroll-fade-e: var(--_scroll-fade-size-e);
}
}

@utility scroll-fade-y {
--_scroll-fade-size-t: var(--scroll-fade-t-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--_scroll-fade-size-b: var(--scroll-fade-b-size, var(--scroll-fade-size, min(12%, calc(var(--spacing) * 10))));
--scroll-fade-block: linear-gradient(to bottom, transparent 0, #000 var(--scroll-fade-t, 0px), #000 calc(100% - var(--scroll-fade-b, 0px)), transparent 100%);
mask-image: var(--scroll-fade-block);
mask-composite: intersect;
mask-repeat: no-repeat;

@supports (animation-timeline: scroll()) {
animation: scroll-fade-reveal-t 1ms ease-in-out, scroll-fade-reveal-b 1ms ease-in-out;
animation-timeline: scroll(self y), scroll(self y);
animation-range: 0 calc(var(--spacing) * 24), calc(100% - calc(var(--spacing) * 24)) 100%;
animation-fill-mode: both;
}

@supports not (animation-timeline: scroll()) {
--scroll-fade-t: var(--_scroll-fade-size-t);
--scroll-fade-b: var(--_scroll-fade-size-b);
}
}
@layer base {
* {
@apply border-border outline-ring/50;
Expand Down
Loading
Loading