ruby_ui 1.0.1 → 1.1.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +6 -0
  4. data/lib/generators/ruby_ui/component/all_generator.rb +22 -0
  5. data/lib/generators/ruby_ui/component_generator.rb +4 -3
  6. data/lib/generators/ruby_ui/install/docs_generator.rb +33 -0
  7. data/lib/generators/ruby_ui/install/install_generator.rb +1 -7
  8. data/lib/generators/ruby_ui/install/templates/tailwind.css.erb +0 -3
  9. data/lib/generators/ruby_ui/javascript_utils.rb +4 -0
  10. data/lib/ruby_ui/accordion/accordion_docs.rb +53 -0
  11. data/lib/ruby_ui/alert/alert_docs.rb +135 -0
  12. data/lib/ruby_ui/alert_dialog/alert_dialog_content.rb +2 -3
  13. data/lib/ruby_ui/alert_dialog/alert_dialog_docs.rb +35 -0
  14. data/lib/ruby_ui/aspect_ratio/aspect_ratio_docs.rb +64 -0
  15. data/lib/ruby_ui/avatar/avatar_docs.rb +92 -0
  16. data/lib/ruby_ui/badge/badge_docs.rb +80 -0
  17. data/lib/ruby_ui/breadcrumb/breadcrumb_docs.rb +116 -0
  18. data/lib/ruby_ui/button/button.rb +32 -16
  19. data/lib/ruby_ui/button/button_docs.rb +143 -0
  20. data/lib/ruby_ui/calendar/calendar_docs.rb +34 -0
  21. data/lib/ruby_ui/card/card_docs.rb +114 -0
  22. data/lib/ruby_ui/carousel/carousel_docs.rb +104 -0
  23. data/lib/ruby_ui/chart/chart_docs.rb +115 -0
  24. data/lib/ruby_ui/checkbox/checkbox.rb +7 -1
  25. data/lib/ruby_ui/checkbox/checkbox_docs.rb +41 -0
  26. data/lib/ruby_ui/clipboard/clipboard_docs.rb +30 -0
  27. data/lib/ruby_ui/codeblock/codeblock_docs.rb +55 -0
  28. data/lib/ruby_ui/collapsible/collapsible_docs.rb +96 -0
  29. data/lib/ruby_ui/combobox/combobox.rb +3 -2
  30. data/lib/ruby_ui/combobox/combobox_checkbox.rb +4 -2
  31. data/lib/ruby_ui/combobox/combobox_controller.js +20 -5
  32. data/lib/ruby_ui/combobox/combobox_docs.rb +151 -0
  33. data/lib/ruby_ui/combobox/combobox_item.rb +2 -1
  34. data/lib/ruby_ui/combobox/combobox_popover.rb +2 -1
  35. data/lib/ruby_ui/combobox/combobox_radio.rb +8 -1
  36. data/lib/ruby_ui/combobox/combobox_search_input.rb +11 -5
  37. data/lib/ruby_ui/combobox/combobox_toggle_all_checkbox.rb +4 -2
  38. data/lib/ruby_ui/combobox/combobox_trigger.rb +8 -2
  39. data/lib/ruby_ui/command/command_controller.js +0 -1
  40. data/lib/ruby_ui/command/command_docs.rb +154 -0
  41. data/lib/ruby_ui/context_menu/context_menu.rb +1 -1
  42. data/lib/ruby_ui/context_menu/context_menu_docs.rb +85 -0
  43. data/lib/ruby_ui/dialog/dialog_content.rb +2 -3
  44. data/lib/ruby_ui/dialog/dialog_controller.js +1 -1
  45. data/lib/ruby_ui/dialog/dialog_docs.rb +102 -0
  46. data/lib/ruby_ui/docs/base.rb +90 -0
  47. data/lib/ruby_ui/docs/component_setup_tabs.rb +15 -0
  48. data/lib/ruby_ui/docs/components_table.rb +13 -0
  49. data/lib/ruby_ui/docs/header.rb +17 -0
  50. data/lib/ruby_ui/docs/sidebar_examples.rb +22 -0
  51. data/lib/ruby_ui/docs/visual_code_example.rb +22 -0
  52. data/lib/ruby_ui/dropdown_menu/dropdown_menu.rb +9 -0
  53. data/lib/ruby_ui/dropdown_menu/dropdown_menu_content.rb +17 -2
  54. data/lib/ruby_ui/dropdown_menu/dropdown_menu_controller.js +43 -14
  55. data/lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb +212 -0
  56. data/lib/ruby_ui/form/form_docs.rb +178 -0
  57. data/lib/ruby_ui/form/form_field.rb +1 -1
  58. data/lib/ruby_ui/form/form_field_error.rb +1 -1
  59. data/lib/ruby_ui/form/form_field_hint.rb +1 -1
  60. data/lib/ruby_ui/form/form_field_label.rb +7 -1
  61. data/lib/ruby_ui/hover_card/hover_card_docs.rb +71 -0
  62. data/lib/ruby_ui/input/input.rb +9 -1
  63. data/lib/ruby_ui/input/input_docs.rb +68 -0
  64. data/lib/ruby_ui/link/link.rb +32 -16
  65. data/lib/ruby_ui/link/link_docs.rb +106 -0
  66. data/lib/ruby_ui/masked_input/masked_input_docs.rb +47 -0
  67. data/lib/ruby_ui/pagination/pagination_docs.rb +127 -0
  68. data/lib/ruby_ui/popover/popover_docs.rb +971 -0
  69. data/lib/ruby_ui/progress/progress_docs.rb +27 -0
  70. data/lib/ruby_ui/radio_button/radio_button.rb +3 -1
  71. data/lib/ruby_ui/radio_button/radio_button_docs.rb +53 -0
  72. data/lib/ruby_ui/select/select_docs.rb +129 -0
  73. data/lib/ruby_ui/select/select_item.rb +14 -5
  74. data/lib/ruby_ui/select/select_trigger.rb +9 -4
  75. data/lib/ruby_ui/separator/separator_docs.rb +36 -0
  76. data/lib/ruby_ui/sheet/sheet_content.rb +1 -1
  77. data/lib/ruby_ui/sheet/sheet_docs.rb +76 -0
  78. data/lib/ruby_ui/shortcut_key/shortcut_key_docs.rb +29 -0
  79. data/lib/ruby_ui/sidebar/collapsible_sidebar.rb +99 -0
  80. data/lib/ruby_ui/sidebar/mobile_sidebar.rb +45 -0
  81. data/lib/ruby_ui/sidebar/non_collapsible_sidebar.rb +17 -0
  82. data/lib/ruby_ui/sidebar/sidebar.rb +29 -0
  83. data/lib/ruby_ui/sidebar/sidebar_content.rb +20 -0
  84. data/lib/ruby_ui/sidebar/sidebar_controller.js +67 -0
  85. data/lib/ruby_ui/sidebar/sidebar_docs.rb +176 -0
  86. data/lib/ruby_ui/sidebar/sidebar_footer.rb +20 -0
  87. data/lib/ruby_ui/sidebar/sidebar_group.rb +20 -0
  88. data/lib/ruby_ui/sidebar/sidebar_group_action.rb +33 -0
  89. data/lib/ruby_ui/sidebar/sidebar_group_content.rb +20 -0
  90. data/lib/ruby_ui/sidebar/sidebar_group_label.rb +26 -0
  91. data/lib/ruby_ui/sidebar/sidebar_header.rb +20 -0
  92. data/lib/ruby_ui/sidebar/sidebar_input.rb +20 -0
  93. data/lib/ruby_ui/sidebar/sidebar_inset.rb +23 -0
  94. data/lib/ruby_ui/sidebar/sidebar_menu.rb +20 -0
  95. data/lib/ruby_ui/sidebar/sidebar_menu_action.rb +48 -0
  96. data/lib/ruby_ui/sidebar/sidebar_menu_badge.rb +30 -0
  97. data/lib/ruby_ui/sidebar/sidebar_menu_button.rb +63 -0
  98. data/lib/ruby_ui/sidebar/sidebar_menu_item.rb +20 -0
  99. data/lib/ruby_ui/sidebar/sidebar_menu_skeleton.rb +36 -0
  100. data/lib/ruby_ui/sidebar/sidebar_menu_sub.rb +24 -0
  101. data/lib/ruby_ui/sidebar/sidebar_menu_sub_button.rb +50 -0
  102. data/lib/ruby_ui/sidebar/sidebar_menu_sub_item.rb +9 -0
  103. data/lib/ruby_ui/sidebar/sidebar_rail.rb +36 -0
  104. data/lib/ruby_ui/sidebar/sidebar_separator.rb +20 -0
  105. data/lib/ruby_ui/sidebar/sidebar_trigger.rb +42 -0
  106. data/lib/ruby_ui/sidebar/sidebar_wrapper.rb +24 -0
  107. data/lib/ruby_ui/skeleton/skeleton_docs.rb +29 -0
  108. data/lib/ruby_ui/switch/switch.rb +12 -2
  109. data/lib/ruby_ui/switch/switch_docs.rb +46 -0
  110. data/lib/ruby_ui/table/table_docs.rb +102 -0
  111. data/lib/ruby_ui/table/table_footer.rb +1 -1
  112. data/lib/ruby_ui/table/table_row.rb +1 -1
  113. data/lib/ruby_ui/tabs/tabs_docs.rb +211 -0
  114. data/lib/ruby_ui/tabs/tabs_trigger.rb +7 -1
  115. data/lib/ruby_ui/textarea/textarea.rb +8 -1
  116. data/lib/ruby_ui/textarea/textarea_docs.rb +54 -0
  117. data/lib/ruby_ui/theme_toggle/theme_toggle_docs.rb +71 -0
  118. data/lib/ruby_ui/tooltip/tooltip_controller.js +5 -4
  119. data/lib/ruby_ui/tooltip/tooltip_docs.rb +52 -0
  120. data/lib/ruby_ui/typography/typography_docs.rb +107 -0
  121. data/lib/ruby_ui.rb +1 -1
  122. metadata +81 -6
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Chart < Views::Base
4
+ def view_template
5
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
6
+ render Docs::Header.new(title: "Chart", description: "Displays information in a visual way.")
7
+
8
+ Heading(level: 2) { "Introduction" }
9
+
10
+ Text do
11
+ plain "RubyUI uses "
12
+ InlineLink(href: "https://www.chartjs.org/") { "Chart.js" }
13
+ plain " to render charts. Chart.js is a free open-source JavaScript library for data visualization, which supports 8 chart types: bar, line, area, pie, bubble, radar, polar, and scatter. If you're unfamiliar with Chart.js. We recommend the "
14
+ InlineLink(href: "https://www.chartjs.org/docs/latest/getting-started/") { "Getting Started guide" }
15
+ plain ". "
16
+ end
17
+
18
+ Heading(level: 2) { "Usage" }
19
+
20
+ render Docs::VisualCodeExample.new(title: "Bar Chart", context: self) do
21
+ <<~RUBY
22
+ options = {
23
+ type: 'bar',
24
+ data: {
25
+ labels: ['Phlex', 'VC', 'ERB'],
26
+ datasets: [{
27
+ label: 'render time (ms)',
28
+ data: [100, 520, 1200],
29
+ }]
30
+ },
31
+ options: {
32
+ indexAxis: 'y',
33
+ scales: {
34
+ y: {
35
+ beginAtZero: true
36
+ }
37
+ },
38
+ },
39
+ }
40
+
41
+ Chart(options: options)
42
+ RUBY
43
+ end
44
+
45
+ render Docs::VisualCodeExample.new(title: "Line Graph", context: self) do
46
+ <<~RUBY
47
+ options = {
48
+ type: 'line',
49
+ data: {
50
+ labels: ['Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
51
+ datasets: [{
52
+ label: 'Github Stars',
53
+ data: [40, 30, 79, 140, 290, 550],
54
+ }]
55
+ },
56
+ options: {
57
+ scales: {
58
+ y: {
59
+ beginAtZero: true
60
+ }
61
+ },
62
+ plugins: {
63
+ legend: {
64
+ display: false
65
+ }
66
+ }
67
+ },
68
+ }
69
+
70
+ Chart(options: options)
71
+ RUBY
72
+ end
73
+
74
+ render Docs::VisualCodeExample.new(title: "Pie Chart", description: "Setting custom background color", context: self) do
75
+ <<~RUBY
76
+ options = {
77
+ type: 'pie',
78
+ data: {
79
+ labels: [
80
+ 'Red',
81
+ 'Blue',
82
+ 'Yellow'
83
+ ],
84
+ datasets: [{
85
+ label: 'My First Dataset',
86
+ data: [300, 50, 100],
87
+ backgroundColor: [
88
+ 'rgb(255, 99, 132)',
89
+ 'rgb(54, 162, 235)',
90
+ 'rgb(255, 205, 86)'
91
+ ],
92
+ hoverOffset: 4
93
+ }]
94
+ },
95
+ }
96
+
97
+ Chart(options: options)
98
+ RUBY
99
+ end
100
+
101
+ render Components::ComponentSetup::Tabs.new(component_name: "Chart")
102
+
103
+ render Docs::ComponentsTable.new(components)
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def components
110
+ [
111
+ ::Docs::ComponentStruct.new(name: "ChartController", source: "https://github.com/PhlexUI/phlex_ui_stimulus/blob/main/controllers/chart_controller.js", built_using: :stimulus),
112
+ ::Docs::ComponentStruct.new(name: "Chart", source: "https://github.com/PhlexUI/phlex_ui/blob/main/lib/phlex_ui/chart.rb", built_using: :phlex)
113
+ ]
114
+ end
115
+ end
@@ -16,7 +16,13 @@ module RubyUI
16
16
  ruby_ui__checkbox_group_target: "checkbox",
17
17
  action: "change->ruby-ui--checkbox-group#onChange change->ruby-ui--form-field#onInput invalid->ruby-ui--form-field#onInvalid"
18
18
  },
