Skip to content
Closed
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
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ GIT

GIT
remote: https://github.com/ruby-ui/ruby_ui.git
revision: e5a1f6bb7e0e01a3d7584c9ab8b0a94a47f1611b
revision: a3e5c8488b11a4c15caf2f2391181f8f057a589f
branch: main
specs:
ruby_ui (1.0.2)
ruby_ui (1.2.0)

GEM
remote: https://rubygems.org/
Expand Down Expand Up @@ -353,4 +353,4 @@ RUBY VERSION
ruby 3.4.7p58

BUNDLED WITH
2.6.4
2.6.9
6 changes: 3 additions & 3 deletions app/components/docs/visual_code_example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,16 @@ def render_preview_tab(&block)
end

def iframe_preview
div(class: "relative aspect-[4/2.5] w-full overflow-hidden rounded-md border", data: {controller: "iframe-theme"}) do
div(class: "absolute inset-0 hidden w-[1600px] bg-background md:block") do
div(class: "relative min-h-[500px] w-full overflow-hidden rounded-md border", data: {controller: "iframe-theme"}) do
div(class: "absolute inset-0 hidden w-full bg-background md:block") do
iframe(src: @src, class: "size-full", data: {iframe_theme_target: "iframe"})
end
end
end

def raw_preview
div(class: "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative rounded-md border") do
div(class: "preview flex min-h-[350px] w-full justify-center p-10 items-center") do
div(class: "preview min-h-[350px] w-full p-6") do
decoded_code = CGI.unescapeHTML(@display_code)
@context.instance_eval(decoded_code)
end
Expand Down
29 changes: 29 additions & 0 deletions app/components/ruby_ui/data_table/data_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module RubyUI
class DataTable < Base
register_element :turbo_frame, tag: "turbo-frame"

def initialize(id:, **attrs)
@id = id
super(**attrs)
end

def view_template(&block)
turbo_frame(id: @id, target: "_top") do
div(**attrs) do
yield if block
end
end
end

private

def default_attrs
{
class: "w-full space-y-4",
data: {controller: "ruby-ui--data-table"}
}
end
end
end
18 changes: 18 additions & 0 deletions app/components/ruby_ui/data_table/data_table_bulk_actions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module RubyUI
class DataTableBulkActions < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{
class: "hidden items-center gap-2",
data: {"ruby-ui--data-table-target": "bulkActions"}
}
end
end
end
62 changes: 62 additions & 0 deletions app/components/ruby_ui/data_table/data_table_column_toggle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

module RubyUI
class DataTableColumnToggle < Base
def initialize(columns:, **attrs)
@columns = columns
super(**attrs)
end

def view_template
div(**attrs) do
render RubyUI::DropdownMenu.new do
render RubyUI::DropdownMenuTrigger.new do
render RubyUI::Button.new(variant: :outline, size: :sm) do
plain "Columns"
# inline chevron-down SVG (lucide 24px, 1px stroke)
svg(
xmlns: "http://www.w3.org/2000/svg",
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
stroke_width: "2",
stroke_linecap: "round",
stroke_linejoin: "round",
class: "w-4 h-4 ml-1"
) do |s|
s.polyline(points: "6 9 12 15 18 9")
end
end
end
render RubyUI::DropdownMenuContent.new do
@columns.each do |col|
label(class: "flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer hover:bg-accent") do
input(
type: "checkbox",
checked: true,
class: "h-4 w-4 rounded border border-input accent-primary cursor-pointer",
data: {
column_key: col[:key].to_s,
action: "change->ruby-ui--data-table-column-visibility#toggle"
}
)
span { plain col[:label] }
end
end
end
end
end
end

private

def default_attrs
{
class: "relative",
data: {controller: "ruby-ui--data-table-column-visibility"}
}
end
end
end
53 changes: 53 additions & 0 deletions app/components/ruby_ui/data_table/data_table_expand_toggle.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# frozen_string_literal: true

module RubyUI
class DataTableExpandToggle < Base
def initialize(controls:, expanded: false, label: "Toggle row details", **attrs)
@controls = controls
@expanded = expanded
@label = label
super(**attrs)
end

def view_template
button(
type: "button",
aria_expanded: @expanded.to_s,
aria_controls: @controls,
aria_label: @label,
data: {
action: "click->ruby-ui--data-table#toggleRowDetail"
},
**attrs
) do
render_icon
end
end

private

def render_icon
# inline chevron-right SVG (lucide)
svg(
xmlns: "http://www.w3.org/2000/svg",
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
stroke_width: "2",
stroke_linecap: "round",
stroke_linejoin: "round",
class: "h-4 w-4 transition-transform duration-150 group-aria-expanded:rotate-90"
) do |s|
s.polyline(points: "9 18 15 12 9 6")
end
end

