ruby_ui 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +4 -0
  4. data/lib/generators/ruby_ui/component_generator.rb +5 -1
  5. data/lib/generators/ruby_ui/dependencies.yml +10 -0
  6. data/lib/generators/ruby_ui/install/docs_generator.rb +33 -0
  7. data/lib/generators/ruby_ui/install/install_generator.rb +1 -1
  8. data/lib/generators/ruby_ui/javascript_utils.rb +4 -0
  9. data/lib/ruby_ui/accordion/accordion_docs.rb +53 -0
  10. data/lib/ruby_ui/alert/alert_docs.rb +135 -0
  11. data/lib/ruby_ui/alert_dialog/alert_dialog_docs.rb +35 -0
  12. data/lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb +64 -0
  13. data/lib/ruby_ui/avatar/avatar_docs.rb +92 -0
  14. data/lib/ruby_ui/badge/badge_docs.rb +80 -0
  15. data/lib/ruby_ui/breadcrumb/breadcrumb_docs.rb +116 -0
  16. data/lib/ruby_ui/button/button_docs.rb +143 -0
  17. data/lib/ruby_ui/calendar/calendar_docs.rb +34 -0
  18. data/lib/ruby_ui/card/card_docs.rb +114 -0
  19. data/lib/ruby_ui/carousel/carousel_docs.rb +104 -0
  20. data/lib/ruby_ui/chart/chart_docs.rb +115 -0
  21. data/lib/ruby_ui/checkbox/checkbox.rb +2 -2
  22. data/lib/ruby_ui/checkbox/checkbox_docs.rb +41 -0
  23. data/lib/ruby_ui/clipboard/clipboard_docs.rb +30 -0
  24. data/lib/ruby_ui/codeblock/codeblock_docs.rb +55 -0
  25. data/lib/ruby_ui/collapsible/collapsible_docs.rb +96 -0
  26. data/lib/ruby_ui/combobox/combobox.rb +7 -1
  27. data/lib/ruby_ui/combobox/combobox_badge.rb +17 -0
  28. data/lib/ruby_ui/combobox/combobox_badge_trigger.rb +47 -0
  29. data/lib/ruby_ui/combobox/combobox_checkbox.rb +1 -7
  30. data/lib/ruby_ui/combobox/combobox_clear_button.rb +40 -0
  31. data/lib/ruby_ui/combobox/combobox_controller.js +252 -47
  32. data/lib/ruby_ui/combobox/combobox_docs.rb +286 -0
  33. data/lib/ruby_ui/combobox/combobox_input_trigger.rb +64 -0
  34. data/lib/ruby_ui/combobox/combobox_item.rb +5 -7
  35. data/lib/ruby_ui/combobox/combobox_item_indicator.rb +30 -0
  36. data/lib/ruby_ui/combobox/combobox_list_group.rb +1 -1
  37. data/lib/ruby_ui/combobox/combobox_popover.rb +1 -5
  38. data/lib/ruby_ui/combobox/combobox_radio.rb +1 -8
  39. data/lib/ruby_ui/combobox/combobox_toggle_all_checkbox.rb +1 -6
  40. data/lib/ruby_ui/combobox/combobox_trigger.rb +19 -19
  41. data/lib/ruby_ui/command/command_docs.rb +154 -0
  42. data/lib/ruby_ui/context_menu/context_menu.rb +1 -1
  43. data/lib/ruby_ui/context_menu/context_menu_docs.rb +85 -0
  44. data/lib/ruby_ui/data_table/data_table.rb +29 -0
  45. data/lib/ruby_ui/data_table/data_table_bulk_actions.rb +18 -0
  46. data/lib/ruby_ui/data_table/data_table_column_toggle.rb +62 -0
  47. data/lib/ruby_ui/data_table/data_table_column_visibility_controller.js +14 -0
  48. data/lib/ruby_ui/data_table/data_table_controller.js +57 -0
  49. data/lib/ruby_ui/data_table/data_table_docs.rb +180 -0
  50. data/lib/ruby_ui/data_table/data_table_expand_toggle.rb +53 -0
  51. data/lib/ruby_ui/data_table/data_table_form.rb +39 -0
  52. data/lib/ruby_ui/data_table/data_table_kaminari_adapter.rb +17 -0
  53. data/lib/ruby_ui/data_table/data_table_manual_adapter.rb +17 -0
  54. data/lib/ruby_ui/data_table/data_table_pagination.rb +100 -0
  55. data/lib/ruby_ui/data_table/data_table_pagination_bar.rb +15 -0
  56. data/lib/ruby_ui/data_table/data_table_pagy_adapter.rb +17 -0
  57. data/lib/ruby_ui/data_table/data_table_per_page_select.rb +35 -0
  58. data/lib/ruby_ui/data_table/data_table_row_checkbox.rb +30 -0
  59. data/lib/ruby_ui/data_table/data_table_search.rb +57 -0
  60. data/lib/ruby_ui/data_table/data_table_search_controller.js +62 -0
  61. data/lib/ruby_ui/data_table/data_table_select_all_checkbox.rb +21 -0
  62. data/lib/ruby_ui/data_table/data_table_selection_summary.rb +25 -0
  63. data/lib/ruby_ui/data_table/data_table_sort_head.rb +112 -0
  64. data/lib/ruby_ui/data_table/data_table_toolbar.rb +15 -0
  65. data/lib/ruby_ui/dialog/dialog_docs.rb +102 -0
  66. data/lib/ruby_ui/docs/base.rb +90 -0
  67. data/lib/ruby_ui/docs/component_setup_tabs.rb +15 -0
  68. data/lib/ruby_ui/docs/components_table.rb +13 -0
  69. data/lib/ruby_ui/docs/header.rb +17 -0
  70. data/lib/ruby_ui/docs/sidebar_examples.rb +22 -0
  71. data/lib/ruby_ui/docs/visual_code_example.rb +22 -0
  72. data/lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb +212 -0
  73. data/lib/ruby_ui/form/form_docs.rb +178 -0
  74. data/lib/ruby_ui/form/form_field.rb +1 -1
  75. data/lib/ruby_ui/form/form_field_error.rb +1 -1
  76. data/lib/ruby_ui/form/form_field_hint.rb +1 -1
  77. data/lib/ruby_ui/form/form_field_label.rb +1 -1
  78. data/lib/ruby_ui/hover_card/hover_card_docs.rb +71 -0
  79. data/lib/ruby_ui/input/input.rb +4 -3
  80. data/lib/ruby_ui/input/input_docs.rb +68 -0
  81. data/lib/ruby_ui/link/link_docs.rb +106 -0
  82. data/lib/ruby_ui/masked_input/masked_input.rb +11 -1
  83. data/lib/ruby_ui/masked_input/masked_input_controller.js +13 -0
  84. data/lib/ruby_ui/masked_input/masked_input_docs.rb +47 -0
  85. data/lib/ruby_ui/native_select/native_select.rb +39 -0
  86. data/lib/ruby_ui/native_select/native_select_docs.rb +83 -0
  87. data/lib/ruby_ui/native_select/native_select_group.rb +15 -0
  88. data/lib/ruby_ui/native_select/native_select_icon.rb +39 -0
  89. data/lib/ruby_ui/native_select/native_select_option.rb +15 -0
  90. data/lib/ruby_ui/pagination/pagination_docs.rb +127 -0
  91. data/lib/ruby_ui/popover/popover_docs.rb +971 -0
  92. data/lib/ruby_ui/progress/progress_docs.rb +27 -0
  93. data/lib/ruby_ui/radio_button/radio_button.rb +1 -1
  94. data/lib/ruby_ui/radio_button/radio_button_docs.rb +53 -0
  95. data/lib/ruby_ui/select/select_docs.rb +129 -0
  96. data/lib/ruby_ui/separator/separator_docs.rb +36 -0
  97. data/lib/ruby_ui/sheet/sheet_content.rb +1 -1
  98. data/lib/ruby_ui/sheet/sheet_docs.rb +76 -0
  99. data/lib/ruby_ui/shortcut_key/shortcut_key_docs.rb +29 -0
  100. data/lib/ruby_ui/sidebar/sidebar_docs.rb +176 -0
  101. data/lib/ruby_ui/skeleton/skeleton_docs.rb +29 -0
  102. data/lib/ruby_ui/switch/switch_docs.rb +46 -0
  103. data/lib/ruby_ui/table/table_docs.rb +102 -0
  104. data/lib/ruby_ui/tabs/tabs_docs.rb +211 -0
  105. data/lib/ruby_ui/textarea/textarea_docs.rb +54 -0
  106. data/lib/ruby_ui/theme_toggle/theme_toggle_docs.rb +71 -0
  107. data/lib/ruby_ui/tooltip/tooltip_docs.rb +52 -0
  108. data/lib/ruby_ui/typography/typography_docs.rb +107 -0
  109. data/lib/ruby_ui.rb +1 -1
  110. metadata +90 -3
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class ComboboxInputTrigger < Base
5
+ def initialize(placeholder: "", **)
6
+ @placeholder = placeholder
7
+ super(**)
8
+ end
9
+
10
+ def view_template
11
+ div(**attrs) do
12
+ input(
13
+ type: "text",
14
+ placeholder: @placeholder,
15
+ autocomplete: "off",
16
+ autocorrect: "off",
17
+ spellcheck: "false",
18
+ class: "flex-1 border-0 px-0 bg-transparent outline-none focus:ring-0 placeholder:text-muted-foreground text-sm disabled:cursor-not-allowed",
19
+ data: {
20
+ ruby_ui__combobox_target: "inputTrigger",
21
+ action: "keyup->ruby-ui--combobox#filterItems input->ruby-ui--combobox#filterItems"
22
+ }
23
+ )
24
+ chevron_icon
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def default_attrs
31
+ {
32
+ class: "flex h-9 w-full items-center rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 aria-invalid:border-destructive",
33
+ data: {
34
+ ruby_ui__combobox_target: "trigger",
35
+ placeholder: @placeholder,
36
+ action: "click->ruby-ui--combobox#openPopover focusin->ruby-ui--combobox#openPopover"
37
+ },
38
+ aria: {
39
+ haspopup: "listbox",
40
+ expanded: "false"
41
+ }
42
+ }
43
+ end
44
+
45
+ def chevron_icon
46
+ span(class: "shrink-0 flex items-center justify-center size-6 rounded-sm hover:bg-muted hover:text-foreground") do
47
+ svg(
48
+ xmlns: "http://www.w3.org/2000/svg",
49
+ width: "24",
50
+ height: "24",
51
+ viewbox: "0 0 24 24",
52
+ fill: "none",
53
+ stroke: "currentColor",
54
+ stroke_width: "2",
55
+ stroke_linecap: "round",
56
+ stroke_linejoin: "round",
57
+ class: "pointer-events-none size-4 text-muted-foreground"
58
+ ) do |s|
59
+ s.path(d: "m6 9 6 6 6-6")
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -3,19 +3,17 @@
3
3
  module RubyUI
