ruby_ui 1.4.0 → 1.6.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/ruby_ui/dependencies.yml +12 -2
  3. data/lib/generators/ruby_ui/javascript_utils.rb +0 -13
  4. data/lib/ruby_ui/accordion/accordion_controller.js +16 -9
  5. data/lib/ruby_ui/bubble/bubble.rb +37 -0
  6. data/lib/ruby_ui/bubble/bubble_content.rb +23 -0
  7. data/lib/ruby_ui/bubble/bubble_docs.rb +167 -0
  8. data/lib/ruby_ui/bubble/bubble_group.rb +18 -0
  9. data/lib/ruby_ui/bubble/bubble_reactions.rb +38 -0
  10. data/lib/ruby_ui/combobox/combobox.rb +3 -1
  11. data/lib/ruby_ui/combobox/combobox_checkbox.rb +8 -1
  12. data/lib/ruby_ui/combobox/combobox_controller.js +3 -2
  13. data/lib/ruby_ui/combobox/combobox_trigger.rb +1 -4
  14. data/lib/ruby_ui/context_menu/context_menu_content.rb +5 -6
  15. data/lib/ruby_ui/context_menu/context_menu_controller.js +87 -75
  16. data/lib/ruby_ui/data_table/data_table_column_toggle.rb +8 -4
  17. data/lib/ruby_ui/data_table/data_table_column_visibility_controller.js +12 -2
  18. data/lib/ruby_ui/dialog/dialog_content.rb +1 -1
  19. data/lib/ruby_ui/dropdown_menu/dropdown_menu.rb +0 -1
  20. data/lib/ruby_ui/dropdown_menu/dropdown_menu_controller.js +5 -1
  21. data/lib/ruby_ui/dropdown_menu/dropdown_menu_docs.rb +1 -1
  22. data/lib/ruby_ui/empty/empty.rb +18 -0
  23. data/lib/ruby_ui/empty/empty_content.rb +18 -0
  24. data/lib/ruby_ui/empty/empty_description.rb +18 -0
  25. data/lib/ruby_ui/empty/empty_docs.rb +69 -0
  26. data/lib/ruby_ui/empty/empty_header.rb +18 -0
  27. data/lib/ruby_ui/empty/empty_media.rb +31 -0
  28. data/lib/ruby_ui/empty/empty_title.rb +18 -0
  29. data/lib/ruby_ui/hover_card/hover_card_content.rb +5 -6
  30. data/lib/ruby_ui/hover_card/hover_card_controller.js +113 -76
  31. data/lib/ruby_ui/input_otp/input_otp.rb +42 -0
  32. data/lib/ruby_ui/input_otp/input_otp_controller.js +128 -0
  33. data/lib/ruby_ui/input_otp/input_otp_docs.rb +213 -0
  34. data/lib/ruby_ui/input_otp/input_otp_group.rb +15 -0
  35. data/lib/ruby_ui/input_otp/input_otp_separator.rb +39 -0
  36. data/lib/ruby_ui/input_otp/input_otp_slot.rb +33 -0
  37. data/lib/ruby_ui/message/message.rb +23 -0
  38. data/lib/ruby_ui/message/message_avatar.rb +18 -0
  39. data/lib/ruby_ui/message/message_content.rb +18 -0
  40. data/lib/ruby_ui/message/message_docs.rb +173 -0
  41. data/lib/ruby_ui/message/message_footer.rb +18 -0
  42. data/lib/ruby_ui/message/message_group.rb +18 -0
  43. data/lib/ruby_ui/message/message_header.rb +18 -0
  44. data/lib/ruby_ui/message_scroller/message_scroller.rb +18 -0
  45. data/lib/ruby_ui/message_scroller/message_scroller_button.rb +56 -0
  46. data/lib/ruby_ui/message_scroller/message_scroller_content.rb +23 -0
  47. data/lib/ruby_ui/message_scroller/message_scroller_controller.js +335 -0
  48. data/lib/ruby_ui/message_scroller/message_scroller_docs.rb +208 -0
  49. data/lib/ruby_ui/message_scroller/message_scroller_item.rb +28 -0
  50. data/lib/ruby_ui/message_scroller/message_scroller_provider.rb +34 -0
  51. data/lib/ruby_ui/message_scroller/message_scroller_viewport.rb +22 -0
  52. data/lib/ruby_ui/native_select/native_select_icon.rb +1 -1
  53. data/lib/ruby_ui/popover/popover_content.rb +2 -1
  54. data/lib/ruby_ui/popover/popover_controller.js +95 -32
  55. data/lib/ruby_ui/toast/toaster_controller.js +24 -8
  56. data/lib/ruby_ui.rb +1 -1
  57. metadata +34 -1