def default_attrs
{
class: "group inline-flex items-center justify-center h-8 w-8 rounded-md hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
}
end
end
end
39 changes: 39 additions & 0 deletions app/components/ruby_ui/data_table/data_table_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module RubyUI
class DataTableForm < Base
def initialize(action: "", method: "post", id: nil, **attrs)
@action = action
@method = method
@id = id
super(**attrs)
end

def view_template(&block)
form_attrs = {action: @action, method: @method}
form_attrs[:id] = @id if @id
form(**form_attrs, **attrs) do
input(type: "hidden", name: "authenticity_token", value: csrf_token)
yield if block
end
end

private

def csrf_token
# In a Rails app, view_context provides a real CSRF token.
# Outside Rails (gem tests), fall back to a placeholder.
if respond_to?(:helpers, true) && helpers.respond_to?(:form_authenticity_token)
helpers.form_authenticity_token
elsif respond_to?(:view_context, true) && view_context.respond_to?(:form_authenticity_token)
view_context.form_authenticity_token
else
"csrf-token-placeholder"
end
end

def default_attrs
{}
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module RubyUI
class DataTableKaminariAdapter
def initialize(collection)
@collection = collection
end

def current_page = @collection.current_page

def total_pages = @collection.total_pages

def total_count = @collection.total_count

def per_page = @collection.limit_value
end
end
17 changes: 17 additions & 0 deletions app/components/ruby_ui/data_table/data_table_manual_adapter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module RubyUI
class DataTableManualAdapter
attr_reader :current_page, :per_page, :total_count

def initialize(page:, per_page:, total_count:)
@current_page = page.to_i
@per_page = [per_page.to_i, 1].max
@total_count = total_count.to_i
end

def total_pages
[(@total_count.to_f / @per_page).ceil, 1].max
end
end
end
100 changes: 100 additions & 0 deletions app/components/ruby_ui/data_table/data_table_pagination.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

require "cgi"
require_relative "data_table_manual_adapter"
require_relative "data_table_pagy_adapter"
require_relative "data_table_kaminari_adapter"

module RubyUI
class DataTablePagination < Base
def initialize(with: nil, pagy: nil, kaminari: nil, page: nil, per_page: nil, total_count: nil, page_param: "page", path: "", query: {}, window: 1, prev_label: "<", next_label: ">", **attrs)
@adapter = resolve_adapter(with:, pagy:, kaminari:, page:, per_page:, total_count:)
@page_param = page_param
@path = path
@query = query.to_h.transform_keys(&:to_s)
@window = window
@prev_label = prev_label
@next_label = next_label
super(**attrs)
end

def view_template
return if total <= 1

render RubyUI::Pagination.new(class: "mx-0 w-auto justify-end", **attrs) do
render RubyUI::PaginationContent.new do
prev_item
number_items
next_item
end
end
end

private

def resolve_adapter(with:, pagy:, kaminari:, page:, per_page:, total_count:)
return with if with
return RubyUI::DataTablePagyAdapter.new(pagy) if pagy
return RubyUI::DataTableKaminariAdapter.new(kaminari) if kaminari
if page && per_page && total_count
return RubyUI::DataTableManualAdapter.new(page:, per_page:, total_count:)
end
raise ArgumentError, "DataTablePagination requires one of: with:, pagy:, kaminari:, or page:+per_page:+total_count:"
end

def current = @adapter.current_page

def total = @adapter.total_pages

def page_href(p)
qs = build_query(@query.merge(@page_param => p.to_s))
qs.empty? ? @path : "#{@path}?#{qs}"
end

def build_query(hash)
hash.flat_map { |k, v|
Array(v).map { |val| "#{CGI.escape(k.to_s)}=#{CGI.escape(val.to_s)}" }
}.join("&")
end

def prev_item
if current <= 1
li do
span(class: "opacity-50 pointer-events-none px-3 h-9 inline-flex items-center text-sm") { @prev_label }
end
else
render RubyUI::PaginationItem.new(href: page_href(current - 1)) { @prev_label }
end
end

def next_item
if current >= total
li do
span(class: "opacity-50 pointer-events-none px-3 h-9 inline-flex items-center text-sm") { @next_label }
end
else
render RubyUI::PaginationItem.new(href: page_href(current + 1)) { @next_label }
end
end

def number_items
windowed_pages.each do |p|
if p == :gap
render RubyUI::PaginationEllipsis.new
else
render RubyUI::PaginationItem.new(href: page_href(p), active: p == current) { plain p.to_s }
end
end
end

def windowed_pages
return (1..total).to_a if total <= 7
pages = [1]
pages << :gap if current - @window > 2
((current - @window)..(current + @window)).each { |p| pages << p if p > 1 && p < total }
pages << :gap if current + @window < total - 1
pages << total
pages
end
end
end
15 changes: 15 additions & 0 deletions app/components/ruby_ui/data_table/data_table_pagination_bar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module RubyUI
class DataTablePaginationBar < Base
def view_template(&)
div(**attrs, &)
end

private

def default_attrs
{class: "flex items-center justify-between gap-4 py-2"}
end
end
end
Loading