19
- class: "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 accent-primary"
19
+ class: [
20
+ "peer h-4 w-4 shrink-0 rounded-sm border-input ring-offset-background accent-primary",
21
+ "disabled:cursor-not-allowed disabled:opacity-50",
22
+ "checked:bg-primary checked:text-primary-foreground dark:checked:bg-secondary checked:text-primary checked:border-primary",
23
+ "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none",
24
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
25
+ ]
20
26
  }
21
27
  end
22
28
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Checkbox < Views::Base
4
+ def view_template
5
+ component = "Checkbox"
6
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
7
+ render Docs::Header.new(title: "Checkbox", description: "A control that allows the user to toggle between checked and not checked.")
8
+
9
+ Heading(level: 2) { "Usage" }
10
+
11
+ render Docs::VisualCodeExample.new(title: "Example", context: self) do
12
+ <<~RUBY
13
+ div(class: 'flex items-center space-x-3') do
14
+ Checkbox(id: 'terms')
15
+ label(for: 'terms', class: 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70') { "Accept terms and conditions" }
16
+ end
17
+ RUBY
18
+ end
19
+
20
+ render Docs::VisualCodeExample.new(title: "Checked", context: self) do
21
+ <<~RUBY
22
+ div(class: "items-top flex space-x-3") do
23
+ Checkbox(id: 'terms1', checked: true)
24
+ div(class: "grid gap-1.5 leading-none") do
25
+ label(
26
+ for: "terms1",
27
+ class:
28
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
29
+ ) { " Accept terms and conditions " }
30
+ p(class: "text-sm text-muted-foreground") { " You agree to our Terms of Service and Privacy Policy." }
31
+ end
32
+ end
33
+ RUBY
34
+ end
35
+
36
+ render Components::ComponentSetup::Tabs.new(component_name: component)
37
+
38
+ render Docs::ComponentsTable.new(component_files(component))
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Clipboard < Views::Base
4
+ def view_template
5
+ component = "Clipboard"
6
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
7
+ render Docs::Header.new(title: "Clipboard", description: "A control to allow you to copy content to the clipboard.")
8
+
9
+ Heading(level: 2) { "Usage" }
10
+
11
+ render Docs::VisualCodeExample.new(title: "Example", context: self) do
12
+ <<~RUBY
13
+ Clipboard(success: "Copied!", error: "Copy failed!", class: "relative", options: {placement: "top"}) do
14
+ ClipboardSource(class: "hidden") { span { "Born rich!!!" } }
15
+
16
+ ClipboardTrigger do
17
+ Link(href: "#", class: "gap-1") do
18
+ Text(size: :small, class: "text-primary") { "Copy the secret of success!!!" }
19
+ end
20
+ end
21
+ end
22
+ RUBY
23
+ end
24
+
25
+ render Components::ComponentSetup::Tabs.new(component_name: component)
26
+
27
+ render Docs::ComponentsTable.new(component_files(component))
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Codeblock < Views::Base
4
+ def view_template
5
+ component = "Codeblock"
6
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
7
+ render Docs::Header.new(title: "Codeblock", description: "A component for displaying highlighted code.")
8
+
9
+ Heading(level: 2) { "Usage" }
10
+
11
+ render Docs::VisualCodeExample.new(title: "With clipboard", context: self) do
12
+ <<~RUBY
13
+ code = <<~CODE
14
+ def hello_world
15
+ puts "Hello, world!"
16
+ end
17
+ CODE
18
+ div(class: 'w-full') do
19
+ Codeblock(code, syntax: :ruby)
20
+ end
21
+ RUBY
22
+ end
23
+
24
+ render Docs::VisualCodeExample.new(title: "Without clipboard", context: self) do
25
+ <<~RUBY
26
+ code = <<~CODE
27
+ def hello_world
28
+ puts "Hello, world!"
29
+ end
30
+ CODE
31
+ div(class: 'w-full') do
32
+ Codeblock(code, syntax: :ruby, clipboard: false)
33
+ end
34
+ RUBY
35
+ end
36
+
37
+ render Docs::VisualCodeExample.new(title: "Custom message", description: "Copy the code to see the message", context: self) do
38
+ <<~RUBY
39
+ code = <<~CODE
40
+ def hello_world
41
+ puts "Hello, world!"
42
+ end
43
+ CODE
44
+ div(class: 'w-full') do
45
+ Codeblock(code, syntax: :ruby, clipboard_success: "Nice one!")
46
+ end
47
+ RUBY
48
+ end
49
+
50
+ render Components::ComponentSetup::Tabs.new(component_name: component)
51
+
52
+ render Docs::ComponentsTable.new(component_files(component))
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Collapsible < Views::Base
4
+ def view_template
5
+ component = "Collapsible"
6
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
7
+ render Docs::Header.new(title: "Collapsible", description: "An interactive component which expands/collapses a panel.")
8
+
9
+ Heading(level: 2) { "Usage" }
10
+
11
+ render Docs::VisualCodeExample.new(title: "Example", context: self) do
12
+ <<~RUBY
13
+ Collapsible do
14
+ div(class: "flex items-center justify-between space-x-4 px-4 py-2") do
15
+ h4(class: "text-sm font-semibold") { " @joeldrapper starred 3 repositories" }
16
+ CollapsibleTrigger do
17
+ Button(variant: :ghost, icon: true) do
18
+ chevron_icon
19
+ span(class: "sr-only") { "Toggle" }
20
+ end
21
+ end
22
+ end
23
+
24
+ div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do
25
+ "phlex-ruby/phlex"
26
+ end
27
+
28
+ CollapsibleContent do
29
+ div(class: 'space-y-2 mt-2') do
30
+ div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do
31
+ "phlex-ruby/phlex-rails"
32
+ end
33
+ div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do
34
+ "ruby-ui/ruby_ui"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ render Docs::VisualCodeExample.new(title: "Open", context: self) do
43
+ <<~RUBY
44
+ Collapsible(open: true) do
45
+ div(class: "flex items-center justify-between space-x-4 px-4 py-2") do
46
+ h4(class: "text-sm font-semibold") { " @joeldrapper starred 3 repositories" }
47
+ CollapsibleTrigger do
48
+ Button(variant: :ghost, icon: true) do
49
+ chevron_icon
50
+ span(class: "sr-only") { "Toggle" }
51
+ end
52
+ end
53
+ end
54
+
55
+ div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do
56
+ "phlex-ruby/phlex"
57
+ end
58
+
59
+ CollapsibleContent do
60
+ div(class: 'space-y-2 mt-2') do
61
+ div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do
62
+ "phlex-ruby/phlex-rails"
63
+ end
64
+ div(class: "rounded-md border px-4 py-2 font-mono text-sm shadow-sm") do
65
+ "ruby-ui/ruby_ui"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ RUBY
71
+ end
72
+
73
+ render Components::ComponentSetup::Tabs.new(component_name: component)
74
+
75
+ render Docs::ComponentsTable.new(component_files(component))
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def chevron_icon
82
+ svg(
83
+ xmlns: "http://www.w3.org/2000/svg",
84
+ viewbox: "0 0 20 20",
85
+ fill: "currentColor",
86
+ class: "w-4 h-4"
87
+ ) do |s|
88
+ s.path(
89
+ fill_rule: "evenodd",
90
+ d:
91
+ "M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z",
92
+ clip_rule: "evenodd"
93
+ )
94
+ end
95
+ end
96
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module RubyUI
4
4
  class Combobox < Base
5
- def initialize(term: "items", **)
5
+ def initialize(term: nil, **)
6
6
  @term = term
7
7
  super(**)
8
8
  end
@@ -18,7 +18,8 @@ module RubyUI
18
18
  role: "combobox",
19
19
  data: {
20
20
  controller: "ruby-ui--combobox",
21
- ruby_ui__combobox_term_value: @term.to_s
21
+ ruby_ui__combobox_term_value: @term,
22
+ action: "turbo:morph@window->ruby-ui--combobox#updateTriggerContent"
22
23
  }
23
24
  }
