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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class AlertDialogTrigger < 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--alert-dialog#open",
21
+ "shadcn-phlexcomponents--alert-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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class AlertTitle < Base
5
+ STYLES = "mb-1 font-medium leading-none tracking-tight"
6
+
7
+ def view_template(&)
8
+ div(**@attributes, &)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class AspectRatio < Base
5
+ STYLES = "absolute inset-0"
6
+
7
+ def initialize(ratio: "1/1", **attributes)
8
+ ratio_arr = ratio.split("/").map(&:to_f)
9
+ @ratio = ratio_arr[0] / ratio_arr[1]
10
+ super(**attributes)
11
+ end
12
+
13
+ def view_template(&)
14
+ div(style: { position: "relative", width: "100%", "padding-bottom": "#{100 / @ratio}%" }) do
15
+ div(**@attributes, &)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Avatar < Base
5
+ STYLES = "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"
6
+
7
+ def initialize(**attributes)
8
+ super(**attributes)
9
+ end
10
+
11
+ def image(**attributes, &)
12
+ AvatarImage(**attributes, &)
13
+ end
14
+
15
+ def fallback(**attributes, &)
16
+ AvatarFallback(**attributes, &)
17
+ end
18
+
19
+ def default_attributes
20
+ {
21
+ data: {
22
+ controller: "shadcn-phlexcomponents--avatar",
23
+ },
24
+ }
25
+ end
26
+
27
+ def view_template(&)
28
+ span(**@attributes, &)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class AvatarFallback < Base
5
+ STYLES = "flex h-full w-full items-center justify-center rounded-full bg-muted"
6
+
7
+ def default_attributes
8
+ {
9
+ data: {
10
+ "shadcn-phlexcomponents--avatar-target": "fallback",
11
+ },
12
+ }
13
+ end
14
+
15
+ def view_template(&)
16
+ @class = @attributes.delete(:class)
17
+
18
+ span(class: "#{@class} hidden", **@attributes, &)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+
5
+ class AvatarImage < Base
6
+ STYLES = "aspect-square h-full w-full"
7
+
8
+ def default_attributes
9
+ {
10
+ data: {
11
+ "shadcn-phlexcomponents--avatar-target": "image",
12
+ },
13
+ }
14
+ end
15
+
16
+ def view_template(&)
17
+ img(**@attributes, &)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Badge < Base
5
+ STYLES = <<~HEREDOC
6
+ inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold
7
+ transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2
8
+ HEREDOC
9
+
10
+ VARIANTS = {
11
+ primary: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
13
+ destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
14
+ outline: "text-foreground",
15
+ }.freeze
16
+
17
+ class << self
18
+ def default_styles(variant)
19
+ "#{STYLES} #{VARIANTS[variant]}"
20
+ end
21
+ end
22
+
23
+ def initialize(variant: :primary, **attributes)
24
+ @variant = variant
25
+ super(**attributes)
26
+ end
27
+
28
+ def default_styles
29
+ self.class.default_styles(@variant)
30
+ end
31
+
32
+ def view_template(&)
33
+ div(**@attributes, &)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Base < Phlex::HTML
5
+ # Include any helpers you want to be available across all components
6
+ include Phlex::Rails::Helpers::Sanitize
7
+ include Phlex::Rails::Helpers::LinkTo
8
+ include Phlex::Rails::Helpers::ButtonTo
9
+
10
+ TAILWIND_MERGER = ::TailwindMerge::Merger.new.freeze
11
+ STYLES = ""
12
+
13
+ SANITIZER_ALLOWED_TAGS = (Rails::HTML::SafeListSanitizer.allowed_tags.to_a +
14
+ [ "svg", "path", "polygon", "polyline", "circle", "ellipse", "rect", "line", "use", "defs", "g" ]).freeze
15
+
16
+ SANITIZER_ALLOWED_ATTRIBUTES = (Rails::HTML::SafeListSanitizer.allowed_attributes.to_a +
17
+ [
18
+ "viewBox",
19
+ "preserveaspectratio",
20
+ "cx",
21
+ "cy",
22
+ "d",
23
+ "fill",
24
+ "height",
25
+ "points",
26
+ "r",
27
+ "stroke",
28
+ "width",
29
+ "x",
30
+ "y",
31
+ "stroke-linejoin",
32
+ "stroke-width",
33
+ "stroke-linecap",
34
+ "aria-hidden",
35
+ "class"
36
+ ]).freeze
37
+
38
+ def initialize(**attributes)
39
+ @attributes = mix(default_attributes, attributes)
40
+ @attributes[:class] = TAILWIND_MERGER.merge("#{default_styles} #{@attributes[:class]}")
41
+ end
42
+
43
+ if Rails.env.development?
44
+ def before_template
45
+ comment { "Before #{self.class.name}" }
46
+ super
47
+ end
48
+ end
49
+
50
+ def default_attributes
51
+ {}
52
+ end
53
+
54
+ def default_styles
55
+ self.class::STYLES
56
+ end
57
+
58
+ def nokogiri_attributes_to_hash(element)
59
+ hash = {}
60
+
61
+ element.attributes.each do |key, attr|
62
+ hash[key] = attr.value
63
+ end
64
+
65
+ hash.transform_keys(&:to_sym)
66
+ end
67
+
68
+ def sanitize_as_child(html)
69
+ sanitize(
70
+ html,
71
+ tags: SANITIZER_ALLOWED_TAGS,
72
+ attributes: SANITIZER_ALLOWED_ATTRIBUTES,
73
+ )
74
+ end
75
+
76
+ def find_as_child(rendered_element)
77
+ fragment = Nokogiri::HTML.fragment(rendered_element)
78
+ element = fragment.children.find do |child|
79
+ if child.is_a?(Nokogiri::XML::Comment)
80
+ false
81
+ else
82
+ (child.is_a?(Nokogiri::XML::Text) && child.text.strip.present?) || !child.is_a?(Nokogiri::XML::Text)
83
+ end
84
+ end
85
+
86
+ element
87
+ end
88
+
89
+ # https://github.com/heyvito/lucide-rails/blob/master/lib/lucide-rails/rails_helper.rb
90
+ def icon(named, **options)
91
+ options = options.with_indifferent_access
92
+ size = options.delete(:size)
93
+ options = options.merge width: size, height: size if size
94
+
95
+ svg(**LucideRails.default_options.merge(**options)) { LucideRails::IconProvider.icon(named).html_safe }
96
+ end
97
+
98
+ def convert_collection_hash_to_struct(collection, value_method:, text_method:)
99
+ structConstructor = Struct.new(value_method, text_method)
100
+ collection.map do |item|
101
+ struct = structConstructor.new
102
+ struct[value_method] = item[value_method]
103
+ struct[text_method] = item[text_method]
104
+ struct
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Breadcrumb < Base
5
+ STYLES = "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5"
6
+
7
+ def item(**attributes, &)
8
+ BreadcrumbItem(**attributes, &)
9
+ end
10
+
11
+ def link(name = nil, options = nil, html_options = nil, &block)
12
+ BreadcrumbLink(name, options, html_options, &block)
13
+ end
14
+
15
+ def separator(**attributes, &)
16
+ BreadcrumbSeparator(**attributes, &)
17
+ end
18
+
19
+ def page(**attributes, &)
20
+ BreadcrumbPage(**attributes, &)
21
+ end
22
+
23
+ def ellipsis(**attributes)
24
+ BreadcrumbEllipsis(**attributes)
25
+ end
26
+
27
+ def links(collection)
28
+ collection.each_with_index do |link, index|
29
+ if index == collection.size - 1
30
+ item do
31
+ page { link[:name] }
32
+ end
33
+ else
34
+ item do
35
+ link(link[:name], link[:path])
36
+ end
37
+ end
38
+
39
+ if index < collection.size - 1
40
+ separator
41
+ end
42
+ end
43
+ end
44
+
45
+ def view_template(&)
46
+ nav(aria: { label: "breadcrumb" }) do
47
+ ol(**@attributes, &)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class BreadcrumbEllipsis < Base
5
+ STYLES = "flex h-9 w-9 items-center justify-center"
6
+
7
+ def default_attributes
8
+ {
9
+ role: "presentation",
10
+ aria: {
11
+ hidden: "true",
12
+ },
13
+ }
14
+ end
15
+
16
+ def view_template
17
+ span(**@attributes) do
18
+ icon("ellipsis", class: "size-4")
19
+ span(class: "sr-only") { "More" }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class BreadcrumbItem < Base
5
+ STYLES = "inline-flex items-center gap-1.5"
6
+
7
+ def view_template(&)
8
+ li(**@attributes, &)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class BreadcrumbLink < Link
5
+ STYLES = "transition-colors hover:text-foreground"
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class BreadcrumbPage < Base
5
+ STYLES = "font-normal text-foreground"
6
+
7
+ def default_attributes
8
+ {
9
+ role: "link",
10
+ aria: {
11
+ disabled: "true",
12
+ current: "page",
13
+ },
14
+ }
15
+ end
16
+
17
+ def view_template(&)
18
+ span(**@attributes, &)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class BreadcrumbSeparator < Base
5
+ STYLES = "[&>svg]:w-3.5 [&>svg]:h-3.5"
6
+
7
+ def default_attributes
8
+ {
9
+ role: "presentation",
10
+ aria: {
11
+ hidden: "true",
12
+ },
13
+ }
14
+ end
15
+
16
+ def view_template(&)
17
+ li(**@attributes) do
18
+ if block_given?
19
+ yield
20
+ else
21
+ icon("chevron-right")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Button < Base
5
+ STYLES = <<~HEREDOC
6
+ inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md
7
+ text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1
8
+ focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50
9
+ [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 cursor-pointer
10
+ HEREDOC
11
+
12
+ VARIANTS = {
13
+ primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
15
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
17
+ ghost: "hover:bg-accent hover:text-accent-foreground",
18
+ link: "text-primary underline-offset-4 hover:underline",
19
+ }.freeze
20
+
21
+ SIZES = {
22
+ default: "h-9 px-4 py-2",
23
+ sm: "h-8 rounded-md px-3 text-xs",
24
+ lg: "h-10 rounded-md px-8",
25
+ icon: "h-9 w-9",
26
+ }
27
+
28
+ class << self
29
+ def default_styles(variant:, size:)
30
+ "#{STYLES} #{VARIANTS[variant]} #{SIZES[size]}"
31
+ end
32
+ end
33
+
34
+ def initialize(variant: :primary, size: :default, type: :button, **attributes)
35
+ @type = type
36
+ @variant = variant
37
+ @size = size
38
+ super(**attributes)
39
+ end
40
+
41
+ def default_attributes
42
+ { type: @type }
43
+ end
44
+
45
+ def default_styles
46
+ self.class.default_styles(variant: @variant, size: @size)
47
+ end
48
+
49
+ def view_template(&)
50
+ button(**@attributes, &)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Card < Base
5
+ STYLES = "rounded-xl border bg-card text-card-foreground shadow"
6
+
7
+ def header(**attributes, &)
8
+ CardHeader(**attributes, &)
9
+ end
10
+
11
+ def title(**attributes, &)
12
+ CardTitle(**attributes, &)
13
+ end
14
+
15
+ def description(**attributes, &)
16
+ CardDescription(**attributes, &)
17
+ end
18
+
19
+ def content(**attributes, &)
20
+ CardContent(**attributes, &)
21
+ end
22
+
23
+ def footer(**attributes, &)
24
+ CardFooter(**attributes, &)
25
+ end
26
+
27
+ def view_template(&)
28
+ div(**@attributes, &)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class CardContent < Base
5
+ STYLES = "p-6 pt-0"
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 CardDescription < Base
5
+ STYLES = "text-sm text-muted-foreground"
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 CardFooter < Base
5
+ STYLES = "flex items-center p-6 pt-0"
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 CardHeader < Base
5
+ STYLES = "flex flex-col space-y-1.5 p-6"
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 CardTitle < Base
5
+ STYLES = "font-semibold leading-none tracking-tight"
6
+
7
+ def view_template(&)
8
+ div(**@attributes, &)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShadcnPhlexcomponents
4
+ class Checkbox < Base
5
+ STYLES = <<~HEREDOC.freeze
6
+ peer size-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none
7
+ focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50
8
+ data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground relative
9
+ cursor-pointer group/checkbox
10
+ HEREDOC
11
+
12
+ def initialize(name: nil, value: "1", unchecked_value: "0", checked: false, id: nil, include_hidden: true, **attributes)
13
+ @name = name
14
+ @value = value
15
+ @unchecked_value = unchecked_value
16
+ @checked = checked
17
+ @id = id || name
18
+ @include_hidden = include_hidden
19
+ super(**attributes)
20
+ end
21
+
22
+ def view_template(&)
23
+ button(**@attributes) do
24
+ span(class: "absolute inset-0 items-center justify-center text-current
25
+ pointer-events-none hidden group-data-[checked=true]/checkbox:flex") do
26
+ icon("check", class: "size-4")
27
+ end
28
+
29
+ if @include_hidden
30
+ input(name: @name, type: "hidden", value: @unchecked_value, autocomplete: "off")
31
+ end
32
+
33
+ input(
34
+ type: "checkbox",
35
+ value: @value,
36
+ class: "-translate-x-full pointer-events-none absolute top-0 left-0 size-4 opacity-0",
37
+ name: @name,
38
+ tabindex: -1,
39
+ checked: @checked,
40
+ aria: { hidden: true },
41
+ data: {
42
+ "shadcn-phlexcomponents--checkbox-target": "input"
43
+ }
44
+ )
45
+ end
46
+ end
47
+
48
+ def default_attributes
49
+ {
50
+ id: @id,
51
+ type: "button",
52
+ role: "checkbox",
53
+ aria: {
54
+ checked: @checked.to_s,
55
+ },
56
+ data: {
57
+ checked: @checked.to_s,
58
+ controller: "shadcn-phlexcomponents--checkbox",
59
+ action: "click->shadcn-phlexcomponents--checkbox#toggle keydown.enter->shadcn-phlexcomponents--checkbox#preventDefault",
60
+ "shadcn-phlexcomponents--checkbox-checked-value": @checked,
61
+ }
62
+ }
63
+ end
64
+ end
65
+ end