rails-active-ui 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 (167) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +6 -0
  3. data/app/assets/stylesheets.css +73555 -0
  4. data/app/components/accordion_component.rb +34 -0
  5. data/app/components/ad_component.rb +28 -0
  6. data/app/components/api_component.rb +24 -0
  7. data/app/components/breadcrumb_component.rb +26 -0
  8. data/app/components/button_component.rb +49 -0
  9. data/app/components/calendar_component.rb +34 -0
  10. data/app/components/card_component.rb +56 -0
  11. data/app/components/checkbox_component.rb +41 -0
  12. data/app/components/column_component.rb +62 -0
  13. data/app/components/comment_component.rb +45 -0
  14. data/app/components/concerns/alignable.rb +21 -0
  15. data/app/components/concerns/attachable.rb +16 -0
  16. data/app/components/concerns/orientable.rb +21 -0
  17. data/app/components/concerns/positionable.rb +21 -0
  18. data/app/components/concerns/sizeable.rb +18 -0
  19. data/app/components/container_component.rb +23 -0
  20. data/app/components/dimmer_component.rb +30 -0
  21. data/app/components/divider_component.rb +30 -0
  22. data/app/components/dropdown_component.rb +63 -0
  23. data/app/components/embed_component.rb +32 -0
  24. data/app/components/emoji_component.rb +15 -0
  25. data/app/components/feed_component.rb +22 -0
  26. data/app/components/flag_component.rb +15 -0
  27. data/app/components/flyout_component.rb +41 -0
  28. data/app/components/form_component.rb +39 -0
  29. data/app/components/grid_component.rb +85 -0
  30. data/app/components/h_stack_component.rb +67 -0
  31. data/app/components/header_component.rb +60 -0
  32. data/app/components/icon_component.rb +41 -0
  33. data/app/components/image_component.rb +46 -0
  34. data/app/components/input_component.rb +52 -0
  35. data/app/components/item_component.rb +39 -0
  36. data/app/components/item_group_component.rb +30 -0
  37. data/app/components/label_component.rb +49 -0
  38. data/app/components/link_component.rb +23 -0
  39. data/app/components/list_component.rb +39 -0
  40. data/app/components/loader_component.rb +33 -0
  41. data/app/components/menu_component.rb +64 -0
  42. data/app/components/menu_item_component.rb +52 -0
  43. data/app/components/message_component.rb +54 -0
  44. data/app/components/modal_component.rb +50 -0
  45. data/app/components/nag_component.rb +25 -0
  46. data/app/components/overlay_component.rb +16 -0
  47. data/app/components/placeholder_component.rb +39 -0
  48. data/app/components/popup_component.rb +31 -0
  49. data/app/components/progress_component.rb +48 -0
  50. data/app/components/pusher_component.rb +18 -0
  51. data/app/components/rail_component.rb +31 -0
  52. data/app/components/rating_component.rb +41 -0
  53. data/app/components/reset_component.rb +12 -0
  54. data/app/components/reveal_component.rb +39 -0
  55. data/app/components/row_component.rb +39 -0
  56. data/app/components/search_component.rb +44 -0
  57. data/app/components/segment_component.rb +57 -0
  58. data/app/components/segment_group_component.rb +36 -0
  59. data/app/components/shape_component.rb +25 -0
  60. data/app/components/sidebar_component.rb +33 -0
  61. data/app/components/site_component.rb +12 -0
  62. data/app/components/slider_component.rb +46 -0
  63. data/app/components/state_component.rb +25 -0
  64. data/app/components/statistic_component.rb +43 -0
  65. data/app/components/step_component.rb +56 -0
  66. data/app/components/step_group_component.rb +38 -0
  67. data/app/components/sticky_component.rb +22 -0
  68. data/app/components/sub_header_component.rb +15 -0
  69. data/app/components/sub_menu_component.rb +24 -0
  70. data/app/components/tab_component.rb +24 -0
  71. data/app/components/table_cell_component.rb +60 -0
  72. data/app/components/table_component.rb +160 -0
  73. data/app/components/table_row_component.rb +43 -0
  74. data/app/components/text_component.rb +73 -0
  75. data/app/components/toast_component.rb +36 -0
  76. data/app/components/transition_component.rb +32 -0
  77. data/app/components/v_stack_component.rb +31 -0
  78. data/app/components/visibility_component.rb +22 -0
  79. data/app/helpers/component_helper.rb +109 -0
  80. data/app/helpers/fui_helper.rb +53 -0
  81. data/app/javascript/accordion.js +547 -0
  82. data/app/javascript/accordion.min.js +11 -0
  83. data/app/javascript/api.js +1112 -0
  84. data/app/javascript/api.min.js +11 -0
  85. data/app/javascript/calendar.js +1960 -0
  86. data/app/javascript/calendar.min.js +11 -0
  87. data/app/javascript/checkbox.js +819 -0
  88. data/app/javascript/checkbox.min.js +11 -0
  89. data/app/javascript/dimmer.js +686 -0
  90. data/app/javascript/dimmer.min.js +11 -0
  91. data/app/javascript/dropdown.js +4019 -0
  92. data/app/javascript/dropdown.min.js +11 -0
  93. data/app/javascript/embed.js +646 -0
  94. data/app/javascript/embed.min.js +11 -0
  95. data/app/javascript/flyout.js +1405 -0
  96. data/app/javascript/flyout.min.js +11 -0
  97. data/app/javascript/form.js +2070 -0
  98. data/app/javascript/form.min.js +11 -0
  99. data/app/javascript/jquery.js +10716 -0
  100. data/app/javascript/jquery.min.js +2 -0
  101. data/app/javascript/modal.js +1507 -0
  102. data/app/javascript/modal.min.js +11 -0
  103. data/app/javascript/nag.js +522 -0
  104. data/app/javascript/nag.min.js +11 -0
  105. data/app/javascript/popup.js +1457 -0
  106. data/app/javascript/popup.min.js +11 -0
  107. data/app/javascript/progress.js +922 -0
  108. data/app/javascript/progress.min.js +11 -0
  109. data/app/javascript/rating.js +496 -0
  110. data/app/javascript/rating.min.js +11 -0
  111. data/app/javascript/search.js +1519 -0
  112. data/app/javascript/search.min.js +11 -0
  113. data/app/javascript/shape.js +721 -0
  114. data/app/javascript/shape.min.js +11 -0
  115. data/app/javascript/sidebar.js +952 -0
  116. data/app/javascript/sidebar.min.js +11 -0
  117. data/app/javascript/site.js +415 -0
  118. data/app/javascript/site.min.js +11 -0
  119. data/app/javascript/slider.js +1449 -0
  120. data/app/javascript/slider.min.js +11 -0
  121. data/app/javascript/state.js +653 -0
  122. data/app/javascript/state.min.js +11 -0
  123. data/app/javascript/sticky.js +852 -0
  124. data/app/javascript/sticky.min.js +11 -0
  125. data/app/javascript/tab.js +867 -0
  126. data/app/javascript/tab.min.js +11 -0
  127. data/app/javascript/toast.js +916 -0
  128. data/app/javascript/toast.min.js +11 -0
  129. data/app/javascript/transition.js +955 -0
  130. data/app/javascript/transition.min.js +11 -0
  131. data/app/javascript/ui/controllers/fui_accordion_controller.js +45 -0
  132. data/app/javascript/ui/controllers/fui_api_controller.js +80 -0
  133. data/app/javascript/ui/controllers/fui_calendar_controller.js +66 -0
  134. data/app/javascript/ui/controllers/fui_checkbox_controller.js +48 -0
  135. data/app/javascript/ui/controllers/fui_dimmer_controller.js +45 -0
  136. data/app/javascript/ui/controllers/fui_dropdown_controller.js +68 -0
  137. data/app/javascript/ui/controllers/fui_embed_controller.js +49 -0
  138. data/app/javascript/ui/controllers/fui_flyout_controller.js +49 -0
  139. data/app/javascript/ui/controllers/fui_form_controller.js +62 -0
  140. data/app/javascript/ui/controllers/fui_modal_controller.js +61 -0
  141. data/app/javascript/ui/controllers/fui_nag_controller.js +52 -0
  142. data/app/javascript/ui/controllers/fui_popup_controller.js +58 -0
  143. data/app/javascript/ui/controllers/fui_progress_controller.js +60 -0
  144. data/app/javascript/ui/controllers/fui_rating_controller.js +49 -0
  145. data/app/javascript/ui/controllers/fui_search_controller.js +76 -0
  146. data/app/javascript/ui/controllers/fui_shape_controller.js +45 -0
  147. data/app/javascript/ui/controllers/fui_sidebar_controller.js +48 -0
  148. data/app/javascript/ui/controllers/fui_site_controller.js +29 -0
  149. data/app/javascript/ui/controllers/fui_slider_controller.js +53 -0
  150. data/app/javascript/ui/controllers/fui_state_controller.js +63 -0
  151. data/app/javascript/ui/controllers/fui_sticky_controller.js +50 -0
  152. data/app/javascript/ui/controllers/fui_tab_controller.js +57 -0
  153. data/app/javascript/ui/controllers/fui_toast_controller.js +60 -0
  154. data/app/javascript/ui/controllers/fui_transition_controller.js +60 -0
  155. data/app/javascript/ui/controllers/fui_visibility_controller.js +55 -0
  156. data/app/javascript/ui/index.js +114 -0
  157. data/app/javascript/visibility.js +1196 -0
  158. data/app/javascript/visibility.min.js +11 -0
  159. data/app/lib/component.rb +63 -0
  160. data/config/importmap.rb +27 -0
  161. data/config/initializers/ruby_template_handler.rb +31 -0
  162. data/config/routes.rb +2 -0
  163. data/lib/tasks/ui_tasks.rake +4 -0
  164. data/lib/ui/engine.rb +27 -0
  165. data/lib/ui/version.rb +3 -0
  166. data/lib/ui.rb +6 -0
  167. metadata +220 -0
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Accordion — collapsible content panels.
4
+ #
5
+ # Usage:
6
+ # Accordion(styled: true) {
7
+ # MenuItem(header: true) {
8
+ # Icon(name: "dropdown")
9
+ # text " Section"
10
+ # }
11
+ # text "Panel content"
12
+ # }
13
+
14
+ class AccordionComponent < Component
15
+ attribute :styled, :boolean, default: false
16
+ attribute :fluid, :boolean, default: false
17
+ attribute :inverted, :boolean, default: false
18
+ attribute :exclusive, :boolean, default: true
19
+ attribute :collapsible, :boolean, default: true
20
+
21
+ def to_s
22
+ classes = class_names(
23
+ "ui",
24
+ { "styled" => styled, "fluid" => fluid, "inverted" => inverted },
25
+ "accordion"
26
+ )
27
+
28
+ data = { controller: "fui-accordion" }
29
+ data[:fui_accordion_exclusive_value] = "false" unless exclusive
30
+ data[:fui_accordion_collapsible_value] = "false" unless collapsible
31
+
32
+ tag.div(class: classes, data: data) { @content }
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ad — standard ad unit sizes following IAB standards.
4
+ #
5
+ # Usage:
6
+ # Ad(unit: :leaderboard)
7
+ # Ad(unit: :medium_rectangle, test: "Ad Placeholder")
8
+
9
+ class AdComponent < Component
10
+ attribute :unit, :string, default: nil
11
+ attribute :test, :string, default: nil
12
+
13
+ def to_s
14
+ unit_class = unit.to_s.tr("_", " ")
15
+
16
+ classes = class_names(
17
+ "ui",
18
+ unit_class,
19
+ { "test" => test },
20
+ "ad"
21
+ )
22
+
23
+ opts = { class: classes }
24
+ opts[:data] = { text: test } if test
25
+
26
+ tag.div(**opts) { @content }
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Api — AJAX request management wrapper.
4
+ #
5
+ # Usage:
6
+ # Api(url: "/api/users", method_val: :get) {
7
+ # Button { text "Load Users" }
8
+ # }
9
+
10
+ class ApiComponent < Component
11
+ attribute :url, :string, default: nil
12
+ attribute :method_val, :string, default: "get"
13
+ attribute :action_val, :string, default: nil
14
+ attribute :state_context, :string, default: nil
15
+
16
+ def to_s
17
+ data = { controller: "fui-api", fui_api_method_value: method_val }
18
+ data[:fui_api_url_value] = url if url
19
+ data[:fui_api_action_value] = action_val if action_val
20
+ data[:fui_api_state_context_value] = state_context if state_context
21
+
22
+ tag.div(data: data) { @content }
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Breadcrumb — navigation breadcrumb trail.
4
+ #
5
+ # Usage:
6
+ # Breadcrumb {
7
+ # text '<a class="section">Home</a>'.html_safe
8
+ # text '<div class="divider">/</div>'.html_safe
9
+ # text '<div class="active section">Current</div>'.html_safe
10
+ # }
11
+ # Breadcrumb(size: :large)
12
+
13
+ class BreadcrumbComponent < Component
14
+ attribute :size, :string, default: nil
15
+ attribute :divider, :string, default: nil
16
+
17
+ def to_s
18
+ classes = class_names(
19
+ "ui",
20
+ size,
21
+ "breadcrumb"
22
+ )
23
+
24
+ tag.div(class: classes) { @content }
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Button — buttons in many styles.
4
+ #
5
+ # Usage:
6
+ # Button(color: :green, icon: "checkmark") { text "Approve" }
7
+ # Button(variant: :primary, size: :large, fluid: true) { text "Submit" }
8
+ # Button(href: "/path", variant: :primary) { text "Go" }
9
+
10
+ class ButtonComponent < Component
11
+ include Attachable
12
+ include Sizeable
13
+
14
+ attribute :color, :string, default: nil
15
+ attribute :variant, :string, default: nil
16
+ attribute :icon, :string, default: nil
17
+ attribute :fluid, :boolean, default: false
18
+ attribute :disabled, :boolean, default: false
19
+ attribute :loading, :boolean, default: false
20
+ attribute :animated, :string, default: nil
21
+ attribute :labeled, :boolean, default: false
22
+ attribute :href, :string, default: nil
23
+ attribute :type, :string, default: "button"
24
+ attribute :inverted, :boolean, default: false
25
+
26
+ def to_s
27
+ classes = class_names(
28
+ "ui",
29
+ color,
30
+ size,
31
+ variant,
32
+ animated && "animated #{animated unless animated == 'true'}".strip,
33
+ { "fluid" => fluid, "disabled" => disabled, "loading" => loading,
34
+ "inverted" => inverted, "attached" => attached,
35
+ "labeled icon" => labeled && icon,
36
+ "icon" => icon && !labeled && !@content },
37
+ "button"
38
+ )
39
+
40
+ icon_el = icon ? tag.i(class: "#{icon} icon") : nil
41
+ content = safe_join([ icon_el, @content ])
42
+
43
+ if href
44
+ tag.a(class: classes, href: href) { content }
45
+ else
46
+ tag.button(class: classes, type: type) { content }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Calendar — date/time picker.
4
+ #
5
+ # Usage:
6
+ # Calendar(type: :date, name: "start_date")
7
+ # Calendar(type: :datetime, format: "YYYY-MM-DD HH:mm")
8
+
9
+ class CalendarComponent < Component
10
+ attribute :type, :string, default: "date"
11
+ attribute :name, :string, default: nil
12
+ attribute :format, :string, default: nil
13
+ attribute :min_date, :string, default: nil
14
+ attribute :max_date, :string, default: nil
15
+
16
+ def to_s
17
+ data = { controller: "fui-calendar", fui_calendar_type_value: type }
18
+ data[:fui_calendar_format_value] = format if format
19
+ data[:fui_calendar_min_date_value] = min_date if min_date
20
+ data[:fui_calendar_max_date_value] = max_date if max_date
21
+
22
+ input_opts = { type: "text", placeholder: type }
23
+ input_opts[:name] = name if name
24
+
25
+ tag.div(class: "ui calendar", data: data) {
26
+ tag.div(class: "ui input left icon") {
27
+ safe_join([
28
+ tag.i(class: "calendar icon"),
29
+ tag.input(**input_opts)
30
+ ])
31
+ }
32
+ }
33
+ end
34
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Card — content cards with multiple regions.
4
+ #
5
+ # Usage:
6
+ # Card(fluid: true) { |c|
7
+ # c.image { Image(src: "photo.jpg") }
8
+ # c.header { text "Title" }
9
+ # c.meta { text "Subtitle" }
10
+ # c.description { text "Body text" }
11
+ # c.extra { text "Extra content" }
12
+ # }
13
+
14
+ class CardComponent < Component
15
+ attribute :fluid, :boolean, default: false
16
+ attribute :centered, :boolean, default: false
17
+ attribute :raised, :boolean, default: false
18
+ attribute :link, :boolean, default: false
19
+ attribute :color, :string, default: nil
20
+ attribute :href, :string, default: nil
21
+
22
+ slot :image
23
+ slot :header
24
+ slot :meta
25
+ slot :description
26
+ slot :extra
27
+
28
+ def to_s
29
+ classes = class_names(
30
+ "ui",
31
+ color,
32
+ { "fluid" => fluid, "centered" => centered, "raised" => raised,
33
+ "link" => link || href },
34
+ "card"
35
+ )
36
+
37
+ image_el = @slots[:image] ? tag.div(class: "image") { @slots[:image] } : nil
38
+
39
+ content_parts = [
40
+ @slots[:header] ? tag.div(class: "header") { @slots[:header] } : nil,
41
+ @slots[:meta] ? tag.div(class: "meta") { @slots[:meta] } : nil,
42
+ @slots[:description] ? tag.div(class: "description") { @slots[:description] } : nil
43
+ ].compact
44
+
45
+ content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
46
+ extra_el = @slots[:extra] ? tag.div(class: "extra content") { @slots[:extra] } : nil
47
+
48
+ inner = safe_join([ image_el, content_el, @content.presence, extra_el ])
49
+
50
+ if href
51
+ tag.a(class: classes, href: href) { inner }
52
+ else
53
+ tag.div(class: classes) { inner }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Checkbox — checkboxes, radios, toggles, sliders.
4
+ #
5
+ # Usage:
6
+ # Checkbox(label_text: "Accept terms", name: "terms")
7
+ # Checkbox(type: :toggle, label_text: "Dark mode", checked: true)
8
+ # Checkbox(type: :radio, label_text: "Option A", name: "choice", value: "a")
9
+
10
+ class CheckboxComponent < Component
11
+ attribute :type, :string, default: "checkbox"
12
+ attribute :label_text, :string, default: nil
13
+ attribute :name, :string, default: nil
14
+ attribute :value, :string, default: nil
15
+ attribute :checked, :boolean, default: false
16
+ attribute :disabled, :boolean, default: false
17
+ attribute :read_only, :boolean, default: false
18
+ attribute :fitted, :boolean, default: false
19
+
20
+ def to_s
21
+ classes = class_names(
22
+ "ui",
23
+ (type unless type == "checkbox"),
24
+ { "fitted" => fitted, "read-only" => read_only, "disabled" => disabled },
25
+ "checkbox"
26
+ )
27
+
28
+ field_type = type == "radio" ? "radio" : "checkbox"
29
+ input_opts = { type: field_type }
30
+ input_opts[:name] = name if name
31
+ input_opts[:value] = value if value
32
+ input_opts[:checked] = "checked" if checked
33
+ input_opts[:disabled] = "disabled" if disabled
34
+
35
+ label_el = tag.label { label_text || "" }
36
+
37
+ tag.div(class: classes, data: { controller: "fui-checkbox" }) {
38
+ safe_join([ tag.input(**input_opts), label_el ])
39
+ }
40
+ end
41
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Column — grid column with responsive width support.
4
+ #
5
+ # Usage:
6
+ # Grid(stackable: true) {
7
+ # Column(width: 4) { text "Four wide" }
8
+ # Column(computer: 8, tablet: 16) { text "Responsive" }
9
+ # Column(computer: 4, tablet: 8, mobile: 16, aligned: "center") { text "All breakpoints" }
10
+ # }
11
+
12
+ class ColumnComponent < Component
13
+ NUMBERS = %w[one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen].freeze
14
+
15
+ attribute :width, :integer, default: nil
16
+ attribute :computer, :integer, default: nil
17
+ attribute :tablet, :integer, default: nil
18
+ attribute :mobile, :integer, default: nil
19
+ attribute :widescreen, :integer, default: nil
20
+ attribute :large_screen, :integer, default: nil
21
+ attribute :aligned, :string, default: nil
22
+ attribute :floated, :string, default: nil
23
+ attribute :only, :string, default: nil
24
+ attribute :color, :string, default: nil
25
+
26
+ def to_s
27
+ # Cannot use class_names here — it deduplicates tokens, which breaks
28
+ # responsive widths where "wide" appears for each breakpoint.
29
+ classes = [
30
+ width_class(width, nil),
31
+ width_class(computer, "computer"),
32
+ width_class(tablet, "tablet"),
33
+ width_class(mobile, "mobile"),
34
+ width_class(widescreen, "widescreen"),
35
+ width_class(large_screen, "large screen"),
36
+ only_class,
37
+ (aligned && "#{aligned} aligned"),
38
+ (floated && "#{floated} floated"),
39
+ color,
40
+ "column"
41
+ ].compact.join(" ")
42
+
43
+ tag.div(class: classes) { @content }
44
+ end
45
+
46
+ private
47
+
48
+ # Supports single ("mobile") or multiple ("tablet mobile") only values.
49
+ # Each device word gets "only" appended: "tablet only mobile only"
50
+ def only_class
51
+ return unless only
52
+
53
+ only.split.map { |device| "#{device} only" }.join(" ")
54
+ end
55
+
56
+ def width_class(value, device)
57
+ return unless value && value.between?(1, 16)
58
+
59
+ word = NUMBERS[value - 1]
60
+ device ? "#{word} wide #{device}" : "#{word} wide"
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Comment — threaded comment display.
4
+ #
5
+ # Usage:
6
+ # Comment { |c|
7
+ # c.avatar { Image(src: "avatar.png") }
8
+ # c.author { text "Matt" }
9
+ # c.metadata { text "Today at 5:42PM" }
10
+ # c.text_slot { text "Great post!" }
11
+ # c.actions { text '<a class="reply">Reply</a>'.html_safe }
12
+ # }
13
+
14
+ class CommentComponent < Component
15
+ attribute :collapsed, :boolean, default: false
16
+ attribute :threaded, :boolean, default: false
17
+
18
+ slot :avatar
19
+ slot :author
20
+ slot :metadata
21
+ slot :text_slot
22
+ slot :actions
23
+
24
+ def to_s
25
+ classes = class_names(
26
+ { "collapsed" => collapsed },
27
+ "comment"
28
+ )
29
+
30
+ avatar_el = @slots[:avatar] ? tag.a(class: "avatar") { @slots[:avatar] } : nil
31
+
32
+ content_parts = [
33
+ @slots[:author] ? tag.a(class: "author") { @slots[:author] } : nil,
34
+ @slots[:metadata] ? tag.div(class: "metadata") { @slots[:metadata] } : nil,
35
+ @slots[:text_slot] ? tag.div(class: "text") { @slots[:text_slot] } : nil,
36
+ @slots[:actions] ? tag.div(class: "actions") { @slots[:actions] } : nil
37
+ ].compact
38
+
39
+ content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
40
+
41
+ tag.div(class: classes) {
42
+ safe_join([ avatar_el, content_el, @content.presence ])
43
+ }
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Alignable — adds an `aligned` string attribute (vertical / horizontal).
4
+ #
5
+ # Usage:
6
+ # class GridComponent < Component
7
+ # include Alignable # default: nil
8
+ # end
9
+ #
10
+ # class ColumnComponent < Component
11
+ # include Alignable
12
+ # default aligned: "vertical" # override default
13
+ # end
14
+
15
+ module Alignable
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ attribute :aligned, :string, default: nil
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Attachable — adds an `attached` boolean attribute to a component.
4
+ #
5
+ # Usage:
6
+ # class ButtonComponent < Component
7
+ # include Attachable
8
+ # end
9
+
10
+ module Attachable
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ attribute :attached, :boolean, default: false
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Orientable — adds an `orientation` string attribute (vertical / horizontal).
4
+ #
5
+ # Usage:
6
+ # class DividerComponent < Component
7
+ # include Orientable # default: nil
8
+ # end
9
+ #
10
+ # class MenuComponent < Component
11
+ # include Orientable
12
+ # default orientation: "horizontal" # override default
13
+ # end
14
+
15
+ module Orientable
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ attribute :orientation, :string, default: nil
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Positionable — adds a `position` string attribute to a component.
4
+ #
5
+ # Usage:
6
+ # class MenuComponent < Component
7
+ # include Positionable # default: nil
8
+ # end
9
+ #
10
+ # class RailComponent < Component
11
+ # include Positionable
12
+ # default position: "left" # override default
13
+ # end
14
+
15
+ module Positionable
16
+ extend ActiveSupport::Concern
17
+
18
+ included do
19
+ attribute :position, :string, default: nil
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Sizeable — adds a `size` string attribute.
4
+ #
5
+ # Valid sizes: mini, tiny, small, large, big, huge, massive
6
+ #
7
+ # Usage:
8
+ # class ButtonComponent < Component
9
+ # include Sizeable
10
+ # end
11
+
12
+ module Sizeable
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ attribute :size, :string, default: nil
17
+ end
18
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Container — fixed-width page container.
4
+ #
5
+ # Usage:
6
+ # Container { text "Page content" }
7
+ # Container(text: true) { text "Narrower text container" }
8
+ # Container(fluid: true) { text "Full-width" }
9
+
10
+ class ContainerComponent < Component
11
+ attribute :text, :boolean, default: false
12
+ attribute :fluid, :boolean, default: false
13
+
14
+ def to_s
15
+ classes = class_names(
16
+ "ui",
17
+ { "text" => text, "fluid" => fluid },
18
+ "container"
19
+ )
20
+
21
+ tag.div(class: classes) { @content }
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Dimmer — page/element overlay dimmer.
4
+ #
5
+ # Usage:
6
+ # Dimmer(active: true) { text "Dimmed content" }
7
+ # Dimmer(inverted: true, active: true) {
8
+ # Loader(active: true) { text "Loading..." }
9
+ # }
10
+
11
+ class DimmerComponent < Component
12
+ attribute :active, :boolean, default: false
13
+ attribute :inverted, :boolean, default: false
14
+ attribute :blurring, :boolean, default: false
15
+ attribute :simple, :boolean, default: false
16
+ attribute :page, :boolean, default: false
17
+
18
+ def to_s
19
+ classes = class_names(
20
+ "ui",
21
+ { "active" => active, "inverted" => inverted, "blurring" => blurring,
22
+ "simple" => simple, "page" => page },
23
+ "dimmer"
24
+ )
25
+
26
+ content_el = @content.present? ? tag.div(class: "content") { @content } : nil
27
+
28
+ tag.div(class: classes, data: { controller: "fui-dimmer" }) { content_el }
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Divider — horizontal/vertical dividers, optionally with text or icon.
4
+ #
5
+ # Usage:
6
+ # Divider()
7
+ # Divider(horizontal: true) { text "OR" }
8
+ # Divider(section: true, inverted: true)
9
+
10
+ class DividerComponent < Component
11
+ attribute :vertical, :boolean, default: false
12
+ attribute :horizontal, :boolean, default: false
13
+ attribute :hidden, :boolean, default: false
14
+ attribute :section, :boolean, default: false
15
+ attribute :inverted, :boolean, default: false
16
+ attribute :fitted, :boolean, default: false
17
+ attribute :clearing, :boolean, default: false
18
+
19
+ def to_s
20
+ classes = class_names(
21
+ "ui",
22
+ { "vertical" => vertical, "horizontal" => horizontal, "hidden" => hidden,
23
+ "section" => section, "inverted" => inverted, "fitted" => fitted,
24
+ "clearing" => clearing },
25
+ "divider"
26
+ )
27
+
28
+ tag.div(class: classes) { @content }
29
+ end
30
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Dropdown — selection dropdowns with search and multi-select.
4
+ #
5
+ # Usage:
6
+ # Dropdown(selection: true, placeholder: "Select...", name: "country") {
7
+ # MenuItem { text "United States" }
8
+ # MenuItem { text "Canada" }
9
+ # }
10
+
11
+ class DropdownComponent < Component
12
+ attribute :selection, :boolean, default: false
13
+ attribute :search, :boolean, default: false
14
+ attribute :multiple, :boolean, default: false
15
+ attribute :clearable, :boolean, default: false
16
+ attribute :fluid, :boolean, default: false
17
+ attribute :placeholder, :string, default: nil
18
+ attribute :name, :string, default: nil
19
+ attribute :default_value, :string, default: nil
20
+ attribute :pointing, :string, default: nil
21
+ attribute :compact, :boolean, default: false
22
+ attribute :scrolling, :boolean, default: false
23
+ attribute :inline, :boolean, default: false
24
+ attribute :floating, :boolean, default: false
25
+ attribute :button, :boolean, default: false
26
+ attribute :labeled, :boolean, default: false
27
+ attribute :loading, :boolean, default: false
28
+ attribute :disabled, :boolean, default: false
29
+
30
+ def to_s
31
+ classes = class_names(
32
+ "ui",
33
+ pointing && "#{pointing} pointing",
34
+ { "selection" => selection, "search" => search, "multiple" => multiple,
35
+ "clearable" => clearable, "fluid" => fluid, "compact" => compact,
36
+ "scrolling" => scrolling, "inline" => inline, "floating" => floating,
37
+ "button" => button, "labeled" => labeled, "loading" => loading,
38
+ "disabled" => disabled },
39
+ "dropdown"
40
+ )
41
+
42
+ data = { controller: "fui-dropdown" }
43
+ data[:fui_dropdown_clearable_value] = "true" if clearable
44
+ data[:fui_dropdown_placeholder_value] = placeholder if placeholder
45
+
46
+ hidden_opts = { type: "hidden", value: default_value || "" }
47
+ hidden_opts[:name] = name if name
48
+
49
+ search_el = search ? tag.input(class: "search") : nil
50
+ text_el = tag.div(class: "default text") { placeholder || "" }
51
+ menu_el = tag.div(class: "menu") { @content }
52
+
53
+ tag.div(class: classes, data: data) {
54
+ safe_join([
55
+ tag.input(**hidden_opts),
56
+ search_el,
57
+ text_el,
58
+ tag.i(class: "dropdown icon"),
59
+ menu_el
60
+ ])
61
+ }
62
+ end
63
+ end