shadcn_phlexcomponents 0.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 (168) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +39 -0
  3. data/Rakefile +12 -0
  4. data/app/assets/tailwind/tailwindcss-animate.css +318 -0
  5. data/app/assets/tailwind/vanilla-calendar-pro.css +461 -0
  6. data/app/javascript/controllers/accordion_controller.js +133 -0
  7. data/app/javascript/controllers/alert_dialog_controller.js +157 -0
  8. data/app/javascript/controllers/avatar_controller.js +15 -0
  9. data/app/javascript/controllers/checkbox_controller.js +28 -0
  10. data/app/javascript/controllers/collapsible_controller.js +35 -0
  11. data/app/javascript/controllers/combobox_controller.js +291 -0
  12. data/app/javascript/controllers/datepicker_controller.js +47 -0
  13. data/app/javascript/controllers/dialog_controller.js +159 -0
  14. data/app/javascript/controllers/dropdown_menu_controller.js +193 -0
  15. data/app/javascript/controllers/hover_card_controller.js +135 -0
  16. data/app/javascript/controllers/loading_button_controller.js +15 -0
  17. data/app/javascript/controllers/popover_controller.js +124 -0
  18. data/app/javascript/controllers/progress_controller.js +14 -0
  19. data/app/javascript/controllers/radio_group_controller.js +90 -0
  20. data/app/javascript/controllers/select_controller.js +294 -0
  21. data/app/javascript/controllers/sheet_controller.js +159 -0
  22. data/app/javascript/controllers/sidebar_controller.js +36 -0
  23. data/app/javascript/controllers/sidebar_trigger_controller.js +15 -0
  24. data/app/javascript/controllers/switch_controller.js +24 -0
  25. data/app/javascript/controllers/tabs_controller.js +73 -0
  26. data/app/javascript/controllers/theme_switcher_controller.js +32 -0
  27. data/app/javascript/controllers/toast_container_controller.js +22 -0
  28. data/app/javascript/controllers/toast_controller.js +45 -0
  29. data/app/javascript/controllers/tooltip_controller.js +135 -0
  30. data/lib/components/accordion.rb +38 -0
  31. data/lib/components/accordion_content.rb +28 -0
  32. data/lib/components/accordion_item.rb +26 -0
  33. data/lib/components/accordion_trigger.rb +45 -0
  34. data/lib/components/alert.rb +40 -0
  35. data/lib/components/alert_description.rb +11 -0
  36. data/lib/components/alert_dialog.rb +60 -0
  37. data/lib/components/alert_dialog_action.rb +22 -0
  38. data/lib/components/alert_dialog_action_to.rb +37 -0
  39. data/lib/components/alert_dialog_cancel.rb +22 -0
  40. data/lib/components/alert_dialog_content.rb +40 -0
  41. data/lib/components/alert_dialog_description.rb +22 -0
  42. data/lib/components/alert_dialog_footer.rb +11 -0
  43. data/lib/components/alert_dialog_header.rb +11 -0
  44. data/lib/components/alert_dialog_title.rb +22 -0
  45. data/lib/components/alert_dialog_trigger.rb +50 -0
  46. data/lib/components/alert_title.rb +11 -0
  47. data/lib/components/aspect_ratio.rb +19 -0
  48. data/lib/components/avatar.rb +31 -0
  49. data/lib/components/avatar_fallback.rb +21 -0
  50. data/lib/components/avatar_image.rb +20 -0
  51. data/lib/components/badge.rb +36 -0
  52. data/lib/components/base.rb +108 -0
  53. data/lib/components/breadcrumb.rb +51 -0
  54. data/lib/components/breadcrumb_ellipsis.rb +23 -0
  55. data/lib/components/breadcrumb_item.rb +11 -0
  56. data/lib/components/breadcrumb_link.rb +7 -0
  57. data/lib/components/breadcrumb_page.rb +21 -0
  58. data/lib/components/breadcrumb_separator.rb +26 -0
  59. data/lib/components/button.rb +53 -0
  60. data/lib/components/card.rb +31 -0
  61. data/lib/components/card_content.rb +11 -0
  62. data/lib/components/card_description.rb +11 -0
  63. data/lib/components/card_footer.rb +11 -0
  64. data/lib/components/card_header.rb +11 -0
  65. data/lib/components/card_title.rb +11 -0
  66. data/lib/components/checkbox.rb +65 -0
  67. data/lib/components/checkbox_group.rb +48 -0
  68. data/lib/components/collapsible.rb +32 -0
  69. data/lib/components/collapsible_content.rb +25 -0
  70. data/lib/components/collapsible_trigger.rb +50 -0
  71. data/lib/components/datepicker.rb +38 -0
  72. data/lib/components/dialog.rb +52 -0
  73. data/lib/components/dialog_close.rb +42 -0
  74. data/lib/components/dialog_content.rb +54 -0
  75. data/lib/components/dialog_description.rb +22 -0
  76. data/lib/components/dialog_footer.rb +11 -0
  77. data/lib/components/dialog_header.rb +11 -0
  78. data/lib/components/dialog_title.rb +22 -0
  79. data/lib/components/dialog_trigger.rb +50 -0
  80. data/lib/components/dropdown_menu.rb +50 -0
  81. data/lib/components/dropdown_menu_content.rb +49 -0
  82. data/lib/components/dropdown_menu_item.rb +57 -0
  83. data/lib/components/dropdown_menu_item_to.rb +25 -0
  84. data/lib/components/dropdown_menu_label.rb +12 -0
  85. data/lib/components/dropdown_menu_separator.rb +20 -0
  86. data/lib/components/dropdown_menu_trigger.rb +58 -0
  87. data/lib/components/hover_card.rb +33 -0
  88. data/lib/components/hover_card_content.rb +36 -0
  89. data/lib/components/hover_card_trigger.rb +50 -0
  90. data/lib/components/input.rb +32 -0
  91. data/lib/components/label.rb +15 -0
  92. data/lib/components/link.rb +26 -0
  93. data/lib/components/loading_button.rb +21 -0
  94. data/lib/components/pagination.rb +38 -0
  95. data/lib/components/pagination_ellipsis.rb +24 -0
  96. data/lib/components/pagination_link.rb +34 -0
  97. data/lib/components/pagination_next.rb +32 -0
  98. data/lib/components/pagination_previous.rb +32 -0
  99. data/lib/components/popover.rb +35 -0
  100. data/lib/components/popover_content.rb +37 -0
  101. data/lib/components/popover_trigger.rb +52 -0
  102. data/lib/components/progress.rb +37 -0
  103. data/lib/components/radio_group.rb +62 -0
  104. data/lib/components/radio_group_item.rb +66 -0
  105. data/lib/components/select.rb +189 -0
  106. data/lib/components/select_content.rb +59 -0
  107. data/lib/components/select_group.rb +23 -0
  108. data/lib/components/select_item.rb +58 -0
  109. data/lib/components/select_label.rb +23 -0
  110. data/lib/components/select_trigger.rb +54 -0
  111. data/lib/components/separator.rb +29 -0
  112. data/lib/components/sheet.rb +53 -0
  113. data/lib/components/sheet_close.rb +42 -0
  114. data/lib/components/sheet_content.rb +67 -0
  115. data/lib/components/sheet_description.rb +22 -0
  116. data/lib/components/sheet_footer.rb +11 -0
  117. data/lib/components/sheet_header.rb +11 -0
  118. data/lib/components/sheet_title.rb +22 -0
  119. data/lib/components/sheet_trigger.rb +50 -0
  120. data/lib/components/sidebar.rb +103 -0
  121. data/lib/components/sidebar_container.rb +11 -0
  122. data/lib/components/sidebar_content.rb +11 -0
  123. data/lib/components/sidebar_footer.rb +11 -0
  124. data/lib/components/sidebar_group.rb +11 -0
  125. data/lib/components/sidebar_group_content.rb +11 -0
  126. data/lib/components/sidebar_group_label.rb +16 -0
  127. data/lib/components/sidebar_header.rb +11 -0
  128. data/lib/components/sidebar_inset.rb +15 -0
  129. data/lib/components/sidebar_menu.rb +11 -0
  130. data/lib/components/sidebar_menu_button.rb +61 -0
  131. data/lib/components/sidebar_menu_item.rb +9 -0
  132. data/lib/components/sidebar_menu_sub.rb +14 -0
  133. data/lib/components/sidebar_menu_sub_button.rb +48 -0
  134. data/lib/components/sidebar_menu_sub_item.rb +9 -0
  135. data/lib/components/sidebar_trigger.rb +40 -0
  136. data/lib/components/skeleton.rb +11 -0
  137. data/lib/components/switch.rb +65 -0
  138. data/lib/components/table.rb +73 -0
  139. data/lib/components/table_body.rb +11 -0
  140. data/lib/components/table_caption.rb +11 -0
  141. data/lib/components/table_cell.rb +11 -0
  142. data/lib/components/table_footer.rb +11 -0
  143. data/lib/components/table_head.rb +14 -0
  144. data/lib/components/table_header.rb +11 -0
  145. data/lib/components/table_row.rb +11 -0
  146. data/lib/components/tabs.rb +38 -0
  147. data/lib/components/tabs_content.rb +35 -0
  148. data/lib/components/tabs_list.rb +23 -0
  149. data/lib/components/tabs_trigger.rb +45 -0
  150. data/lib/components/textarea.rb +28 -0
  151. data/lib/components/theme_switcher.rb +21 -0
  152. data/lib/components/toast.rb +100 -0
  153. data/lib/components/toast_action.rb +38 -0
  154. data/lib/components/toast_action_to.rb +25 -0
  155. data/lib/components/toast_container.rb +44 -0
  156. data/lib/components/toast_content.rb +11 -0
  157. data/lib/components/toast_description.rb +11 -0
  158. data/lib/components/toast_title.rb +11 -0
  159. data/lib/components/tooltip.rb +34 -0
  160. data/lib/components/tooltip_content.rb +42 -0
  161. data/lib/components/tooltip_trigger.rb +50 -0
  162. data/lib/install/install_shadcn_phlexcomponents.rb +12 -0
  163. data/lib/shadcn_phlexcomponents/alias.rb +132 -0
  164. data/lib/shadcn_phlexcomponents/engine.rb +11 -0
  165. data/lib/shadcn_phlexcomponents/version.rb +5 -0
  166. data/lib/shadcn_phlexcomponents.rb +9 -0
  167. data/lib/tasks/install.rake +10 -0
  168. metadata +264 -0
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class CheckboxGroup < Base
5
+ STYLES = "space-y-1.5"
6
+
7
+ def initialize(name:, value: [], include_hidden: true, **attributes)
8
+ @name = name
9
+ @value = value
10
+ @include_hidden = include_hidden
11
+ super(**attributes)
12
+ end
13
+
14
+ def items(collection, value_method:, text_method:, wrapper_class: nil)
15
+ wrapper_class = TAILWIND_MERGER.merge("flex items-center space-x-2 #{wrapper_class}")
16
+
17
+ if collection.first && collection.first.is_a?(Hash)
18
+ collection = convert_collection_hash_to_struct(collection, value_method: value_method, text_method: text_method)
19
+ end
20
+
21
+ collection.each do |item|
22
+ value = item.public_send(value_method)
23
+ text = item.public_send(text_method)
24
+ id = "#{@name.parameterize.underscore}_#{value}"
25
+
26
+ div(class: wrapper_class) do
27
+ Checkbox(name: "#{@name}[]",
28
+ id: id,
29
+ value: value,
30
+ checked: @value.include?(value),
31
+ include_hidden: false
32
+ )
33
+ Label(for: id) { text }
34
+ end
35
+ end
36
+ end
37
+
38
+ def view_template(&)
39
+ div(**@attributes) do
40
+ yield
41
+
42
+ if @include_hidden
43
+ input(type: "hidden", name: "#{@name}[]", autocomplete: "off")
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Collapsible < Base
5
+
6
+ def initialize(open: false, aria_id: "collapsible-#{SecureRandom.hex(5)}", **attributes)
7
+ @open = open
8
+ @aria_id = aria_id
9
+ super(**attributes)
10
+ end
11
+
12
+ def trigger(**attributes, &)
13
+ CollapsibleTrigger(open: @open, aria_id: @aria_id, **attributes, &)
14
+ end
15
+
16
+ def content(**attributes, &)
17
+ CollapsibleContent(aria_id: @aria_id, **attributes, &)
18
+ end
19
+
20
+ def default_attributes
21
+ {
22
+ data: {
23
+ controller: "shadcn-phlexcomponents--collapsible"
24
+ }
25
+ }
26
+ end
27
+
28
+ def view_template(&)
29
+ div(**@attributes, &)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class CollapsibleContent < Base
5
+
6
+ def initialize(aria_id: :nil, **attributes)
7
+ @aria_id = aria_id
8
+ super(**attributes)
9
+ end
10
+
11
+ def default_attributes
12
+ {
13
+ id: "#{@aria_id}-content",
14
+ data: {
15
+ "shadcn-phlexcomponents--collapsible-target": "content"
16
+ }
17
+ }
18
+ end
19
+
20
+ def view_template(&)
21
+ @class = @attributes.delete(:class)
22
+ div(class: "#{@class} hidden", **@attributes, &)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class CollapsibleTrigger < Base
5
+ def initialize(open: false, as_child: false, aria_id: nil, **attributes)
6
+ @open = open
7
+ @as_child = as_child
8
+ @aria_id = aria_id
9
+ super(**attributes)
10
+ end
11
+
12
+ def default_attributes
13
+ {
14
+ role: "button",
15
+ aria: {
16
+ expanded: @open.to_s,
17
+ controls: "#{@aria_id}-content"
18
+ },
19
+ data: {
20
+ state: @open ? "open" : 'closed',
21
+ action: "click->shadcn-phlexcomponents--collapsible#toggle",
22
+ "shadcn-phlexcomponents--collapsible-target": "trigger"
23
+ }
24
+ }
25
+ end
26
+
27
+ def view_template(&)
28
+ if @as_child
29
+ content = capture(&)
30
+ element = find_as_child(content.to_s)
31
+
32
+ vanish(&)
33
+ element_attributes = nokogiri_attributes_to_hash(element)
34
+ styles = TAILWIND_MERGER.merge("#{@attributes[:class]} #{element_attributes[:class]}")
35
+ merged_attributes = mix(@attributes, element_attributes)
36
+ merged_attributes[:class] = styles
37
+
38
+ if element.name == "button"
39
+ merged_attributes.delete(:role)
40
+ end
41
+
42
+ send(element.name, **merged_attributes) do
43
+ sanitize_as_child(element.children.to_s)
44
+ end
45
+ else
46
+ div(**@attributes, &)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Datepicker < Base
5
+ STYLES = <<~HEREDOC
6
+ flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1
7
+ text-base shadow-sm transition-colors file:border-0 file:bg-transparent
8
+ file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground
9
+ focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
10
+ disabled:cursor-not-allowed disabled:opacity-50 md:text-sm
11
+ file:pt-0.5
12
+ HEREDOC
13
+
14
+ def initialize(name: nil, id: nil, value: nil, format: nil, settings: {}, **attributes)
15
+ @name = name
16
+ @id = id || @name
17
+ @value = value
18
+ @settings = settings
19
+ @format = format
20
+ super(**attributes)
21
+ end
22
+
23
+ def default_attributes
24
+ {
25
+ data: {
26
+ value: @value&.utc.to_s,
27
+ format: @format,
28
+ controller: "shadcn-phlexcomponents--datepicker",
29
+ settings: @settings.to_json
30
+ }
31
+ }
32
+ end
33
+
34
+ def view_template
35
+ input(type: :text, readonly: true, **@attributes)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Dialog < Base
5
+ STYLES = "inline-block"
6
+
7
+ def initialize(aria_id: "dialog-#{SecureRandom.hex(5)}", **attributes)
8
+ @aria_id = aria_id
9
+ super(**attributes)
10
+ end
11
+
12
+ def trigger(**attributes, &)
13
+ render(DialogTrigger.new(aria_id: @aria_id, **attributes, &))
14
+ end
15
+
16
+ def content(**attributes, &)
17
+ render(DialogContent.new(aria_id: @aria_id, **attributes, &))
18
+ end
19
+
20
+ def header(**attributes, &)
21
+ render(DialogHeader.new(**attributes, &))
22
+ end
23
+
24
+ def title(**attributes, &)
25
+ render(DialogTitle.new(aria_id: @aria_id, **attributes, &))
26
+ end
27
+
28
+ def description(**attributes, &)
29
+ render(DialogDescription.new(aria_id: @aria_id, **attributes, &))
30
+ end
31
+
32
+ def footer(**attributes, &)
33
+ render(DialogFooter.new(**attributes, &))
34
+ end
35
+
36
+ def close(**attributes, &)
37
+ render(DialogClose.new(**attributes, &))
38
+ end
39
+
40
+ def default_attributes
41
+ {
42
+ data: {
43
+ controller: "shadcn-phlexcomponents--dialog",
44
+ },
45
+ }
46
+ end
47
+
48
+ def view_template(&)
49
+ div(**@attributes, &)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogClose < Base
5
+ def initialize(as_child: false, **attributes)
6
+ @as_child = as_child
7
+ super(**attributes)
8
+ end
9
+
10
+ def default_attributes
11
+ {
12
+ role: "button",
13
+ data: {
14
+ action: "click->shadcn-phlexcomponents--dialog#close",
15
+ },
16
+ }
17
+ end
18
+
19
+ def view_template(&)
20
+ if @as_child
21
+ content = capture(&)
22
+ element = find_as_child(content.to_s)
23
+
24
+ vanish(&)
25
+ element_attributes = nokogiri_attributes_to_hash(element)
26
+ styles = TAILWIND_MERGER.merge("#{@attributes[:class]} #{element_attributes[:class]}")
27
+ merged_attributes = mix(@attributes, element_attributes)
28
+ merged_attributes[:class] = styles
29
+
30
+ if element.name == "button"
31
+ merged_attributes.delete(:role)
32
+ end
33
+
34
+ send(element.name, **merged_attributes) do
35
+ sanitize_as_child(element.children.to_s)
36
+ end
37
+ else
38
+ div(**@attributes, &)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogContent < Base
5
+ STYLES = <<~HEREDOC
6
+ fixed left-[50%] top-[50%] z-51 grid w-full max-w-lg translate-x-[-50%]
7
+ translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200
8
+ data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0
9
+ data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
10
+ data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
11
+ data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg
12
+ HEREDOC
13
+
14
+ CLOSE_BUTTON_STYLES = <<~HEREDOC
15
+ absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background
16
+ transition-opacity hover:opacity-100 focus:outline-none focus:ring-2
17
+ focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none
18
+ cursor-pointer
19
+ HEREDOC
20
+
21
+ def initialize(aria_id: nil, **attributes)
22
+ @aria_id = aria_id
23
+ super(**attributes)
24
+ end
25
+
26
+ def default_attributes
27
+ {
28
+ id: "#{@aria_id}-content",
29
+ tabindex: -1,
30
+ role: "dialog",
31
+ aria: {
32
+ describedby: "#{@aria_id}-description",
33
+ labelledby: "#{@aria_id}-title",
34
+ },
35
+ data: {
36
+ "shadcn-phlexcomponents--dialog-target": "content",
37
+ state: "closed"
38
+ },
39
+ }
40
+ end
41
+
42
+ def view_template(&)
43
+ @class = @attributes.delete(:class)
44
+ div(class: "#{@class} hidden", **@attributes) do
45
+ yield
46
+
47
+ button(class: CLOSE_BUTTON_STYLES, data: { action: "click->shadcn-phlexcomponents--dialog#close" }) do
48
+ icon("x", class: "size-4")
49
+ span(class: "sr-only") { "close" }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogDescription < Base
5
+ STYLES = "text-sm text-muted-foreground"
6
+
7
+ def initialize(aria_id: nil, **attributes)
8
+ @aria_id = aria_id
9
+ super(**attributes)
10
+ end
11
+
12
+ def default_attributes
13
+ {
14
+ id: "#{@aria_id}-description",
15
+ }
16
+ end
17
+
18
+ def view_template(&)
19
+ p(**@attributes, &)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogFooter < Base
5
+ STYLES = "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2"
6
+
7
+ def view_template(&)
8
+ div(**@attributes, &)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogHeader < Base
5
+ STYLES = "flex flex-col space-y-1.5 text-center sm:text-left"
6
+
7
+ def view_template(&)
8
+ div(**@attributes, &)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogTitle < Base
5
+ STYLES = "text-lg font-semibold leading-none tracking-tight"
6
+
7
+ def initialize(aria_id: nil, **attributes)
8
+ @aria_id = aria_id
9
+ super(**attributes)
10
+ end
11
+
12
+ def default_attributes
13
+ {
14
+ id: "#{@aria_id}-title",
15
+ }
16
+ end
17
+
18
+ def view_template(&)
19
+ h2(**@attributes, &)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DialogTrigger < Base
5
+ def initialize(as_child: false, aria_id: nil, **attributes)
6
+ @as_child = as_child
7
+ @aria_id = aria_id
8
+ super(**attributes)
9
+ end
10
+
11
+ def default_attributes
12
+ {
13
+ role: "button",
14
+ aria: {
15
+ haspopup: "dialog",
16
+ expanded: "false",
17
+ controls: "#{@aria_id}-content",
18
+ },
19
+ data: {
20
+ action: "click->shadcn-phlexcomponents--dialog#open",
21
+ "shadcn-phlexcomponents--dialog-target": "trigger",
22
+ as_child: @as_child.to_s
23
+ },
24
+ }
25
+ end
26
+
27
+ def view_template(&)
28
+ if @as_child
29
+ content = capture(&)
30
+ element = find_as_child(content.to_s)
31
+
32
+ vanish(&)
33
+ element_attributes = nokogiri_attributes_to_hash(element)
34
+ styles = TAILWIND_MERGER.merge("#{@attributes[:class]} #{element_attributes[:class]}")
35
+ merged_attributes = mix(@attributes, element_attributes)
36
+ merged_attributes[:class] = styles
37
+
38
+ if element.name == "button"
39
+ merged_attributes.delete(:role)
40
+ end
41
+
42
+ send(element.name, **merged_attributes) do
43
+ sanitize_as_child(element.children.to_s)
44
+ end
45
+ else
46
+ div(**@attributes, &)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DropdownMenu < Base
5
+ STYLES = "inline-block".freeze
6
+
7
+ def initialize(side: :bottom, aria_id: "dropdown-menu-#{SecureRandom.hex(5)}", **attributes)
8
+ @side = side
9
+ @aria_id = aria_id
10
+ super(**attributes)
11
+ end
12
+
13
+ def trigger(**attributes, &)
14
+ DropdownMenuTrigger(aria_id: @aria_id, **attributes, &)
15
+ end
16
+
17
+ def content(**attributes, &)
18
+ DropdownMenuContent(aria_id: @aria_id, side: @side, **attributes, &)
19
+ end
20
+
21
+ def label(**attributes, &)
22
+ DropdownMenuLabel(**attributes, &)
23
+ end
24
+
25
+ def item(**attributes, &)
26
+ DropdownMenuItem(**attributes, &)
27
+ end
28
+
29
+ def item_to(name = nil, options = nil, html_options = nil, &block)
30
+ DropdownMenuItemTo(name, options, html_options, &block)
31
+ end
32
+
33
+ def separator(**attributes, &)
34
+ DropdownMenuSeparator(**attributes, &)
35
+ end
36
+
37
+ def default_attributes
38
+ {
39
+ data: {
40
+ controller: "shadcn-phlexcomponents--dropdown-menu",
41
+ side: @side
42
+ }
43
+ }
44
+ end
45
+
46
+ def view_template(&)
47
+ div(**@attributes, &)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DropdownMenuContent < Base
5
+ STYLES = <<~HEREDOC.freeze
6
+ z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1
7
+ text-popover-foreground shadow-md outline-none
8
+ data-[state=open]:animate-in
9
+ data-[state=closed]:animate-out data-[state=closed]:fade-out-0
10
+ data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95
11
+ data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2
12
+ data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
13
+ data-[side=top]:slide-in-from-bottom-2
14
+ HEREDOC
15
+
16
+ def initialize(side: :bottom, aria_id: nil, **attributes)
17
+ @side = side
18
+ @aria_id = aria_id
19
+ super(**attributes)
20
+ end
21
+
22
+ def view_template(&)
23
+ div(class: "hidden fixed top-0 left-0 w-max z-50", data: { "shadcn-phlexcomponents--dropdown-menu-target": "contentWrapper" }) do
24
+ div(**@attributes, &)
25
+ end
26
+ end
27
+
28
+ def default_attributes
29
+ {
30
+ id: "#{@aria_id}-content",
31
+ tabindex: -1,
32
+ role: "menu",
33
+ aria: {
34
+ labelledby: "#{@aria_id}-trigger",
35
+ orientation: "vertical"
36
+ },
37
+ data: {
38
+ state: "closed",
39
+ side: @side,
40
+ "shadcn-phlexcomponents--dropdown-menu-target": "content",
41
+ action: <<~HEREDOC,
42
+ keydown.up->shadcn-phlexcomponents--dropdown-menu#focusLastItem:prevent
43
+ keydown.down->shadcn-phlexcomponents--dropdown-menu#focusFirstItem:prevent
44
+ HEREDOC
45
+ }
46
+ }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+
5
+ class DropdownMenuItem < Base
6
+ STYLES = <<~HEREDOC.freeze
7
+ relative flex cursor-default select-none items-center gap-2 rounded-sm px-2
8
+ py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground
9
+ data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0
10
+ HEREDOC
11
+
12
+ def initialize(as_child: false, disabled: false, **attributes)
13
+ @as_child = as_child
14
+ @disabled = disabled
15
+ super(**attributes)
16
+ end
17
+
18
+ def view_template(&)
19
+ if @as_child
20
+ content = capture(&)
21
+ element = find_as_child(content.to_s)
22
+
23
+ vanish(&)
24
+ element_attributes = nokogiri_attributes_to_hash(element)
25
+ styles = TAILWIND_MERGER.merge("#{@attributes[:class]} #{element_attributes[:class]}")
26
+ merged_attributes = mix(@attributes, element_attributes)
27
+ merged_attributes[:class] = styles
28
+
29
+ send(element.name, **merged_attributes) do
30
+ sanitize_as_child(element.children.to_s)
31
+ end
32
+ else
33
+ div(**@attributes, &)
34
+ end
35
+ end
36
+
37
+ def default_attributes
38
+ {
39
+ role: "menuitem",
40
+ tabindex: -1,
41
+ data: {
42
+ disabled: @disabled,
43
+ action: <<~HEREDOC,
44
+ click->shadcn-phlexcomponents--dropdown-menu#selectItem
45
+ keydown.up->shadcn-phlexcomponents--dropdown-menu#focusPrevItem:stop
46
+ keydown.down->shadcn-phlexcomponents--dropdown-menu#focusNextItem:stop
47
+ keydown.enter->shadcn-phlexcomponents--dropdown-menu#selectItem:prevent
48
+ keydown.space->shadcn-phlexcomponents--dropdown-menu#selectItem:prevent
49
+ mouseover->shadcn-phlexcomponents--dropdown-menu#focusItem
50
+ mouseout->shadcn-phlexcomponents--dropdown-menu#focusContent
51
+ HEREDOC
52
+ "shadcn-phlexcomponents--dropdown-menu-target": "item"
53
+ }
54
+ }
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class DropdownMenuItemTo < DropdownMenuItem
5
+ def initialize(name = nil, options = nil, html_options = nil)
6
+ @name = name
7
+ @options = options
8
+ @html_options = html_options
9
+ end
10
+
11
+ def view_template(&)
12
+ @html_options, @options = @options, @name if block_given?
13
+ @html_options ||= {}
14
+ @disabled = @html_options[:disabled]
15
+ @html_options = mix(default_attributes, @html_options)
16
+ @html_options[:class] = TAILWIND_MERGER.merge("w-full #{default_styles} #{@html_options[:class]}")
17
+
18
+ if block_given?
19
+ button_to(@options, @html_options, &)
20
+ else
21
+ button_to(@name, @options, @html_options)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+
5
+ class DropdownMenuLabel < Base
6
+ STYLES = "px-2 py-1.5 text-sm font-semibold".freeze
7
+
8
+ def view_template(&)
9
+ div(**@attributes, &)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module ShadcnPhlexcomponents
3
+
4
+ class DropdownMenuSeparator < Base
5
+ STYLES = "-mx-1 my-1 h-px bg-muted".freeze
6
+
7
+ def view_template(&)
8
+ div(**@attributes, &)
9
+ end
10
+
11
+ def default_attributes
12
+ {
13
+ role: "separator",
14
+ aria: {
15
+ orientation: "horizontal"
16
+ }
17
+ }
18
+ end
19
+ end
20
+ end