@@ -1,106 +1,125 @@
1
1
  import { Controller } from "@hotwired/stimulus";
2
- import tippy from "tippy.js";
2
+ import {
3
+ computePosition,
4
+ flip,
5
+ shift,
6
+ offset,
7
+ autoUpdate,
8
+ } from "@floating-ui/dom";
3
9
 
4
10
  export default class extends Controller {
5
11
  static targets = ["trigger", "content", "menuItem"];
6
12
  static values = {
7
- options: {
8
- type: Object,
9
- default: {},
10
- },
11
- // make content width of the trigger element (true/false)
12
- matchWidth: {
13
- type: Boolean,
14
- default: false,
15
- }
16
- }
13
+ open: { type: Boolean, default: false },
14
+ options: { type: Object, default: {} },
15
+ // make content width match the trigger element (true/false)
16
+ matchWidth: { type: Boolean, default: false },
17
+ };
17
18
 
18
19
  connect() {
19
- this.boundHandleKeydown = this.handleKeydown.bind(this); // Bind the function so we can remove it later
20
- this.initializeTippy();
20
+ this.cleanup = null;
21
21
  this.selectedIndex = -1;
22
+ this.boundHandleKeydown = this.handleKeydown.bind(this);
22
23
  }
23
24
 
24
25
  disconnect() {
25
- this.destroyTippy();
26
+ this.hide();
26
27
  }
27
28
 
