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: 14 additions & 0 deletions audit/divergences/data_table.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,17 @@
shadcn: "dashboard-01 block: Select onValueChange calls TanStack Table's setPageSize on the client"
reason: "RubyUI's DataTable is server-driven, as for pagination: the page size is a query param the server reads, so the list comes back from the server and the URL can be shared. The Select itself (open, arrows, typeahead, Enter, Esc) behaves as shadcn's."
since: "2026-11-05"
- component: data_table
dimension: Look
match: {part: DataTableRowCheckbox, property: display, rubyui: "block", shadcn: "flex"}
rubyui: "row checkbox is RubyUI::Checkbox, a native <input type=checkbox> with no children; as a flex item of the cell's div.flex it computes display: block"
shadcn: "Base UI Checkbox is a <span class=\"flex\"> that centers its Indicator child"
reason: "flex only lays out children, and the native input has none (its check mark is an absolutely positioned ::before), so block and flex render the same box. Adding flex to the input would change nothing on screen."
since: "2026-11-06"
- component: data_table
dimension: Look
match: {part: DataTableRowCheckbox, property: text-align, rubyui: "start", shadcn: "left"}
rubyui: "select-all checkbox (header cell, text-left) is a native input: the browser's form-control defaults reset text-align to start instead of inheriting left"
shadcn: "the Base UI <span> inherits text-align: left from the header cell (TableHead text-left); row checkboxes in body cells are start on both sides"
reason: "In a left-to-right document start resolves to left, and the input has no text to align either way."
since: "2026-11-06"
31 changes: 29 additions & 2 deletions audit/fixtures/data_table/rubyui.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,15 @@ def view_template
TableRow do
TableCell(class: "w-8") { div(class: "flex items-center justify-center") { DataTableRowCheckbox(value: id, label: "Select row") } }
TableCell { header }
TableCell(data: {column: "type"}) { Badge(variant: :outline, size: :sm) { type } }
TableCell(data: {column: "status"}) { Badge(variant: :outline, size: :sm) { status } }
TableCell(data: {column: "type"}) do
div(class: "w-32") { Badge(variant: :outline, class: "px-1.5 text-muted-foreground") { type } }
end
TableCell(data: {column: "status"}) do
Badge(variant: :outline, class: "px-1.5 text-muted-foreground") do
(status == "Done") ? circle_check_icon : loader_icon
plain status
end
end
TableCell(data: {column: "reviewer"}) { reviewer }
end
end
Expand All @@ -64,5 +71,25 @@ def view_template
end
end
end

private

# lucide circle-check, as the block's status cell for "Done"
def circle_check_icon
svg(xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor",
stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", aria_hidden: "true",
class: "fill-green-500 dark:fill-green-400") do |s|
s.circle(cx: "12", cy: "12", r: "10")
s.path(d: "m16 9-5.5 5.5L8 12")
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved
end
end

# lucide loader, as the block's status cell otherwise
def loader_icon
svg(xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor",
stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round", aria_hidden: "true") do |s|
["M12 2v4", "m16.2 7.8 2.9-2.9", "M18 12h4", "m16.2 16.2 2.9 2.9", "M12 18v4", "m4.9 19.1 2.9-2.9", "M2 12h4", "m4.9 4.9 2.9 2.9"].each { |d| s.path(d:) }
end
end
end
end
10 changes: 9 additions & 1 deletion audit/fixtures/data_table/shadcn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
ChevronRightIcon,
ChevronsLeftIcon,
ChevronsRightIcon,
CircleCheckIcon,
Columns3Icon,
LoaderIcon,
} from "lucide-react"
import { useState } from "react"

Expand Down Expand Up @@ -35,7 +37,8 @@ import {
TableRow,
} from "@/components/ui/table"