24
25
  end
@@ -12,8 +12,10 @@ module RubyUI
12
12
  {
13
13
  class: [
14
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"
15
+ "disabled:cursor-not-allowed disabled:opacity-50",
16
+ "checked:bg-primary checked:text-primary-foreground",
17
+ "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none",
18
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
17
19
  ],
18
20
  data: {
19
21
  ruby_ui__combobox_target: "input",
@@ -28,6 +28,11 @@ export default class extends Controller {
28
28
  if (this.cleanup) { this.cleanup() }
29
29
  }
30
30
 
31
+ handlePopoverToggle(event) {
32
+ // Keep ariaExpanded in sync with the actual popover state
33
+ this.triggerTarget.ariaExpanded = event.newState === 'open' ? 'true' : 'false'
34
+ }
35
+
31
36
  inputChanged(e) {
32
37
  this.updateTriggerContent()
33
38
 
@@ -53,18 +58,28 @@ export default class extends Controller {
53
58
  updateTriggerContent() {
54
59
  const checkedInputs = this.inputTargets.filter(input => input.checked)
55
60
 
56
- if (checkedInputs.length == 0) {
61
+ if (checkedInputs.length === 0) {
57
62
  this.triggerContentTarget.innerText = this.triggerTarget.dataset.placeholder
58
- } else if (checkedInputs.length === 1) {
59
- this.triggerContentTarget.innerText = this.inputContent(checkedInputs[0])
60
- } else {
63
+ } else if (this.termValue && checkedInputs.length > 1) {
61
64
  this.triggerContentTarget.innerText = `${checkedInputs.length} ${this.termValue}`
65
+ } else {
66
+ this.triggerContentTarget.innerText = checkedInputs.map((input) => this.inputContent(input)).join(", ")
62
67
  }
63
68
  }
64
69
 
65
- openPopover(event) {
70
+ togglePopover(event) {
66
71
  event.preventDefault()
67
72
 
73
+ if (this.triggerTarget.ariaExpanded === "true") {
74
+ this.closePopover()
75
+ } else {
76
+ this.openPopover(event)
77
+ }
78
+ }
79
+
80
+ openPopover(event) {
81
+ if (event) event.preventDefault()
82
+
68
83
  this.updatePopoverPosition()
69
84
  this.updatePopoverWidth()
70
85
  this.triggerTarget.ariaExpanded = "true"
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Combobox < Views::Base
4
+ @@code_example = nil
5
+
6
+ def view_template
7
+ component = "Combobox"
8
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
9
+ render Docs::Header.new(title: component, description: "Autocomplete input and command palette with a list of suggestions.")
10
+
11
+ Heading(level: 2) { "Usage" }
12
+
13
+ render Docs::VisualCodeExample.new(title: "Single option", context: self) do
14
+ <<~RUBY
15
+ div class: "w-96" do
16
+ Combobox do
17
+ ComboboxTrigger placeholder: "Pick value"
18
+
19
+ ComboboxPopover do
20
+ ComboboxSearchInput(placeholder: "Pick value or type anything")
21
+
22
+ ComboboxList do
23
+ ComboboxEmptyState { "No result" }
24
+
25
+ ComboboxListGroup(label: "Fruits") do
26
+ ComboboxItem do
27
+ ComboboxRadio(name: "food", value: "apple")
28
+ span { "Apple" }
29
+ end
30
+
31
+ ComboboxItem do
32
+ ComboboxRadio(name: "food", value: "banana")
33
+ span { "Banana" }
34
+ end
35
+ end
36
+
37
+ ComboboxListGroup(label: "Vegetable") do
38
+ ComboboxItem do
39
+ ComboboxRadio(name: "food", value: "brocoli")
40
+ span { "Broccoli" }
41
+ end
42
+
43
+ ComboboxItem do
44
+ ComboboxRadio(name: "food", value: "carrot")
45
+ span { "Carrot" }
46
+ end
47
+ end
48
+
49
+ ComboboxListGroup(label: "Others") do
50
+ ComboboxItem do
51
+ ComboboxRadio(name: "food", value: "chocolate")
52
+ span { "Chocolate" }
53
+ end
54
+
55
+ ComboboxItem do
56
+ ComboboxRadio(name: "food", value: "milk")
57
+ span { "Milk" }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ RUBY
65
+ end
66
+
67
+ render Docs::VisualCodeExample.new(title: "Multiple options", context: self) do
68
+ <<~RUBY
69
+ div class: "w-96" do
70
+ Combobox term: "things" do
71
+ ComboboxTrigger placeholder: "Pick value"
72
+
73
+ ComboboxPopover do
74
+ ComboboxSearchInput(placeholder: "Pick value or type anything")
75
+
76
+ ComboboxList do
77
+ ComboboxEmptyState { "No result" }
78
+
79
+ ComboboxItem(class: "mt-3") do
80
+ ComboboxToggleAllCheckbox(name: "all", value: "all")
81
+ span { "Select all" }
82
+ end
83
+
84
+ ComboboxListGroup label: "Fruits" do
85
+ ComboboxItem do
86
+ ComboboxCheckbox(name: "food", value: "apple")
87
+ span { "Apple" }
88
+ end
89
+
90
+ ComboboxItem do
91
+ ComboboxCheckbox(name: "food", value: "banana")
92
+ span { "Banana" }
93
+ end
94
+ end
95
+
96
+ ComboboxListGroup label: "Vegetable" do
97
+ ComboboxItem do
98
+ ComboboxCheckbox(name: "food", value: "brocoli")
99
+ span { "Broccoli" }
100
+ end
101
+
102
+ ComboboxItem do
103
+ ComboboxCheckbox(name: "food", value: "carrot")
104
+ span { "Carrot" }
105
+ end
106
+ end
107
+
108
+ ComboboxListGroup label: "Others" do
109
+ ComboboxItem do
110
+ ComboboxCheckbox(name: "food", value: "chocolate")
111
+ span { "Chocolate" }
112
+ end
113
+
114
+ ComboboxItem do
115
+ ComboboxCheckbox(name: "food", value: "milk")
116
+ span { "Milk" }
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ RUBY
124
+ end
125
+
126
+ render Docs::VisualCodeExample.new(title: "Disabled", context: self) do
127
+ <<~RUBY
128
+ div(class: "w-96") do
129
+ Combobox do
130
+ ComboboxTrigger(disabled: true, placeholder: "Pick value")
131
+ end
132
+ end
133
+ RUBY
134
+ end
135
+
136
+ render Docs::VisualCodeExample.new(title: "Aria Disabled", context: self) do
137
+ <<~RUBY
138
+ div(class: "w-96") do
139
+ Combobox do
140
+ ComboboxTrigger(aria: {disabled: "true"}, placeholder: "Pick value")
141
+ end
142
+ end
143
+ RUBY
144
+ end
145
+
146
+ render Components::ComponentSetup::Tabs.new(component_name: "Combobox")
147
+
148
+ render Docs::ComponentsTable.new(component_files("Combobox"))
149
+ end
150
+ end
151
+ end
@@ -13,7 +13,8 @@ module RubyUI
13
13
  class: [
14
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
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"
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"
17
18
  ],
18
19
  role: "option",
19
20
  data: {
@@ -17,10 +17,11 @@ module RubyUI
17
17
  data: {
18
18
  ruby_ui__combobox_target: "popover",
19
19
  action: %w[
20
+ toggle->ruby-ui--combobox#handlePopoverToggle
20
21
  keydown.down->ruby-ui--combobox#keyDownPressed
21
22
  keydown.up->ruby-ui--combobox#keyUpPressed
22
23
  keydown.enter->ruby-ui--combobox#keyEnterPressed
23
- keydown.esc->ruby-ui--combobox#closeDialog:prevent
24
+ keydown.esc->ruby-ui--combobox#closePopover:prevent
24
25
  resize@window->ruby-ui--combobox#updatePopoverWidth
25
26
  ]
26
27
  }
@@ -10,7 +10,14 @@ module RubyUI
10
10
 
11
11
  def default_attrs
12
12
  {
13
- class: "aspect-square h-4 w-4 rounded-full border border-primary accent-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
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
+ ],
14
21
  data: {
15
22
  ruby_ui__combobox_target: "input",
16
23
  ruby_ui__form_field_target: "input",
@@ -19,16 +19,22 @@ module RubyUI
19
19
  def default_attrs
20
20
  {
21
21
  type: "search",
22
- class: "flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none border-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
23
22
  role: "searchbox",
23
+ autocorrect: "off",
24
+ autocomplete: "off",
25
+ spellcheck: "false",
24
26
  placeholder: @placeholder,
27
+ class: [
28
+ "flex h-9 w-full rounded-md bg-transparent py-3 text-sm outline-none border-none",
29
+ "focus:ring-0",
30
+ "placeholder:text-muted-foreground",
31
+ "disabled:cursor-not-allowed disabled:opacity-50",
32
+ "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none"
33
+ ],
25
34
  data: {
26
35
  ruby_ui__combobox_target: "searchInput",
27
36
  action: "keyup->ruby-ui--combobox#filterItems search->ruby-ui--combobox#filterItems"
28
- },
29
- autocomplete: "off",
30
- autocorrect: "off",
31
- spellcheck: "false"
37
+ }
32
38
  }
33
39
  end
34
40
 
@@ -12,8 +12,10 @@ module RubyUI
12
12
  {
13
13
  class: [
14
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"
15
+ "disabled:cursor-not-allowed disabled:opacity-50",
16
+ "checked:bg-primary checked:text-primary-foreground",
17
+ "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:pointer-events-none",
18
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
17
19
  ],
18
20
  data: {
19
21
  ruby_ui__combobox_target: "toggleAll",