4
4
  class ComboboxItem < Base
5
5
  def view_template(&)
6
- label(**attrs, &)
6
+ label(**attrs) do
7
+ yield if block_given?
8
+ render ComboboxItemIndicator.new
9
+ end
7
10
  end
8
11
 
9
12
  private
10
13
 
11
14
  def default_attrs
12
15
  {
13
- class: [
14
- "flex flex-row w-full text-wrap [&>span,&>div]:truncate gap-2 items-center rounded-sm px-2 py-1 text-sm outline-none cursor-pointer",
15
- "select-none has-[:checked]:bg-accent hover:bg-accent p-2",
16
- "[&>svg]:pointer-events-none [&>svg]:size-4 [&>svg]:shrink-0 aria-[current=true]:bg-accent aria-[current=true]:ring aria-[current=true]:ring-offset-2",
17
- "has-disabled:opacity-50 has-disabled:cursor-not-allowed"
18
- ],
16
+ class: "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground aria-[current=true]:bg-accent has-[input:disabled]:opacity-50 has-[input:disabled]:cursor-not-allowed",
19
17
  role: "option",
20
18
  data: {
21
19
  ruby_ui__combobox_target: "item"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class ComboboxItemIndicator < Base
5
+ def view_template
6
+ svg(
7
+ xmlns: "http://www.w3.org/2000/svg",
8
+ width: "24",
9
+ height: "24",
10
+ viewbox: "0 0 24 24",
11
+ fill: "none",
12
+ stroke: "currentColor",
13
+ stroke_width: "2",
14
+ stroke_linecap: "round",
15
+ stroke_linejoin: "round",
16
+ **attrs
17
+ ) do |s|
18
+ s.path(d: "M20 6 9 17l-5-5")
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def default_attrs
25
+ {
26
+ class: "ml-auto size-4 shrink-0 opacity-0 peer-checked:opacity-100"
27
+ }
28
+ end
29
+ end
30
+ end
@@ -12,7 +12,7 @@ module RubyUI
12
12
 
13
13
  def default_attrs
14
14
  {
15
- class: ["hidden has-[label:not(.hidden)]:flex flex-col py-1 gap-1 border-b", LABEL_CLASSES],
15
+ class: ["hidden has-[label:not(.hidden)]:flex flex-col py-1 gap-1", LABEL_CLASSES],
16
16
  role: "group"
17
17
  }
18
18
  end
@@ -12,15 +12,11 @@ module RubyUI
12
12
  {
13
13
  class: "inset-auto m-0 absolute border bg-background shadow-lg rounded-lg",
14
14
  role: "popover",
15
- autofocus: true,
16
15
  popover: true,
17
16
  data: {
18
17
  ruby_ui__combobox_target: "popover",
19
18
  action: %w[
20
- keydown.down->ruby-ui--combobox#keyDownPressed
21
- keydown.up->ruby-ui--combobox#keyUpPressed
22
- keydown.enter->ruby-ui--combobox#keyEnterPressed
23
- keydown.esc->ruby-ui--combobox#closeDialog:prevent
19
+ toggle->ruby-ui--combobox#handlePopoverToggle
24
20
  resize@window->ruby-ui--combobox#updatePopoverWidth
25
21
  ]
26
22
  }
@@ -10,14 +10,7 @@ module RubyUI
10
10
 
11
11
  def default_attrs
12
12
  {
13
- class: [
14
- "aspect-square h-4 w-4 rounded-full border border-primary accent-primary text-primary shadow",
15
- "focus:outline-none",
16
- "focus-visible:ring-1 focus-visible:ring-ring",
17
- "disabled:cursor-not-allowed disabled:opacity-50",
18
- "checked:bg-primary checked:text-primary-foreground",
19
- "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none"
20
- ],
13
+ class: "peer sr-only",
21
14
  data: {
22
15
  ruby_ui__combobox_target: "input",
23
16
  ruby_ui__form_field_target: "input",
@@ -10,12 +10,7 @@ module RubyUI
10
10
 
11
11
  def default_attrs
12
12
  {
13
- class: [
14
- "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background accent-primary",
15
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
16
- "disabled:cursor-not-allowed disabled:opacity-50",
17
- "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none"
18
- ],
13
+ class: "peer sr-only disabled:cursor-not-allowed",
19
14
  data: {
20
15
  ruby_ui__combobox_target: "toggleAll",
21
16
  action: "change->ruby-ui--combobox#toggleAllItems"
@@ -9,7 +9,7 @@ module RubyUI
9
9
 
10
10
  def view_template
11
11
  button(**attrs) do
12
- span(class: "truncate", data: {ruby_ui__combobox_target: "triggerContent"}) do
12
+ span(class: "truncate text-muted-foreground", data: {ruby_ui__combobox_target: "triggerContent"}) do
13
13
  @placeholder
14
14
  end
15
15
  icon
@@ -26,12 +26,13 @@ module RubyUI
26
26
  "hover:bg-accent hover:text-accent-foreground",
27
27
  "disabled:pointer-events-none disabled:opacity-50",
28
28
  "aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:cursor-not-allowed",
29
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
29
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
30
+ "aria-invalid:border-destructive"
30
31
  ],
31
32
  data: {
32
33
  placeholder: @placeholder,
33
34
  ruby_ui__combobox_target: "trigger",
34
- action: "ruby-ui--combobox#openPopover"
35
+ action: "click->ruby-ui--combobox#togglePopover focus->ruby-ui--combobox#openPopover"
35
36
  },
36
37
  aria: {
37
38
  haspopup: "listbox",
@@ -41,22 +42,21 @@ module RubyUI
41
42
  end
42
43
 
43
44
  def icon
44
- svg(
45
- xmlns: "http://www.w3.org/2000/svg",
46
- viewbox: "0 0 24 24",
47
- fill: "none",
48
- stroke: "currentColor",
49
- class: "ml-2 h-4 w-4 shrink-0 opacity-50",
50
- stroke_width: "2",
51
- stroke_linecap: "round",
52
- stroke_linejoin: "round"
53
- ) do |s|
54
- s.path(
55
- d: "m7 15 5 5 5-5"
56
- )
57
- s.path(
58
- d: "m7 9 5-5 5 5"
59
- )
45
+ span(class: "shrink-0 flex items-center justify-center size-6 rounded-sm hover:bg-muted hover:text-foreground") do
46
+ svg(
47
+ xmlns: "http://www.w3.org/2000/svg",
48
+ width: "24",
49
+ height: "24",
50
+ viewbox: "0 0 24 24",
51
+ fill: "none",
52
+ stroke: "currentColor",
53
+ stroke_width: "2",
54
+ stroke_linecap: "round",
55
+ stroke_linejoin: "round",
56
+ class: "pointer-events-none size-4 text-muted-foreground"
57
+ ) do |s|
58
+ s.path(d: "m6 9 6 6 6-6")
59
+ end
60
60
  end
61
61
  end
62
62
  end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Command < Views::Base
4
+ def view_template
5
+ component = "Command"
6
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
7
+ render Docs::Header.new(title: "Command", description: "Fast, composable, unstyled command menu for Phlex.")
8
+
9
+ Heading(level: 2) { "Usage" }
10
+
11
+ render Docs::VisualCodeExample.new(title: "Example", context: self) do
12
+ <<~RUBY
13
+ CommandDialog do
14
+ CommandDialogTrigger do
15
+ Button(variant: "outline", class: 'w-56 pr-2 pl-3 justify-between') do
16
+ div(class: "flex items-center space-x-1") do
17
+ search_icon
18
+ span(class: "text-muted-foreground font-normal") do
19
+ plain "Search"
20
+ end
21
+ end
22
+ ShortcutKey do
23
+ span(class: "text-xs") { "⌘" }
24
+ plain "K"
25
+ end
26
+ end
27
+ end
28
+ CommandDialogContent do
29
+ Command do
30
+ CommandInput(placeholder: "Type a command or search...")
31
+ CommandEmpty { "No results found." }
32
+ CommandList do
33
+ CommandGroup(title: "Components") do
34
+ components_list.each do |component|
35
+ CommandItem(value: component[:name], href: component[:path]) do
36
+ default_icon
37
+ plain component[:name]
38
+ end
39
+ end
40
+ end
41
+ CommandGroup(title: "Settings") do
42
+ settings_list.each do |setting|
43
+ CommandItem(value: setting[:name], href: setting[:path]) do
44
+ default_icon
45
+ plain setting[:name]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ RUBY
54
+ end
55
+
56
+ render Docs::VisualCodeExample.new(title: "With keybinding", context: self) do
57
+ <<~RUBY
58
+ CommandDialog do
59
+ CommandDialogTrigger(keybindings: ['keydown.ctrl+j@window', 'keydown.meta+j@window']) do
60
+ p(class: "text-sm text-muted-foreground") do
61
+ span(class: 'mr-1') { "Press" }
62
+ ShortcutKey do
63
+ span(class: "text-xs") { "⌘" }
64
+ plain "J"
65
+ end
66
+ end
67
+ end
68
+ CommandDialogContent do
69
+ Command do
70
+ CommandInput(placeholder: "Type a command or search...")
71
+ CommandEmpty { "No results found." }
72
+ CommandList do
73
+ CommandGroup(title: "Components") do
74
+ components_list.each do |component|
75
+ CommandItem(value: component[:name], href: component[:path]) do
76
+ default_icon
77
+ plain component[:name]
78
+ end
79
+ end
80
+ end
81
+ CommandGroup(title: "Settings") do
82
+ settings_list.each do |setting|
83
+ CommandItem(value: setting[:name], href: setting[:path]) do
84
+ default_icon
85
+ plain setting[:name]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ RUBY
94
+ end
95
+
96
+ render Components::ComponentSetup::Tabs.new(component_name: component)
97
+
98
+ render Docs::ComponentsTable.new(component_files(component))
99
+ end
100
+ end
101
+
102
+ private
103
+
104
+ def search_icon
105
+ svg(
106
+ xmlns: "http://www.w3.org/2000/svg",
107
+ viewbox: "0 0 20 20",
108
+ fill: "currentColor",
109
+ class: "w-4 h-4 mr-1.5"
110
+ ) do |s|
111
+ s.path(
112
+ fill_rule: "evenodd",
113
+ d:
114
+ "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z",
115
+ clip_rule: "evenodd"
116
+ )
117
+ end
118
+ end
119
+
120
+ def default_icon
121
+ svg(
122
+ xmlns: "http://www.w3.org/2000/svg",
123
+ viewbox: "0 0 24 24",
124
+ fill: "currentColor",
125
+ class: "w-5 h-5"
126
+ ) do |s|
127
+ s.path(
128
+ fill_rule: "evenodd",
129
+ d:
130
+ "M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25zm4.28 10.28a.75.75 0 000-1.06l-3-3a.75.75 0 10-1.06 1.06l1.72 1.72H8.25a.75.75 0 000 1.5h5.69l-1.72 1.72a.75.75 0 101.06 1.06l3-3z",
131
+ clip_rule: "evenodd"
132
+ )
133
+ end
134
+ end
135
+
136
+ def components_list
137
+ [
138
+ {name: "Accordion", path: docs_accordion_path},
139
+ {name: "Alert", path: docs_alert_path},
140
+ {name: "Alert Dialog", path: docs_alert_dialog_path},
141
+ {name: "Aspect Ratio", path: docs_aspect_ratio_path},
142
+ {name: "Avatar", path: docs_avatar_path},
143
+ {name: "Badge", path: docs_badge_path}
144
+ ]
145
+ end
146
+
147
+ def settings_list
148
+ [
149
+ {name: "Profile", path: "#"},
150
+ {name: "Mail", path: "#"},
151
+ {name: "Settings", path: "#"}
152
+ ]
153
+ end
154
+ end
@@ -18,7 +18,7 @@ module RubyUI
18
18
  {
19
19
  data: {
20
20
  controller: "ruby-ui--context-menu",
21
- popover_options_value: @options.to_json
21
+ ruby_ui__context_menu_options_value: @options.to_json
22
22
  }
23
23
  }
24
24
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::ContextMenu < Views::Base
4
+ def view_template
5
+ component = "ContextMenu"
6
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
7
+ render Docs::Header.new(title: "Context Menu", description: "Displays a menu to the user — such as a set of actions or functions — triggered by a right click.")
8
+
9
+ Heading(level: 2) { "Usage" }
10
+
11
+ render Docs::VisualCodeExample.new(title: "Example", context: self) do
12
+ <<~RUBY
13
+ ContextMenu do
14
+ ContextMenuTrigger(class: 'flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm') { "Right click here" }
15
+ ContextMenuContent(class: 'w-64') do
16
+ ContextMenuItem(href: '#', shortcut: "⌘[") { "Back" }
17
+ ContextMenuItem(href: '#', shortcut: "⌘]", disabled: true) { "Forward" }
18
+ ContextMenuItem(href: '#', shortcut: "⌘R") { "Reload" }
19
+ ContextMenuSeparator
20
+ ContextMenuItem(href: '#', shortcut: "⌘⇧B", checked: true) { "Show Bookmarks Bar" }
21
+ ContextMenuItem(href: '#') { "Show Full URLs" }
22
+ ContextMenuSeparator
23
+ ContextMenuLabel(inset: true) { "More Tools" }
24
+ ContextMenuSeparator
25
+ ContextMenuItem(href: '#') { "Developer Tools" }
26
+ ContextMenuItem(href: '#') { "Task Manager" }
27
+ ContextMenuItem(href: '#') { "Extensions" }
28
+ end
29
+ end
30
+ RUBY
31
+ end
32
+
33
+ render Docs::VisualCodeExample.new(title: "Placement", context: self) do
34
+ <<~RUBY
35
+ div(class: 'space-y-4') do
36
+ ContextMenu(options: { placement: 'right' }) do
37
+ ContextMenuTrigger(class: 'flex flex-col items-center gap-y-2 h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm') do
38
+ plain "Right click here"
39
+ Badge(variant: :primary) { "right" }
40
+ end
41
+ ContextMenuContent(class: 'w-64') do
42
+ ContextMenuItem(href: '#', shortcut: "⌘[") { "Back" }
43
+ ContextMenuItem(href: '#', shortcut: "⌘]", disabled: true) { "Forward" }
44
+ ContextMenuItem(href: '#', shortcut: "⌘R") { "Reload" }
45
+ ContextMenuSeparator
46
+ ContextMenuItem(href: '#', shortcut: "⌘⇧B", checked: true) { "Show Bookmarks Bar" }
47
+ ContextMenuItem(href: '#') { "Show Full URLs" }
48
+ ContextMenuSeparator
49
+ ContextMenuLabel(inset: true) { "More Tools" }
50
+ ContextMenuSeparator
51
+ ContextMenuItem(href: '#') { "Developer Tools" }
52
+ ContextMenuItem(href: '#') { "Task Manager" }
53
+ ContextMenuItem(href: '#') { "Extensions" }
54
+ end
55
+ end
56
+ ContextMenu(options: { placement: 'left' }) do
57
+ ContextMenuTrigger(class: 'flex flex-col items-center gap-y-2 h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm') do
58
+ plain "Right click here"
59
+ Badge(variant: :primary) { "left" }
60
+ end
61
+ ContextMenuContent(class: 'w-64') do
62
+ ContextMenuItem(href: '#', shortcut: "⌘[") { "Back" }
63
+ ContextMenuItem(href: '#', shortcut: "⌘]", disabled: true) { "Forward" }
64
+ ContextMenuItem(href: '#', shortcut: "⌘R") { "Reload" }
65
+ ContextMenuSeparator
66
+ ContextMenuItem(href: '#', shortcut: "⌘⇧B", checked: true) { "Show Bookmarks Bar" }
67
+ ContextMenuItem(href: '#') { "Show Full URLs" }
68
+ ContextMenuSeparator
69
+ ContextMenuLabel(inset: true) { "More Tools" }
70
+ ContextMenuSeparator
71
+ ContextMenuItem(href: '#') { "Developer Tools" }
72
+ ContextMenuItem(href: '#') { "Task Manager" }
73
+ ContextMenuItem(href: '#') { "Extensions" }
74
+ end
75
+ end
76
+ end
77
+ RUBY
78
+ end
79
+
80
+ render Components::ComponentSetup::Tabs.new(component_name: component)
81
+
82
+ render Docs::ComponentsTable.new(component_files(component))
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class DataTable < Base
5
+ register_element :turbo_frame, tag: "turbo-frame"
6
+
7
+ def initialize(id:, **attrs)
8
+ @id = id
9
+ super(**attrs)
10
+ end
11
+
12
+ def view_template(&block)
13
+ turbo_frame(id: @id, target: "_top") do
14
+ div(**attrs) do
15
+ yield if block
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def default_attrs
23
+ {
24
+ class: "w-full space-y-4",
25
+ data: {controller: "ruby-ui--data-table"}
26
+ }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class DataTableBulkActions < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "hidden items-center gap-2",
14
+ data: {"ruby-ui--data-table-target": "bulkActions"}
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class DataTableColumnToggle < Base
5
+ def initialize(columns:, **attrs)
6
+ @columns = columns
7
+ super(**attrs)
8
+ end
9
+
10
+ def view_template
11
+ div(**attrs) do
12
+ render RubyUI::DropdownMenu.new do
13
+ render RubyUI::DropdownMenuTrigger.new do
14
+ render RubyUI::Button.new(variant: :outline, size: :sm) do
15
+ plain "Columns"
16
+ # inline chevron-down SVG (lucide 24px, 1px stroke)
17
+ svg(
18
+ xmlns: "http://www.w3.org/2000/svg",
19
+ width: "16",
20
+ height: "16",
21
+ viewBox: "0 0 24 24",
22
+ fill: "none",
23
+ stroke: "currentColor",
24
+ stroke_width: "2",
25
+ stroke_linecap: "round",
26
+ stroke_linejoin: "round",
27
+ class: "w-4 h-4 ml-1"
28
+ ) do |s|
29
+ s.polyline(points: "6 9 12 15 18 9")
30
+ end
31
+ end
32
+ end
33
+ render RubyUI::DropdownMenuContent.new do
34
+ @columns.each do |col|
35
+ label(class: "flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer hover:bg-accent") do
36
+ input(
37
+ type: "checkbox",
38
+ checked: true,
39
+ class: "h-4 w-4 rounded border border-input accent-primary cursor-pointer",
40
+ data: {
41
+ column_key: col[:key].to_s,
42
+ action: "change->ruby-ui--data-table-column-visibility#toggle"
43
+ }
44
+ )
45
+ span { plain col[:label] }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def default_attrs
56
+ {
57
+ class: "relative",
58
+ data: {controller: "ruby-ui--data-table-column-visibility"}
59
+ }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,14 @@
1
+ // app/javascript/controllers/ruby_ui/data_table_column_visibility_controller.js
2
+ import { Controller } from "@hotwired/stimulus";
3
+
4
+ export default class extends Controller {
5
+ toggle(event) {
6
+ const key = event.target.dataset.columnKey;
7
+ const visible = event.target.checked;
8
+ const root = this.element.closest('[data-controller~="ruby-ui--data-table"]');
9
+ if (!root) return;
10
+ root
11
+ .querySelectorAll(`[data-column="${key}"]`)
12
+ .forEach((el) => el.classList.toggle("hidden", !visible));
13
+ }
14
+ }
@@ -0,0 +1,57 @@
1
+ // app/javascript/controllers/ruby_ui/data_table_controller.js
2
+ import { Controller } from "@hotwired/stimulus";
3
+
4
+ export default class extends Controller {
5
+ static targets = [
6
+ "selectAll",
7
+ "rowCheckbox",
8
+ "selectionSummary",
9
+ "selectionBar",
10
+ "bulkActions",
11
+ ];
12
+
13
+ connect() {
14
+ this.updateState();
15
+ }
16
+
17
+ toggleAll(event) {
18
+ const checked = event.target.checked;
19
+ this.rowCheckboxTargets.forEach((cb) => {
20
+ cb.checked = checked;
21
+ });
22
+ this.updateState();
23
+ }
24
+
25
+ toggleRow() {
26
+ this.updateState();
27
+ }
28
+
29
+ toggleRowDetail(event) {
30
+ const button = event.currentTarget;
31
+ const id = button.getAttribute("aria-controls");
32
+ if (!id) return;
33
+ const target = document.getElementById(id);
34
+ if (!target) return;
35
+ const expanded = button.getAttribute("aria-expanded") === "true";
36
+ button.setAttribute("aria-expanded", String(!expanded));
37
+ target.classList.toggle("hidden", expanded);
38
+ }
39
+
40
+ updateState() {
41
+ const total = this.rowCheckboxTargets.length;
42
+ const selected = this.rowCheckboxTargets.filter((cb) => cb.checked).length;
43
+
44
+ if (this.hasSelectAllTarget) {
45
+ this.selectAllTarget.checked = total > 0 && selected === total;
46
+ this.selectAllTarget.indeterminate = selected > 0 && selected < total;
47
+ }
48
+
49
+ if (this.hasSelectionSummaryTarget) {
50
+ this.selectionSummaryTarget.textContent = `${selected} of ${total} row(s) selected.`;
51
+ }
52
+
53
+ if (this.hasBulkActionsTarget) {
54
+ this.bulkActionsTarget.classList.toggle("hidden", selected === 0);
55
+ }
56
+ }
57
+ }