Skip to content
Draft
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
26 changes: 26 additions & 0 deletions lib/ruby_ui/data_table/data_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module RubyUI
class DataTable < Base
def initialize(src: nil, **attrs)
@src = src
super(**attrs)
end

def view_template(&)
div(**attrs, &)
end

private

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

module RubyUI
class DataTableContent < Base
def initialize(frame_id: "data_table_content", **attrs)
@frame_id = frame_id
super(**attrs)
end

def view_template(&)
div(id: @frame_id, **attrs, &)
end

private

def default_attrs
{
class: "rounded-md border"
}
end
end
end
77 changes: 77 additions & 0 deletions lib/ruby_ui/data_table/data_table_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["search", "perPage"]
static values = {
src: String,
sortColumn: String,
sortDirection: String,
page: { type: Number, default: 1 },
perPage: { type: Number, default: 10 },
searchQuery: String,
debounceMs: { type: Number, default: 300 }
}

connect() {
this.searchTimeout = null
}

disconnect() {
if (this.searchTimeout) clearTimeout(this.searchTimeout)
}

sort(event) {
const { column, direction } = event.params
this.sortColumnValue = column
this.sortDirectionValue = direction || ""
this.pageValue = 1
this._reload()
}

search() {
if (this.searchTimeout) clearTimeout(this.searchTimeout)
this.searchTimeout = setTimeout(() => {
this.searchQueryValue = this.searchTarget.value
this.pageValue = 1
this._reload()
}, this.debounceMsValue)
}

nextPage() {
this.pageValue += 1
this._reload()
}

previousPage() {
if (this.pageValue > 1) {
this.pageValue -= 1
this._reload()
}
}

changePerPage() {
this.perPageValue = parseInt(this.perPageTarget.value)
this.pageValue = 1
this._reload()
}

_reload() {
if (!this.hasSrcValue || !this.srcValue) return

const url = new URL(this.srcValue, window.location.origin)
if (this.sortColumnValue) url.searchParams.set("sort", this.sortColumnValue)
if (this.sortDirectionValue) url.searchParams.set("direction", this.sortDirectionValue)
if (this.searchQueryValue) url.searchParams.set("search", this.searchQueryValue)
url.searchParams.set("page", this.pageValue)
url.searchParams.set("per_page", this.perPageValue)

// Use Turbo to fetch and replace the content frame
const frame = this.element.querySelector("turbo-frame")
if (frame) {
frame.src = url.toString()
} else {
// Fallback: dispatch custom event for consumer to handle
this.dispatch("navigate", { detail: { url: url.toString() } })
}
}
}
9 changes: 9 additions & 0 deletions lib/ruby_ui/data_table/data_table_docs.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

module RubyUI
class DataTableDocs < Phlex::HTML
def view_template
# Documentation placeholder for RubyUI website
end
end
end
60 changes: 60 additions & 0 deletions lib/ruby_ui/data_table/data_table_pagination.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module RubyUI
class DataTablePagination < Base
def initialize(current_page:, total_pages:, **attrs)
@current_page = current_page
@total_pages = total_pages
super(**attrs)
end

def view_template
div(**attrs) do
div(class: "flex items-center justify-between px-2") do
div(class: "flex-1 text-sm text-muted-foreground") do
plain "Page #{@current_page} of #{@total_pages}"
end
div(class: "flex items-center space-x-2") do
nav_button(
direction: "previous",
disabled: @current_page <= 1,
action: "click->ruby-ui--data-table#previousPage",
icon_path: "m15 18-6-6 6-6"
)
nav_button(
direction: "next",
disabled: @current_page >= @total_pages,
action: "click->ruby-ui--data-table#nextPage",
icon_path: "m9 18 6-6-6-6"
)
end
end
end
end

private

def nav_button(direction:, disabled:, action:, icon_path:)
button(
type: "button",
class: "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground h-8 w-8 p-0 #{disabled ? "opacity-50 pointer-events-none" : ""}",
disabled: disabled,
aria_label: direction,
data: {action: action}
) do
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"
) { |s| s.path(d: icon_path) }
end
end

def default_attrs
{
class: "flex items-center justify-end py-4"
}
end
end
end
37 changes: 37 additions & 0 deletions lib/ruby_ui/data_table/data_table_per_page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

module RubyUI
class DataTablePerPage < Base
def initialize(options: [10, 20, 50, 100], current: 10, **attrs)
@options = options
@current = current
super(**attrs)
end

def view_template
div(**attrs) do
span(class: "text-sm text-muted-foreground") { "Rows per page" }
render RubyUI::NativeSelect.new(
size: :sm,
class: "w-16",
data: {
action: "change->ruby-ui--data-table#changePerPage",
ruby_ui__data_table_target: "perPage"
}
) do
@options.each do |opt|
render RubyUI::NativeSelectOption.new(value: opt, selected: opt == @current) { opt.to_s }
end
end
end
end

private

def default_attrs
{
class: "flex items-center gap-2"
}
end
end
end
32 changes: 32 additions & 0 deletions lib/ruby_ui/data_table/data_table_search.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module RubyUI
class DataTableSearch < Base
def initialize(placeholder: "Search...", name: "search", **attrs)
@placeholder = placeholder
@name = name
super(**attrs)
end

def view_template
render RubyUI::Input.new(
type: :search,
name: @name,
placeholder: @placeholder,
**attrs
)
end

private

def default_attrs
{
class: "max-w-sm",
data: {
ruby_ui__data_table_target: "search",
action: "input->ruby-ui--data-table#search"
}
}
end
end
end
69 changes: 69 additions & 0 deletions lib/ruby_ui/data_table/data_table_sortable_header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

module RubyUI
class DataTableSortableHeader < Base
def initialize(column:, label: nil, direction: nil, **attrs)
@column = column
@label = label || column.to_s.tr("_", " ").capitalize
@direction = direction # nil, "asc", or "desc"
super(**attrs)
end

def view_template(&block)
th(**attrs) do
button(
type: "button",
class: "inline-flex items-center gap-1 hover:text-foreground",
data: {
action: "click->ruby-ui--data-table#sort",
ruby_ui__data_table_column_param: @column,
ruby_ui__data_table_direction_param: next_direction
}
) do
if block
yield
else
plain @label
end
render_sort_icon
end
end
end

private

def next_direction
case @direction
when "asc" then "desc"
when "desc" then ""
else "asc"
end
end

def render_sort_icon
svg(
xmlns: "http://www.w3.org/2000/svg",
width: "14", height: "14",
viewBox: "0 0 24 24",
fill: "none", stroke: "currentColor",
stroke_width: "2", stroke_linecap: "round", stroke_linejoin: "round",
class: "ml-1 #{@direction ? "" : "text-muted-foreground"}"
) do |s|
if @direction == "asc"
s.path(d: "m18 15-6-6-6 6")
elsif @direction == "desc"
s.path(d: "m6 9 6 6 6-6")
else
s.path(d: "m7 15 5 5 5-5")
s.path(d: "m7 9 5-5 5 5")
end
end
end

def default_attrs
{
class: "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0"
}
end
end
end
17 changes: 17 additions & 0 deletions lib/ruby_ui/data_table/data_table_toolbar.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

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

private

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