Skip to content

Commit 01e1baf

Browse files
committed
Merge main into monorepo branch and relocate gem updates
2 parents 416117d + a3e5c84 commit 01e1baf

22 files changed

Lines changed: 619 additions & 3 deletions

gem/lib/generators/ruby_ui/dependencies.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ accordion:
22
js_packages:
33
- "motion"
44

5+
data_table:
6+
components:
7+
- "Table"
8+
- "Checkbox"
9+
- "NativeSelect"
10+
- "Pagination"
11+
- "DropdownMenu"
12+
- "Input"
13+
- "Button"
14+
515
alert_dialog:
616
components:
717
- "Button"

gem/lib/ruby_ui.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module RubyUI
4-
VERSION = "1.1.0"
4+
VERSION = "1.2.0"
55
end
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# frozen_string_literal: true
2+
3+
class Views::Docs::DataTable < Views::Base
4+
Row = Struct.new(:id, :name, :email, :salary, :status, keyword_init: true)
5+
6+
SAMPLE_ROWS = [
7+
Row.new(id: 1, name: "Alice", email: "alice@example.com", salary: 90_000, status: "Active"),
8+
Row.new(id: 2, name: "Bob", email: "bob@example.com", salary: 75_000, status: "Inactive"),
9+
Row.new(id: 3, name: "Carol", email: "carol@example.com", salary: 85_000, status: "Active")
10+
].freeze
11+
12+
def view_template
13+
div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
14+
component = "DataTable"
15+
render Docs::Header.new(
16+
title: component,
17+
description: "A Hotwire-first data table. Every interaction (sort, search, pagination) is a Rails request answered with HTML, swapped via Turbo Frame. Row selection uses form-first submission."
18+
)
19+
20+
Heading(level: 2) { "Usage" }
21+
22+
render Docs::VisualCodeExample.new(title: "Server-driven table", context: self) do
23+
@@code = <<~RUBY
24+
DataTable(id: "employees") do
25+
DataTableToolbar do
26+
DataTableSearch(path: employees_path, value: @search)
27+
DataTablePerPageSelect(path: employees_path, value: @per_page)
28+
end
29+
30+
div(class: "rounded-md border") do
31+
Table do
32+
TableHeader do
33+
TableRow do
34+
TableHead { "Name" }
35+
DataTableSortHead(column_key: :email, label: "Email",
36+
sort: @sort, direction: @direction,
37+
path: employees_path)
38+
TableHead(class: "text-right") { "Salary" }
39+
end
40+
end
41+
TableBody do
42+
@rows.each do |r|
43+
TableRow do
44+
TableCell { r.name }
45+
TableCell { r.email }
46+
TableCell(class: "text-right") { r.salary }
47+
end
48+
end
49+
end
50+
end
51+
end
52+
53+
DataTablePaginationBar do
54+
DataTableSelectionSummary(total_on_page: @rows.size)
55+
DataTablePagination(page: @page, per_page: @per_page,
56+
total_count: @total_count, path: employees_path)
57+
end
58+
end
59+
RUBY
60+
end
61+
62+
render Docs::VisualCodeExample.new(title: "Selection + bulk actions", context: self) do
63+
@@code = <<~RUBY
64+
FORM_ID = "employees_form"
65+
66+
DataTable(id: "employees_select") do
67+
DataTableToolbar do
68+
DataTableSearch(path: employees_path, value: @search)
69+
DataTableBulkActions do
70+
Button(type: "submit", form: FORM_ID,
71+
formaction: bulk_delete_employees_path,
72+
formmethod: "post",
73+
variant: :destructive, size: :sm) { "Delete" }
74+
end
75+
end
76+
77+
DataTableForm(id: FORM_ID, action: "") do
78+
div(class: "rounded-md border") do
79+
Table do
80+
TableHeader do
81+
TableRow do
82+
TableHead(class: "w-10") { DataTableSelectAllCheckbox() }
83+
TableHead { "Name" }
84+
TableHead { "Email" }
85+
end
86+
end
87+
TableBody do
88+
@rows.each do |r|
89+
TableRow do
90+
TableCell { DataTableRowCheckbox(value: r.id) }
91+
TableCell { r.name }
92+
TableCell { r.email }
93+
end
94+
end
95+
end
96+
end
97+
end
98+
end
99+
100+
DataTablePaginationBar do
101+
DataTableSelectionSummary(total_on_page: @rows.size)
102+
DataTablePagination(page: @page, per_page: @per_page,
103+
total_count: @total_count, path: employees_path)
104+
end
105+
end
106+
RUBY
107+
end
108+
109+
render Docs::VisualCodeExample.new(title: "Column visibility", context: self) do
110+
@@code = <<~RUBY
111+
DataTable(id: "employees_cols") do
112+
DataTableToolbar do
113+
DataTableColumnToggle(columns: [
114+
{key: :email, label: "Email"},
115+
{key: :salary, label: "Salary"}
116+
])
117+
end
118+
119+
Table do
120+
TableHeader do
121+
TableRow do
122+
TableHead { "Name" }
123+
TableHead(data: {column: "email"}) { "Email" }
124+
TableHead(data: {column: "salary"}) { "Salary" }
125+
end
126+
end
127+
TableBody do
128+
@rows.each do |r|
129+
TableRow do
130+
TableCell { r.name }
131+
TableCell(data: {column: "email"}) { r.email }
132+
TableCell(data: {column: "salary"}) { r.salary }
133+
end
134+
end
135+
end
136+
end
137+
end
138+
RUBY
139+
end
140+
141+
render Docs::VisualCodeExample.new(title: "Expandable rows", context: self) do
142+
@@code = <<~RUBY
143+
DataTable(id: "employees_expand") do
144+
Table do
145+
TableHeader do
146+
TableRow do
147+
TableHead(class: "w-10") { }
148+
TableHead { "Name" }
149+
TableHead { "Email" }
150+
end
151+
end
152+
TableBody do
153+
@rows.each do |r|
154+
detail_id = "row-\#{r.id}-detail"
155+
TableRow do
156+
TableCell { DataTableExpandToggle(controls: detail_id, label: "Toggle \#{r.name}") }
157+
TableCell { r.name }
158+
TableCell { r.email }
159+
end
160+
TableRow(id: detail_id, class: "hidden", role: "region") do
161+
TableCell(colspan: 3, class: "bg-muted/40") do
162+
div(class: "p-4") do
163+
p { "Salary: $\#{r.salary}" }
164+
p { "Status: \#{r.status}" }
165+
end
166+
end
167+
end
168+
end
169+
end
170+
end
171+
end
172+
RUBY
173+
end
174+
175+
render Components::ComponentSetup::Tabs.new(component_name: component)
176+
177+
render Docs::ComponentsTable.new(component_files(component))
178+
end
179+
end
180+
end