28
- initializeTippy() {
29
- const defaultOptions = {
30
- content: this.contentTarget.innerHTML,
31
- allowHTML: true,
32
- interactive: true,
33
- onShow: (instance) => {
34
- this.matchWidthValue && this.setContentWidth(instance); // ensure content width matches trigger width
35
- this.addEventListeners();
36
- },
37
- onHide: () => {
38
- this.removeEventListeners();
39
- this.deselectAll();
40
- },
41
- popperOptions: {
42
- modifiers: [
43
- {
44
- name: "offset",
45
- options: {
46
- offset: [0, 4]
47
- },
48
- },
49
- ],
50
- }
51
- };
52
-
53
- const mergedOptions = { ...this.optionsValue, ...defaultOptions };
54
- this.tippy = tippy(this.triggerTarget, mergedOptions);
29
+ handleContextMenu(event) {
30
+ event.preventDefault();
31
+ this.open();
55
32
  }
56
33
 
57
- destroyTippy() {
58
- if (this.tippy) {
59
- this.tippy.destroy();
34
+ open() {
35
+ this.openValue = true;
36
+ this.contentTarget.classList.remove("hidden");
37
+ this.contentTarget.dataset.state = "open";
38
+ if (this.matchWidthValue) {
39
+ this.contentTarget.style.width = `${this.triggerTarget.offsetWidth}px`;
60
40
  }
41
+ this.addEventListeners();
42
+ this.updatePosition();
61
43
  }
62
44
 
63
- setContentWidth(instance) {
64
- // box-sizing: border-box
65
- const content = instance.popper.querySelector('.tippy-content');
66
- if (content) {
67
- content.style.width = `${instance.reference.offsetWidth}px`;
45
+ close() {
46
+ this.hide();
47
+ }
48
+
49
+ hide() {
50
+ if (!this.openValue) return;
51
+ this.openValue = false;
52
+ this.contentTarget.classList.add("hidden");
53
+ this.contentTarget.dataset.state = "closed";
54
+ this.removeEventListeners();
55
+ this.deselectAll();
56
+ if (this.cleanup) {
57
+ this.cleanup();
58
+ this.cleanup = null;
68
59
  }
69
60
  }
70
61
 
71
- handleContextMenu(event) {
72
- event.preventDefault();
73
- this.open();
62
+ updatePosition() {
63
+ if (this.cleanup) this.cleanup();
64
+
65
+ this.cleanup = autoUpdate(this.triggerTarget, this.contentTarget, () => {
66
+ computePosition(this.triggerTarget, this.contentTarget, {
67
+ placement: this.optionsValue.placement || "bottom-start",
68
+ middleware: [offset(4), flip(), shift({ padding: 8 })],
69
+ }).then(({ x, y, placement }) => {
70
+ Object.assign(this.contentTarget.style, {
71
+ left: `${x}px`,
72
+ top: `${y}px`,
73
+ });
74
+ this.contentTarget.dataset.side = placement.split("-")[0];
75
+ });
76
+ });
74
77
  }
75
78
 
76
- open() {
77
- this.tippy.show();
79
+ addEventListeners() {
80
+ document.addEventListener("keydown", this.boundHandleKeydown);
81
+ document.addEventListener("click", this.handleOutsidePointer);
82
+ // A right-click outside should dismiss this menu and let the native
83
+ // context menu (or another trigger's menu) take over.
84
+ document.addEventListener("contextmenu", this.handleOutsidePointer);
78
85
  }
79
86
 
80
- close() {
81
- this.tippy.hide();
87
+ removeEventListeners() {
88
+ document.removeEventListener("keydown", this.boundHandleKeydown);
89
+ document.removeEventListener("click", this.handleOutsidePointer);
90
+ document.removeEventListener("contextmenu", this.handleOutsidePointer);
82
91
  }
83
92
 
93
+ handleOutsidePointer = (event) => {
94
+ if (!this.element.contains(event.target)) {
95
+ this.hide();
96
+ }
97
+ };
98
+
84
99
  handleKeydown(e) {
85
- // return if no menu items (one line fix for)
86
- if (this.menuItemTargets.length === 0) { return; }
100
+ if (e.key === "Escape") {
101
+ e.preventDefault();
102
+ this.hide();
103
+ return;
104
+ }
87
105
 
88
- if (e.key === 'ArrowDown') {
106
+ if (this.menuItemTargets.length === 0) return;
107
+
108
+ if (e.key === "ArrowDown") {
89
109
  e.preventDefault();
90
110
  this.updateSelectedItem(1);
91
- } else if (e.key === 'ArrowUp') {
111
+ } else if (e.key === "ArrowUp") {
92
112
  e.preventDefault();
93
113
  this.updateSelectedItem(-1);
94
- } else if (e.key === 'Enter' && this.selectedIndex !== -1) {
114
+ } else if (e.key === "Enter" && this.selectedIndex !== -1) {
95
115
  e.preventDefault();
96
116
  this.menuItemTargets[this.selectedIndex].click();
97
117
  }
98
118
  }
99
119
 
100
120
  updateSelectedItem(direction) {
101
- // Check if any of the menuItemTargets have aria-selected="true" and set the selectedIndex to that index
102
121
  this.menuItemTargets.forEach((item, index) => {
103
- if (item.getAttribute('aria-selected') === 'true') {
122
+ if (item.getAttribute("aria-selected") === "true") {
104
123
  this.selectedIndex = index;
105
124
  }
106
125
  });
@@ -121,24 +140,17 @@ export default class extends Controller {
121
140
  }
122
141
 
123
142
  toggleAriaSelected(element, isSelected) {
124
- // Add or remove attribute
125
143
  if (isSelected) {
126
- element.setAttribute('aria-selected', 'true');
144
+ element.setAttribute("aria-selected", "true");
127
145
  } else {
128
- element.removeAttribute('aria-selected');
146
+ element.removeAttribute("aria-selected");
129
147
  }
130
148
  }
131
149
 
132
150
  deselectAll() {
133
- this.menuItemTargets.forEach(item => this.toggleAriaSelected(item, false));
151
+ this.menuItemTargets.forEach((item) =>
152
+ this.toggleAriaSelected(item, false)
153
+ );
134
154
  this.selectedIndex = -1;
135
155
  }
136
-
137
- addEventListeners() {
138
- document.addEventListener('keydown', this.boundHandleKeydown);
139
- }
140
-
141
- removeEventListeners() {
142
- document.removeEventListener('keydown', this.boundHandleKeydown);
143
- }
144
156
  }
@@ -2,8 +2,9 @@
2
2
 
3
3
  module RubyUI
4
4
  class DataTableColumnToggle < Base
5
- def initialize(columns:, **attrs)
5
+ def initialize(columns:, label: "Columns", **attrs)
6
6
  @columns = columns
7
+ @label = label
7
8
  super(**attrs)
8
9
  end
9
10
 
@@ -12,7 +13,7 @@ module RubyUI
12
13
  render RubyUI::DropdownMenu.new do
13
14
  render RubyUI::DropdownMenuTrigger.new do
14
15
  render RubyUI::Button.new(variant: :outline, size: :sm) do
15
- plain "Columns"
16
+ plain @label
16
17
  # inline chevron-down SVG (lucide 24px, 1px stroke)
17
18
  svg(
18
19
  xmlns: "http://www.w3.org/2000/svg",
@@ -35,8 +36,11 @@ module RubyUI
35
36
  label(class: "flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm cursor-pointer hover:bg-accent") do
36
37
  input(
37
38
  type: "checkbox",
38
- checked: true,
39
- class: "h-4 w-4 rounded border border-input accent-primary cursor-pointer",
39
+ checked: col.fetch(:visible, true),
40
+ class: [
41
+ "h-4 w-4 rounded border border-input accent-primary cursor-pointer",
42
+ "checked:bg-primary checked:text-primary-foreground dark:checked:bg-secondary checked:text-primary checked:border-primary"
43
+ ],
40
44
  data: {
41
45
  column_key: col[:key].to_s,
42
46
  action: "change->ruby-ui--data-table-column-visibility#toggle"
@@ -2,9 +2,19 @@
2
2
  import { Controller } from "@hotwired/stimulus";
3
3
 
4
4
  export default class extends Controller {
5
+ connect() {
6
+ this.element
7
+ .querySelectorAll("input[type=checkbox][data-column-key]")
8
+ .forEach((checkbox) =>
9
+ this._apply(checkbox.dataset.columnKey, checkbox.checked)
10
+ );
11
+ }
12
+
5
13
  toggle(event) {
6
- const key = event.target.dataset.columnKey;
7
- const visible = event.target.checked;
14
+ this._apply(event.target.dataset.columnKey, event.target.checked);
15
+ }
16
+
17
+ _apply(key, visible) {
8
18
  const root = this.element.closest('[data-controller~="ruby-ui--data-table"]');
9
19
  if (!root) return;
10
20
  root
@@ -30,7 +30,7 @@ module RubyUI
30
30
  data_ruby_ui__dialog_target: "dialog",
31
31
  data_action: "click->ruby-ui--dialog#backdropClick",
32
32
  class: [
33
- "fixed flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full",
33
+ "fixed open:flex flex-col pointer-events-auto left-[50%] top-[50%] z-50 w-full max-h-screen overflow-y-auto translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 backdrop:bg-background/80 backdrop:backdrop-blur-sm open:animate-in open:fade-in-0 open:zoom-in-95 sm:rounded-lg md:w-full",
34
34
  SIZES[@size]
35
35
  ]
36
36
  }
@@ -16,7 +16,6 @@ module RubyUI
16
16
  def default_attrs
17
17
  {
18
18
  class: [
19
- "z-50",
20
19
  "group/dropdown-menu",
21
20
  (strategy == "absolute") ? "is-absolute" : "is-fixed"
22
21
  ],
@@ -43,7 +43,7 @@ export default class extends Controller {
43
43
 
44
44
  #computeTooltip() {
45
45
  computePosition(this.triggerTarget, this.contentTarget, {
46
- placement: this.optionsValue.placement || "top",
46
+ placement: this.optionsValue.placement || "bottom",
47
47
  middleware: [flip(), shift(), offset(8)],
48
48
  strategy: this.optionsValue.strategy || "absolute",
49
49
  }).then(({ x, y }) => {
@@ -73,12 +73,16 @@ export default class extends Controller {
73
73
  this.#deselectAll();
74
74
  this.#addEventListeners();
75
75
  this.#computeTooltip();
76
+ // Lift the open menu above sibling dropdowns/elements. The container has no
77
+ // static z-index, so closed siblings stack in normal flow and never cover it.
78
+ this.element.style.zIndex = "50";
76
79
  this.contentTarget.classList.remove("hidden");
77
80
  }
78
81
 
79
82
  close() {
80
83
  this.openValue = false;
81
84
  this.#removeEventListeners();
85
+ this.element.style.zIndex = "";
82
86
  this.contentTarget.classList.add("hidden");
83
87
  }
84
88
 
@@ -11,7 +11,7 @@ class Views::Docs::DropdownMenu < Views::Base
11
11
  render Docs::VisualCodeExample.new(title: "Example", context: self) do
12
12
  <<~RUBY
13
13
  DropdownMenu do
14
- DropdownMenuTrigger(class: 'w-full') do
14
+ DropdownMenuTrigger do
15
15
  Button(variant: :outline) { "Open" }
16
16
  end
17
17
  DropdownMenuContent do
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class Empty < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ data: {slot: "empty"},
14
+ class: "flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed p-12 text-center text-balance"
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class EmptyContent < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ data: {slot: "empty-content"},
14
+ class: "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance"
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class EmptyDescription < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ data: {slot: "empty-description"},
14
+ class: "text-sm leading-relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary"
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Views::Docs::Empty < Views::Base
4
+ def view_template
5
+ component = "Empty"
6
+
7
+ div(class: "max-w-2xl mx-auto w-full py-10 space-y-10") do
8
+ render Docs::Header.new(title: "Empty", description: "Use the empty component to display a state when there is no data or content.")
9
+
10
+ Heading(level: 2) { "Usage" }
11
+
12
+ render Docs::VisualCodeExample.new(title: "Default", context: self) do
13
+ <<~RUBY
14
+ Empty do
15
+ EmptyHeader do
16
+ EmptyMedia(variant: :icon) do
17
+ svg(xmlns: "http://www.w3.org/2000/svg", fill: "none", viewbox: "0 0 24 24", stroke_width: "1.5", stroke: "currentColor", class: "size-6") do |s|
18
+ s.path(stroke_linecap: "round", stroke_linejoin: "round", d: "M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155")
19
+ end
20
+ end
21
+ EmptyTitle { "No messages yet" }
22
+ EmptyDescription { "Start a conversation to see your messages here." }
23
+ end
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ render Docs::VisualCodeExample.new(title: "With action", context: self) do
29
+ <<~RUBY
30
+ Empty do
31
+ EmptyHeader do
32
+ EmptyMedia(variant: :icon) do
33
+ svg(xmlns: "http://www.w3.org/2000/svg", fill: "none", viewbox: "0 0 24 24", stroke_width: "1.5", stroke: "currentColor", class: "size-6") do |s|
34
+ s.path(stroke_linecap: "round", stroke_linejoin: "round", d: "M2.25 12.76c0 1.6 1.123 2.994 2.707 3.227 1.129.166 2.27.293 3.423.379.35.026.67.21.865.501L12 21l2.755-4.133a1.14 1.14 0 0 1 .865-.501 48.172 48.172 0 0 0 3.423-.379c1.584-.233 2.707-1.626 2.707-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z")
35
+ end
36
+ end
37
+ EmptyTitle { "No projects" }
38
+ EmptyDescription { "Get started by creating your first project." }
39
+ end
40
+ EmptyContent do
41
+ Button { "Create project" }
42
+ end
43
+ end
44
+ RUBY
45
+ end
46
+
47
+ render Docs::VisualCodeExample.new(title: "Default media", context: self) do
48
+ <<~RUBY
49
+ Empty(class: "border-none") do
50
+ EmptyHeader do
51
+ EmptyMedia(variant: :default) do
52
+ Avatar(size: :lg) do
53
+ AvatarFallback { "RU" }
54
+ end
55
+ end
56
+ EmptyTitle { "No team members" }
57
+ EmptyDescription { "Invite your team to start collaborating." }
58
+ end
59
+ end
60
+ RUBY
61
+ end
62
+
63
+ render Components::ComponentSetup::Tabs.new(component_name: component)
64
+
65
+ # components
66
+ render Docs::ComponentsTable.new(component_files(component))
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class EmptyHeader < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ data: {slot: "empty-header"},
14
+ class: "flex max-w-sm flex-col items-center gap-2"
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class EmptyMedia < Base
5
+ VARIANTS = {
6
+ default: "bg-transparent",
7
+ icon: "size-10 rounded-xl bg-muted text-foreground [&_svg:not([class*='size-'])]:size-5"
8
+ }
9
+
10
+ def initialize(variant: :default, **attrs)
11
+ @variant = variant
12
+ super(**attrs)
13
+ end
14
+
15
+ def view_template(&)
16
+ div(**attrs, &)
17
+ end
18
+
19
+ private
20
+
21
+ def default_attrs
22
+ {
23
+ data: {slot: "empty-icon", variant: @variant},
24
+ class: [
25
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
26
+ VARIANTS[@variant]
27
+ ]
28
+ }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyUI
4
+ class EmptyTitle < Base
5
+ def view_template(&)
6
+ div(**attrs, &)
7
+ end
8
+
9
+ private
10
+
11
+ def default_attrs
12
+ {
13
+ data: {slot: "empty-title"},
14
+ class: "text-lg font-medium tracking-tight"
15
+ }
16
+ end
17
+ end
18
+ end
@@ -2,10 +2,8 @@
2
2
 
3
3
  module RubyUI
4
4
  class HoverCardContent < Base
5
- def view_template(&block)
6
- template(data: {ruby_ui__hover_card_target: "content"}) do
7
- div(**attrs, &block)
8
- end
5
+ def view_template(&)
6
+ div(**attrs, &)
9
7
  end
10
8
 
11
9
  private
@@ -13,9 +11,10 @@ module RubyUI
13
11
  def default_attrs
14
12
  {
15
13
  data: {
16
- state: :open
14
+ ruby_ui__hover_card_target: "content",
15
+ state: :closed
17
16
  },
18
- class: "z-50 rounded-md border bg-background p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
17
+ class: "hidden absolute z-50 rounded-md border bg-background p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2"
19
18
  }
20
19
  end
21
20
  end