// JSX and classes from bases/base/blocks/dashboard-01/components/data-table.tsx,
// JSX and classes from bases/base/blocks/dashboard-01/components/data-table.tsx
// (status icons: the lucide picks of its IconPlaceholder),
// without drag-and-drop, tabs, the drawer and the inline target/limit inputs.
const rows = [
{ id: 1, header: "Cover page", type: "Cover page", status: "In Process", reviewer: "Eddie Lake" },
Expand Down Expand Up @@ -125,6 +128,11 @@ export default function DataTableFixture() {
{visible.Status && (
<TableCell>
<Badge variant="outline" className="px-1.5 text-muted-foreground">
{row.status === "Done" ? (
<CircleCheckIcon className="fill-green-500 dark:fill-green-400" />
) : (
<LoaderIcon />
)}
{row.status}
</Badge>
</TableCell>
Expand Down
6 changes: 4 additions & 2 deletions gem/lib/ruby_ui/data_table/data_table_column_toggle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ def initialize(columns:, label: "Columns", **attrs)

def view_template
div(**attrs) do
render RubyUI::DropdownMenu.new do
# Both wrappers are display: contents so the button is a flex item of
# the toolbar, as shadcn's trigger (inline-flex blockifies to flex).
render RubyUI::DropdownMenu.new(class: "contents") do
render RubyUI::DropdownMenuTrigger.new do
render RubyUI::Button.new(variant: :outline, size: :sm) do
columns_icon
Expand Down Expand Up @@ -55,7 +57,7 @@ def chevron_down_icon

def default_attrs
{
class: "relative",
class: "contents",
data: {
controller: "ruby-ui--data-table-column-visibility",
action: "ruby-ui--dropdown-menu:change->ruby-ui--data-table-column-visibility#toggle"
Expand Down
5 changes: 4 additions & 1 deletion gem/lib/ruby_ui/data_table/data_table_toolbar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ def view_template(&)
private

def default_attrs
{class: "flex items-center justify-between gap-2"}
# mb-6 overrides DataTable's space-y-4 (zero specificity) below the
# toolbar: the dashboard-01 block puts 24px (gap-6) between the toolbar
# and the table, 16px between the table and the pagination.
{class: "mb-6 flex items-center justify-between gap-2"}
end
end
end
6 changes: 6 additions & 0 deletions gem/test/ruby_ui/data_table_column_toggle_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,10 @@ def test_trigger_icons_carry_data_icon
assert_match(/<svg[^>]*data-icon="inline-start"/, output)
assert_match(/<svg[^>]*data-icon="inline-end"/, output)
end

def test_wrappers_stay_out_of_layout
output = phlex { RubyUI.DataTableColumnToggle(columns: [{key: :email, label: "Email"}]) }
assert_match(/<div class="contents" data-controller="ruby-ui--data-table-column-visibility"/, output)
assert_match(/<div[^>]*class="contents"[^>]*data-slot="dropdown-menu"|<div[^>]*data-slot="dropdown-menu"[^>]*class="contents"/, output)
end
end
5 changes: 5 additions & 0 deletions gem/test/ruby_ui/data_table_toolbar_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ def test_renders_div_with_flex_layout_and_children
assert_match(/<div[^>]*class="[^"]*flex[^"]*"/, output)
assert_match(/INNER/, output)
end

def test_leaves_24px_before_the_table
output = phlex { RubyUI.DataTableToolbar { "INNER" } }
assert_match(/<div[^>]*class="[^"]*\bmb-6\b[^"]*"/, output)
end
end
4 changes: 2 additions & 2 deletions mcp/data/registry/components/data_table.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
{
"path": "data_table_column_toggle.rb",
"content": "# frozen_string_literal: true\n\nmodule RubyUI\n class DataTableColumnToggle < Base\n def initialize(columns:, label: \"Columns\", **attrs)\n @columns = columns\n @label = label\n super(**attrs)\n end\n\n def view_template\n div(**attrs) do\n render RubyUI::DropdownMenu.new do\n render RubyUI::DropdownMenuTrigger.new do\n render RubyUI::Button.new(variant: :outline, size: :sm) do\n columns_icon\n plain @label\n chevron_down_icon\n end\n end\n render RubyUI::DropdownMenuContent.new(align: :end, class: \"w-32\") do\n @columns.each do |col|\n render RubyUI::DropdownMenuCheckboxItem.new(\n checked: col.fetch(:visible, true),\n class: \"capitalize\",\n data: {column_key: col[:key].to_s}\n ) { plain col[:label] }\n end\n end\n end\n end\n end\n\n private\n\n # lucide columns-3, as shadcn's trigger (data-icon tightens the button's padding)\n def columns_icon\n svg(xmlns: \"http://www.w3.org/2000/svg\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\",\n stroke_width: \"2\", stroke_linecap: \"round\", stroke_linejoin: \"round\", aria_hidden: \"true\",\n data: {icon: \"inline-start\"}) do |s|\n s.rect(width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\")\n s.path(d: \"M9 3v18\")\n s.path(d: \"M15 3v18\")\n end\n end\n\n # lucide chevron-down\n def chevron_down_icon\n svg(xmlns: \"http://www.w3.org/2000/svg\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\",\n stroke_width: \"2\", stroke_linecap: \"round\", stroke_linejoin: \"round\", aria_hidden: \"true\",\n data: {icon: \"inline-end\"}) do |s|\n s.path(d: \"m6 9 6 6 6-6\")\n end\n end\n\n def default_attrs\n {\n class: \"relative\",\n data: {\n controller: \"ruby-ui--data-table-column-visibility\",\n action: \"ruby-ui--dropdown-menu:change->ruby-ui--data-table-column-visibility#toggle\"\n }\n }\n end\n end\nend\n"
"content": "# frozen_string_literal: true\n\nmodule RubyUI\n class DataTableColumnToggle < Base\n def initialize(columns:, label: \"Columns\", **attrs)\n @columns = columns\n @label = label\n super(**attrs)\n end\n\n def view_template\n div(**attrs) do\n # Both wrappers are display: contents so the button is a flex item of\n # the toolbar, as shadcn's trigger (inline-flex blockifies to flex).\n render RubyUI::DropdownMenu.new(class: \"contents\") do\n render RubyUI::DropdownMenuTrigger.new do\n render RubyUI::Button.new(variant: :outline, size: :sm) do\n columns_icon\n plain @label\n chevron_down_icon\n end\n end\n render RubyUI::DropdownMenuContent.new(align: :end, class: \"w-32\") do\n @columns.each do |col|\n render RubyUI::DropdownMenuCheckboxItem.new(\n checked: col.fetch(:visible, true),\n class: \"capitalize\",\n data: {column_key: col[:key].to_s}\n ) { plain col[:label] }\n end\n end\n end\n end\n end\n\n private\n\n # lucide columns-3, as shadcn's trigger (data-icon tightens the button's padding)\n def columns_icon\n svg(xmlns: \"http://www.w3.org/2000/svg\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\",\n stroke_width: \"2\", stroke_linecap: \"round\", stroke_linejoin: \"round\", aria_hidden: \"true\",\n data: {icon: \"inline-start\"}) do |s|\n s.rect(width: \"18\", height: \"18\", x: \"3\", y: \"3\", rx: \"2\")\n s.path(d: \"M9 3v18\")\n s.path(d: \"M15 3v18\")\n end\n end\n\n # lucide chevron-down\n def chevron_down_icon\n svg(xmlns: \"http://www.w3.org/2000/svg\", viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\",\n stroke_width: \"2\", stroke_linecap: \"round\", stroke_linejoin: \"round\", aria_hidden: \"true\",\n data: {icon: \"inline-end\"}) do |s|\n s.path(d: \"m6 9 6 6 6-6\")\n end\n end\n\n def default_attrs\n {\n class: \"contents\",\n data: {\n controller: \"ruby-ui--data-table-column-visibility\",\n action: \"ruby-ui--dropdown-menu:change->ruby-ui--data-table-column-visibility#toggle\"\n }\n }\n end\n end\nend\n"
},
{
"path": "data_table_column_visibility_controller.js",
Expand Down Expand Up @@ -84,7 +84,7 @@
},
{
"path": "data_table_toolbar.rb",
"content": "# frozen_string_literal: true\n\nmodule RubyUI\n class DataTableToolbar < Base\n def view_template(&)\n div(**attrs, &)\n end\n\n private\n\n def default_attrs\n {class: \"flex items-center justify-between gap-2\"}\n end\n end\nend\n"
"content": "# frozen_string_literal: true\n\nmodule RubyUI\n class DataTableToolbar < Base\n def view_template(&)\n div(**attrs, &)\n end\n\n private\n\n def default_attrs\n # mb-6 overrides DataTable's space-y-4 (zero specificity) below the\n # toolbar: the dashboard-01 block puts 24px (gap-6) between the toolbar\n # and the table, 16px between the table and the pagination.\n {class: \"mb-6 flex items-center justify-between gap-2\"}\n end\n end\nend\n"
}
],
"dependencies": {
Expand Down
Loading