gem/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ruby_ui_js",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"main": "lib/ruby_ui/index.js",
55
"description": "Stimulus controllers for ruby_ui Component Library",
66
"homepage": "https://ruby_ui.dev",

gem/ruby_ui.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
1515

1616
s.required_ruby_version = ">= 3.2"
1717

18-
s.add_development_dependency "phlex", ">= 2.1.2"
18+
s.add_development_dependency "phlex", "~> 2.1", ">= 2.1.2"
1919
s.add_development_dependency "rouge", "~> 4.2.0"
2020
s.add_development_dependency "tailwind_merge", "~> 0.12"
2121
s.add_development_dependency "rake", "~> 13.0"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class RubyUI::DataTableBulkActionsTest < ComponentTest
6+
def test_starts_hidden_with_bulk_actions_target_and_renders_children
7+
output = phlex { RubyUI.DataTableBulkActions { "BUTTONS" } }
8+
assert_match(/class="[^"]*hidden[^"]*"/, output)
9+
assert_match(/data-ruby-ui--data-table-target="bulkActions"/, output)
10+
assert_match(/BUTTONS/, output)
11+
end
12+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class RubyUI::DataTableColumnToggleTest < ComponentTest
6+
def test_renders_dropdown_with_checkbox_per_column
7+
output = phlex do
8+
RubyUI.DataTableColumnToggle(columns: [
9+
{key: :email, label: "Email"},
10+
{key: :salary, label: "Salary"}
11+
])
12+
end
13+
assert_match(/Columns/, output)
14+
assert_match(/data-controller="[^"]*ruby-ui--data-table-column-visibility/, output)
15+
assert_match(/data-column-key="email"/, output)
16+
assert_match(/data-column-key="salary"/, output)
17+
assert_match(/Email/, output)
18+
assert_match(/Salary/, output)
19+
end
20+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class RubyUI::DataTableExpandToggleTest < ComponentTest
6+
def test_renders_button_with_aria_attributes_and_delegated_action
7+
output = phlex { RubyUI.DataTableExpandToggle(controls: "emp-1-detail") }
8+
assert_match(/<button[^>]*type="button"/, output)
9+
assert_match(/aria-expanded="false"/, output)
10+
assert_match(/aria-controls="emp-1-detail"/, output)
11+
assert_match(/aria-label="Toggle row details"/, output)
12+
assert_match(/data-action="[^"]*click->ruby-ui--data-table#toggleRowDetail/, output)
13+
refute_match(/data-controller="ruby-ui--data-table-row-expand"/, output)
14+
end
15+
16+
def test_accepts_custom_label_and_initial_expanded_state
17+
output = phlex { RubyUI.DataTableExpandToggle(controls: "x", expanded: true, label: "Toggle") }
18+
assert_match(/aria-expanded="true"/, output)
19+
assert_match(/aria-label="Toggle"/, output)
20+
end
21+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
class RubyUI::DataTableFormTest < ComponentTest
6+
def test_renders_form_with_method_post_and_action
7+
output = phlex { RubyUI.DataTableForm(action: "/x") }
8+
assert_match(/<form[^>]*action="\/x"[^>]*method="post"|<form[^>]*method="post"[^>]*action="\/x"/, output)
9+
end
10+
11+
def test_renders_hidden_authenticity_token
12+
output = phlex { RubyUI.DataTableForm() }
13+
assert_match(/<input[^>]*type="hidden"[^>]*name="authenticity_token"[^>]*value="[^"]+"/, output)
14+
end
15+
16+
def test_yields_children
17+
output = phlex { RubyUI.DataTableForm() { "INNER" } }
18+
assert_match(/INNER/, output)
19+
end
20+
21+
def test_renders_form_with_id_attribute_when_given
22+
output = phlex { RubyUI.DataTableForm(id: "my_form") }
23+
assert_match(/<form[^>]*id="my_form"/, output)
24+
end
25+
26+
def test_renders_form_with_method_get_when_given
27+
output = phlex { RubyUI.DataTableForm(method: "get") }
28+
assert_match(/<form[^>]*method="get"/, output)
29+
end
30+
31+
def test_renders_form_with_method_delete_when_given
32+
output = phlex { RubyUI.DataTableForm(method: "delete") }
33+
assert_match(/<form[^>]*method="delete"/, output)
34+
end
35+
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
require "ruby_ui/data_table/data_table_kaminari_adapter"
5+
6+
class RubyUI::DataTableKaminariAdapterTest < ComponentTest
7+
CollectionDouble = Data.define(:current_page, :total_pages, :total_count, :limit_value)
8+
9+
def test_reads_current_page_total_pages_total_count_limit_value
10+
coll = CollectionDouble.new(current_page: 3, total_pages: 7, total_count: 61, limit_value: 10)
11+
adapter = RubyUI::DataTableKaminariAdapter.new(coll)
12+
assert_equal 3, adapter.current_page
13+
assert_equal 7, adapter.total_pages
14+
assert_equal 61, adapter.total_count
15+
assert_equal 10, adapter.per_page
16+
end
17+
end

0 commit comments

Comments
 (0)