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,67 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ const SIDEBAR_COOKIE_NAME = "sidebar_state";
4
+ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
5
+ const State = {
6
+ EXPANDED: "expanded",
7
+ COLLAPSED: "collapsed",
8
+ };
9
+ const MOBILE_BREAKPOINT = 768;
10
+
11
+ export default class extends Controller {
12
+ static targets = ["sidebar", "mobileSidebar"];
13
+
14
+ sidebarTargetConnected() {
15
+ const { state, collapsibleKind } = this.sidebarTarget.dataset;
16
+
17
+ this.open = state === State.EXPANDED;
18
+ this.collapsibleKind = collapsibleKind;
19
+ }
20
+
21
+ toggle(e) {
22
+ e.preventDefault();
23
+
24
+ if (this.#isMobile()) {
25
+ this.#openMobileSidebar();
26
+
27
+ return;
28
+ }
29
+
30
+ this.open = !this.open;
31
+ this.onToggle();
32
+ }
33
+
34
+ onToggle() {
35
+ this.#updateSidebarState();
36
+ this.#persistSidebarState();
37
+ }
38
+
39
+ #updateSidebarState() {
40
+ if (!this.hasSidebarTarget) {
41
+ return;
42
+ }
43
+
44
+ const { dataset } = this.sidebarTarget;
45
+
46
+ dataset.state = this.open ? State.EXPANDED : State.COLLAPSED;
47
+ dataset.collapsible = this.open ? "" : this.collapsibleKind;
48
+ }
49
+
50
+ #persistSidebarState() {
51
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${this.open}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
52
+ }
53
+
54
+ #isMobile() {
55
+ return window.innerWidth < MOBILE_BREAKPOINT;
56
+ }
57
+
58
+ #openMobileSidebar() {
59
+ if (!this.hasMobileSidebarTarget) {
60
+ return;
61
+ }
62
+
63
+ this.mobileSidebarTarget.dispatchEvent(
64
+ new CustomEvent("ruby--ui-sidebar:open"),
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Sidebar < Views::Base
4
+ def view_template
5
+ component = "Sidebar"
6
+
7
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
8
+ render Docs::Header.new(title: "Sidebar", description: "A composable, themeable and customizable sidebar component.")
9
+
10
+ Heading(level: 2) { "Usage" }
11
+
12
+ Alert do
13
+ info_icon
14
+ AlertTitle { "Requirements" }
15
+ AlertDescription { "The sidebar component depends on the following components:" }
16
+ ul(class: "list-disc list-inside") do
17
+ li do
18
+ InlineLink(href: docs_sheet_path, target: "_blank", class: "inline-flex items-center gap-2") do
19
+ span { "Sheet" }
20
+ external_icon_link
21
+ end
22
+ end
23
+ li do
24
+ div(class: "inline-flex items-center gap-2") do
25
+ InlineLink(href: docs_separator_path, target: "_blank") { "Separator" }
26
+ external_icon_link
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ render Docs::VisualCodeExample.new(title: "Example", src: "/docs/sidebar/example", context: self) do
33
+ Views::Docs::Sidebar::Example::CODE
34
+ end
35
+
36
+ render Docs::VisualCodeExample.new(title: "Inset variant", src: "/docs/sidebar/inset", context: self) do
37
+ Views::Docs::Sidebar::InsetExample::CODE
38
+ end
39
+
40
+ render Docs::VisualCodeExample.new(title: "Dialog variant", context: self) do
41
+ <<~RUBY
42
+ Dialog(data: {action: "ruby-ui--dialog:connect->ruby-ui--dialog#open"}) do
43
+ DialogTrigger do
44
+ Button { "Open Dialog" }
45
+ end
46
+ DialogContent(class: "grid overflow-hidden p-0 md:max-h-[500px] md:max-w-[700px] lg:max-w-[800px]") do
47
+ SidebarWrapper(class: "items-start") do
48
+ Sidebar(collapsible: :none, class: "hidden md:flex") do
49
+ SidebarContent do
50
+ SidebarGroup do
51
+ SidebarGroupContent do
52
+ SidebarMenu do
53
+ SidebarMenuItem do
54
+ SidebarMenuButton(as: :a, href: "#") do
55
+ search_icon()
56
+ span { "Search" }
57
+ end
58
+ end
59
+ SidebarMenuItem do
60
+ SidebarMenuButton(as: :a, href: "#", active: true) do
61
+ home_icon()
62
+ span { "Home" }
63
+ end
64
+ end
65
+ SidebarMenuItem do
66
+ SidebarMenuButton(as: :a, href: "#") do
67
+ inbox_icon()
68
+ span { "Inbox" }
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ main(class: "flex h-[480px] flex-1 flex-col overflow-hidden") do
77
+ end
78
+ end
79
+ end
80
+ end
81
+ RUBY
82
+ end
83
+
84
+ render Components::ComponentSetup::Tabs.new(component_name: component)
85
+
86
+ render Docs::ComponentsTable.new(component_files(component))
87
+ end
88
+ end
89
+
90
+ def search_icon
91
+ svg(
92
+ xmlns: "http://www.w3.org/2000/svg",
93
+ width: "24",
94
+ height: "24",
95
+ viewBox: "0 0 24 24",
96
+ fill: "none",
97
+ stroke: "currentColor",
98
+ stroke_width: "2",
99
+ stroke_linecap: "round",
100
+ stroke_linejoin: "round",
101
+ class: "lucide lucide-search"
102
+ ) do |s|
103
+ s.circle(cx: "11", cy: "11", r: "8")
104
+ s.path(d: "M21 21L16.7 16.7")
105
+ end
106
+ end
107
+
108
+ def home_icon
109
+ svg(
110
+ xmlns: "http://www.w3.org/2000/svg",
111
+ width: "24",
112
+ height: "24",
113
+ viewBox: "0 0 24 24",
114
+ fill: "none",
115
+ stroke: "currentColor",
116
+ stroke_width: "2",
117
+ stroke_linecap: "round",
118
+ stroke_linejoin: "round",
119
+ class: "lucide lucide-house"
120
+ ) do |s|
121
+ s.path(d: "M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8")
122
+ s.path(d: "M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z")
123
+ end
124
+ end
125
+
126
+ def inbox_icon
127
+ svg(
128
+ xmlns: "http://www.w3.org/2000/svg",
129
+ width: "24",
130
+ height: "24",
131
+ viewBox: "0 0 24 24",
132
+ fill: "none",
133
+ stroke: "currentColor",
134
+ stroke_width: "2",
135
+ stroke_linecap: "round",
136
+ stroke_linejoin: "round",
137
+ class: "lucide lucide-inbox"
138
+ ) do |s|
139
+ s.polyline(points: "22 12 16 12 14 15 10 15 8 12 2 12")
140
+ s.path(d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z")
141
+ end
142
+ end
143
+
144
+ def external_icon_link
145
+ svg(
146
+ xmlns: "http://www.w3.org/2000/svg",
147
+ viewBox: "0 0 24 24",
148
+ fill: "none",
149
+ stroke: "currentColor",
150
+ stroke_width: "2",
151
+ stroke_linecap: "round",
152
+ stroke_linejoin: "round",
153
+ class: "lucide lucide-external-link-icon lucide-external-link size-3"
154
+ ) do |s|
155
+ s.path(d: "M15 3h6v6")
156
+ s.path(d: "M10 14 21 3")
157
+ s.path(d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6")
158
+ end
159
+ end
160
+
161
+ def info_icon
162
+ svg(
163
+ xmlns: "http://www.w3.org/2000/svg",
164
+ viewbox: "0 0 24 24",
165
+ fill: "currentColor",
166
+ class: "w-5 h-5"
167
+ ) do |s|
168
+ s.path(
169
+ fill_rule: "evenodd",
170
+ d:
171
+ "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z",
172
+ clip_rule: "evenodd"
173
+ )
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarFooter < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "flex flex-col gap-2 p-2",
14
+ data: {
15
+ sidebar: "footer"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarGroup < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "relative flex w-full min-w-0 flex-col p-2",
14
+ data: {
15
+ sidebar: "group"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarGroupAction < Base
5
+ def initialize(as: :button, **attrs)
6
+ @as = as
7
+ super(**attrs)
8
+ end
9
+
10
+ def view_template(&)
11
+ tag(@as, **attrs, &)
12
+ end
13
+
14
+ private
15
+
16
+ def default_attrs
17
+ {
18
+ class: [
19
+ "absolute right-3 top-3.5 flex aspect-square w-5 items-center",
20
+ "justify-center rounded-md p-0 text-sidebar-foreground",
21
+ "outline-none ring-sidebar-ring transition-transform",
22
+ "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
23
+ "focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
24
+ "after:absolute after:-inset-2 after:md:hidden",
25
+ "group-data-[collapsible=icon]:hidden"
26
+ ],
27
+ data: {
28
+ sidebar: "group-action"
29
+ }
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarGroupContent < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "w-full text-sm",
14
+ data: {
15
+ sidebar: "group-content"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarGroupLabel < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: [
14
+ "flex h-8 shrink-0 items-center rounded-md px-2 text-xs",
15
+ "font-medium text-sidebar-foreground/70 outline-none",
16
+ "ring-sidebar-ring transition-[margin,opacity] duration-200",
17
+ "ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
18
+ "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0"
19
+ ],
20
+ data: {
21
+ sidebar: "group-label"
22
+ }
23
+ }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarHeader < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "flex flex-col gap-2 p-2",
14
+ data: {
15
+ sidebar: "header"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarInput < Base
5
+ def view_template(&)
6
+ Input(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
14
+ data: {
15
+ sidebar: "input"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarInset < Base
5
+ def view_template(&)
6
+ main(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: [
14
+ "relative flex w-full flex-1 flex-col bg-background",
15
+ "md:peer-data-[variant=inset]:m-2",
16
+ "md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2",
17
+ "md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl",
18
+ "md:peer-data-[variant=inset]:shadow"
19
+ ]
20
+ }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenu < Base
5
+ def view_template(&)
6
+ ul(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "flex w-full min-w-0 flex-col gap-1",
14
+ data: {
15
+ sidebar: "menu"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenuAction < Base
5
+ def initialize(as: :button, show_on_hover: false, **attrs)
6
+ @as = as
7
+ super(**attrs)
8
+ end
9
+
10
+ def view_template(&)
11
+ tag(@as, **attrs, &)
12
+ end
13
+
14
+ private
15
+
16
+ def default_attrs
17
+ {
18
+ class: [
19
+ "absolute right-1 top-1.5 flex aspect-square w-5 items-center",
20
+ "justify-center rounded-md p-0 text-sidebar-foreground outline-none",
21
+ "ring-sidebar-ring transition-transform hover:bg-sidebar-accent",
22
+ "hover:text-sidebar-accent-foreground focus-visible:ring-2",
23
+ "peer-hover/menu-button:text-sidebar-accent-foreground",
24
+ "[&>svg]:size-4 [&>svg]:shrink-0",
25
+ "after:absolute after:-inset-2 after:md:hidden",
26
+ "peer-data-[size=sm]/menu-button:top-1",
27
+ "peer-data-[size=default]/menu-button:top-1.5",
28
+ "peer-data-[size=lg]/menu-button:top-2.5",
29
+ "group-data-[collapsible=icon]:hidden",
30
+ show_on_hover_classes
31
+ ],
32
+ data: {
33
+ sidebar: "menu-action"
34
+ }
35
+ }
36
+ end
37
+
38
+ def show_on_hover_classes
39
+ return unless @show_on_hover
40
+
41
+ [
42
+ "group-focus-within/menu-item:opacity-100",
43
+ "group-hover/menu-item:opacity-100 data-[state=open]:opacity-100",
44
+ "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0"
45
+ ].join(" ")
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenuBadge < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: [
14
+ "pointer-events-none absolute right-1 flex h-5 min-w-5 select-none",
15
+ "items-center justify-center rounded-md px-1 text-xs font-medium",
16
+ "tabular-nums text-sidebar-foreground",
17
+ "peer-hover/menu-button:text-sidebar-accent-foreground",
18
+ "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
19
+ "peer-data-[size=sm]/menu-button:top-1",
20
+ "peer-data-[size=default]/menu-button:top-1.5",
21
+ "peer-data-[size=lg]/menu-button:top-2.5",
22
+ "group-data-[collapsible=icon]:hidden"
23
+ ],
24
+ data: {
25
+ sidebar: "menu-badge"
26
+ }
27
+ }
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenuButton < Base
5
+ VARIANT_CLASSES = {
6
+ default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
7
+ outline:
8
+ "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]"
9
+ }.freeze
10
+
11
+ SIZE_CLASSES = {
12
+ default: "h-8 text-sm",
13
+ sm: "h-7 text-xs",
14
+ lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0"
15
+ }.freeze
16
+
17
+ def initialize(as: :button, variant: :default, size: :default, active: false, **attrs)
18
+ raise ArgumentError, "Invalid variant: #{variant}" unless VARIANT_CLASSES.key?(variant)
19
+ raise ArgumentError, "Invalid size: #{size}" unless SIZE_CLASSES.key?(size)
20
+
21
+ @as = as
22
+ @variant = variant
23
+ @size = size
24
+ @active = active
25
+ super(**attrs)
26
+ end
27
+
28
+ def view_template(&)
29
+ tag(@as, **attrs, &)
30
+ end
31
+
32
+ private
33
+
34
+ def default_attrs
35
+ {
36
+ class: [
37
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden",
38
+ "rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring",
39
+ "transition-[width,height,padding] hover:bg-sidebar-accent",
40
+ "hover:text-sidebar-accent-foreground focus-visible:ring-2",
41
+ "active:bg-sidebar-accent active:text-sidebar-accent-foreground",
42
+ "disabled:pointer-events-none disabled:opacity-50",
43
+ "group-has-[[data-sidebar=menu-action]]/menu-item:pr-8",
44
+ "aria-disabled:pointer-events-none aria-disabled:opacity-50",
45
+ "data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium",
46
+ "data-[active=true]:text-sidebar-accent-foreground",
47
+ "data-[state=open]:hover:bg-sidebar-accent",
48
+ "data-[state=open]:hover:text-sidebar-accent-foreground",
49
+ "group-data-[collapsible=icon]:!size-8",
50
+ "group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate",
51
+ "[&>svg]:size-4 [&>svg]:shrink-0",
52
+ VARIANT_CLASSES[@variant],
53
+ SIZE_CLASSES[@size]
54
+ ],
55
+ data: {
56
+ sidebar: "menu-button",
57
+ size: @size,
58
+ active: @active.to_s
59
+ }
60
+ }
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenuItem < Base
5
+ def view_template(&)
6
+ ul(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: "group/menu-item relative",
14
+ data: {
15
+ sidebar: "menu-item"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenuSkeleton < Base
5
+ def initialize(show_icon: false, **attrs)
6
+ @show_icon = show_icon
7
+ super(**attrs)
8
+ end
9
+
10
+ def view_template(&)
11
+ div(**attrs) do
12
+ Skeleton(class: "size-4 rounded-md", data: {sidebar: "menu-skeleton-icon"}) if @show_icon
13
+ Skeleton(
14
+ class: "h-4 max-w-[var(--skeleton-width)] flex-1",
15
+ data: {sidebar: "menu-skeleton-text"},
16
+ style: {"--skeleton-width" => "#{skeleton_width}%"}
17
+ )
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def default_attrs
24
+ {
25
+ class: "flex h-8 items-center gap-2 rounded-md px-2",
26
+ data: {
27
+ sidebar: "menu-skeleton"
28
+ }
29
+ }
30
+ end
31
+
32
+ def skeleton_width
33
+ @_skeleton_width ||= rand(50..89)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class SidebarMenuSub < Base
5
+ def view_template(&)
6
+ ul(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ class: [
14
+ "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l",
15
+ "border-sidebar-border px-2.5 py-0.5",
16
+ "group-data-[collapsible=icon]:hidden"
17
+ ],
18
+ data: {
19
+ sidebar: "menu-sub"
20
+ }
21
+ }
22
+ end
23
+ end
24
+ end