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,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Embed — embedded video/iframe content.
4
+ #
5
+ # Usage:
6
+ # Embed(source: :youtube, embed_id: "dQw4w9WgXcQ")
7
+ # Embed(source: :vimeo, embed_id: "123456", placeholder: "thumb.jpg")
8
+ # Embed(url: "https://example.com/widget")
9
+
10
+ class EmbedComponent < Component
11
+ attribute :source, :string, default: nil
12
+ attribute :embed_id, :string, default: nil
13
+ attribute :url, :string, default: nil
14
+ attribute :placeholder, :string, default: nil
15
+ attribute :icon, :string, default: "video play"
16
+ attribute :autoplay, :boolean, default: true
17
+
18
+ def to_s
19
+ data = { controller: "fui-embed" }
20
+ data[:source] = source if source
21
+ data[:id] = embed_id if embed_id
22
+ data[:url] = url if url
23
+ data[:placeholder] = placeholder if placeholder
24
+
25
+ icon_el = tag.i(class: "#{icon} icon")
26
+ placeholder_el = placeholder ? tag.img(class: "placeholder", src: placeholder) : nil
27
+
28
+ tag.div(class: "ui embed", data: data) {
29
+ safe_join([ icon_el, placeholder_el, @content ])
30
+ }
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Emoji — renders a Fomantic-UI emoji by name.
4
+ #
5
+ # Usage:
6
+ # Emoji(name: "smile")
7
+ # Emoji(name: "thumbsup")
8
+
9
+ class EmojiComponent < Component
10
+ attribute :name, :string, default: nil
11
+
12
+ def to_s
13
+ tag.em(class: "small", data: { emoji: ":#{name}:" })
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Feed — activity/event feed.
4
+ #
5
+ # Usage:
6
+ # Feed(size: :small) {
7
+ # text '<div class="event">...</div>'.html_safe
8
+ # }
9
+
10
+ class FeedComponent < Component
11
+ attribute :size, :string, default: nil
12
+
13
+ def to_s
14
+ classes = class_names(
15
+ "ui",
16
+ size,
17
+ "feed"
18
+ )
19
+
20
+ tag.div(class: classes) { @content }
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Flag — country flag icon.
4
+ #
5
+ # Usage:
6
+ # Flag(country: "us")
7
+ # Flag(country: "fr")
8
+
9
+ class FlagComponent < Component
10
+ attribute :country, :string, default: nil
11
+
12
+ def to_s
13
+ tag.i(class: "#{country} flag")
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Flyout — slide-out panel from screen edge.
4
+ #
5
+ # Usage:
6
+ # Flyout(direction: :right) { |c|
7
+ # c.header { text "Settings" }
8
+ # c.content { text "Flyout body" }
9
+ # c.actions {
10
+ # Button(variant: :primary) { text "Save" }
11
+ # }
12
+ # }
13
+
14
+ class FlyoutComponent < Component
15
+ attribute :direction, :string, default: "right"
16
+ attribute :closable, :boolean, default: true
17
+
18
+ slot :header
19
+ slot :content
20
+ slot :actions
21
+
22
+ def to_s
23
+ classes = class_names(
24
+ "ui",
25
+ direction,
26
+ "flyout"
27
+ )
28
+
29
+ data = { controller: "fui-flyout" }
30
+ data[:fui_flyout_closable_value] = "false" unless closable
31
+
32
+ close_el = closable ? tag.i(class: "close icon") : nil
33
+ header_el = @slots[:header] ? tag.div(class: "header") { @slots[:header] } : nil
34
+ content_el = @slots[:content] ? tag.div(class: "content") { @slots[:content] } : nil
35
+ actions_el = @slots[:actions] ? tag.div(class: "actions") { @slots[:actions] } : nil
36
+
37
+ tag.div(class: classes, data: data) {
38
+ safe_join([ close_el, header_el, content_el, @content.presence, actions_el ])
39
+ }
40
+ end
41
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Form — form layout and validation with Stimulus controller.
4
+ #
5
+ # Usage:
6
+ # Form(action: "/submit", method: :post) {
7
+ # text '<div class="field"><label>Name</label><input type="text"></div>'.html_safe
8
+ # }
9
+ # Form(loading: true, size: :large) { ... }
10
+
11
+ class FormComponent < Component
12
+ attribute :size, :string, default: nil
13
+ attribute :loading, :boolean, default: false
14
+ attribute :success, :boolean, default: false
15
+ attribute :error, :boolean, default: false
16
+ attribute :warning, :boolean, default: false
17
+ attribute :inverted, :boolean, default: false
18
+ attribute :equal_width, :boolean, default: false
19
+ attribute :action, :string, default: nil
20
+ attribute :method_type, :string, default: "post"
21
+ attribute :reply, :boolean, default: false
22
+
23
+ def to_s
24
+ classes = class_names(
25
+ "ui",
26
+ size,
27
+ { "loading" => loading, "success" => success, "error" => error,
28
+ "warning" => warning, "inverted" => inverted,
29
+ "equal width" => equal_width, "reply" => reply },
30
+ "form"
31
+ )
32
+
33
+ opts = { class: classes, data: { controller: "fui-form" } }
34
+ opts[:action] = action if action
35
+ opts[:method] = method_type if action
36
+
37
+ tag.form(**opts) { @content }
38
+ end
39
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Grid — responsive grid system.
4
+ #
5
+ # Usage:
6
+ # Grid(columns: 3, stackable: true) {
7
+ # Column { text "A" }
8
+ # Column { text "B" }
9
+ # Column { text "C" }
10
+ # }
11
+
12
+ class GridComponent < Component
13
+ NUMBERS = %w[one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen].freeze
14
+
15
+ attribute :columns, :integer, default: nil
16
+ attribute :stackable, :boolean, default: false
17
+ attribute :doubling, :boolean, default: false
18
+ attribute :centered, :boolean, default: false
19
+ attribute :divided, :string, default: nil
20
+ attribute :celled, :string, default: nil
21
+ attribute :padded, :string, default: nil
22
+ attribute :relaxed, :string, default: nil
23
+ attribute :equal_width, :boolean, default: false
24
+ attribute :reversed, :string, default: nil
25
+ attribute :container, :boolean, default: false
26
+ attribute :internal, :boolean, default: false
27
+ attribute :inverted, :boolean, default: false
28
+ attribute :aligned, :string, default: nil
29
+
30
+ def to_s
31
+ col_word = columns && columns.between?(1, 16) ? NUMBERS[columns - 1] : nil
32
+
33
+ classes = class_names(
34
+ "ui",
35
+ col_word && "#{col_word} column",
36
+ aligned && "#{aligned} aligned",
37
+ divided_class,
38
+ celled_class,
39
+ padded_class,
40
+ relaxed_class,
41
+ reversed && "#{reversed} reversed",
42
+ { "stackable" => stackable, "doubling" => doubling, "centered" => centered,
43
+ "equal width" => equal_width, "container" => container,
44
+ "internally" => internal, "inverted" => inverted },
45
+ "grid"
46
+ )
47
+
48
+ tag.div(class: classes) { @content }
49
+ end
50
+
51
+ private
52
+
53
+ def divided_class
54
+ case divided
55
+ when "vertically" then "vertically divided"
56
+ when "true", true then "divided"
57
+ when String then "#{divided} divided"
58
+ end
59
+ end
60
+
61
+ def celled_class
62
+ case celled
63
+ when "internally" then "internally celled"
64
+ when "true", true then "celled"
65
+ when String then "#{celled} celled"
66
+ end
67
+ end
68
+
69
+ def padded_class
70
+ case padded
71
+ when "vertically" then "vertically padded"
72
+ when "horizontally" then "horizontally padded"
73
+ when "true", true then "padded"
74
+ when String then "#{padded} padded"
75
+ end
76
+ end
77
+
78
+ def relaxed_class
79
+ case relaxed
80
+ when "very" then "very relaxed"
81
+ when "true", true then "relaxed"
82
+ when String then "#{relaxed} relaxed"
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HStackComponent < Component
4
+ attribute :spacing, :integer, default: 0
5
+ attribute :justify, :string, default: "start"
6
+ attribute :align, :string, default: "start"
7
+ attribute :padding, :integer, default: nil
8
+ attribute :rounded, :string, default: nil
9
+ attribute :shadow, :string, default: nil
10
+ attribute :bg, :string, default: nil
11
+
12
+ def to_s
13
+ style_parts = [
14
+ "display:flex",
15
+ ("gap:#{spacing}px" if spacing > 0),
16
+ ("justify-content:#{justify_value}" if justify != "start"),
17
+ ("align-items:#{align_value}" if align != "start"),
18
+ ("padding:#{padding}px" if padding),
19
+ ("border-radius:#{rounded_value}" if rounded),
20
+ ("background:#{bg}" if bg)
21
+ ].compact.join(";")
22
+
23
+ classes = [
24
+ ("shadow-border-#{shadow}" if shadow)
25
+ ].compact
26
+
27
+ opts = {}
28
+ opts[:class] = classes.join(" ") unless classes.empty?
29
+ opts[:style] = style_parts unless style_parts.empty?
30
+
31
+ tag.div(**opts) { @content }
32
+ end
33
+
34
+ private
35
+
36
+ def justify_value
37
+ case justify
38
+ when "center" then "center"
39
+ when "end" then "flex-end"
40
+ when "between" then "space-between"
41
+ when "around" then "space-around"
42
+ when "evenly" then "space-evenly"
43
+ else "flex-start"
44
+ end
45
+ end
46
+
47
+ def align_value
48
+ case align
49
+ when "center" then "center"
50
+ when "end" then "flex-end"
51
+ when "stretch" then "stretch"
52
+ when "baseline" then "baseline"
53
+ else "flex-start"
54
+ end
55
+ end
56
+
57
+ def rounded_value
58
+ case rounded
59
+ when "sm" then "0.125rem"
60
+ when "md" then "0.375rem"
61
+ when "lg" then "0.5rem"
62
+ when "xl" then "0.75rem"
63
+ when "full" then "9999px"
64
+ else "0.25rem"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Header — page and content headers.
4
+ #
5
+ # Usage:
6
+ # Header(size: :h2) { text "Page Title" }
7
+ # Header(size: :h3, sub: true) { text "Subtitle" }
8
+ # Header(icon: "settings", dividing: true) { text "Settings" }
9
+ # Header(size: :h4, image: true) { |c|
10
+ # c.header_image { Image(src: "avatar.png", size: "mini", rounded: true) }
11
+ # text "Lena"
12
+ # SubHeader { text "Human Resources" }
13
+ # }
14
+
15
+ class HeaderComponent < Component
16
+ attribute :size, :string, default: "h2"
17
+ attribute :sub, :boolean, default: false
18
+ attribute :icon, :string, default: nil
19
+ attribute :image, :boolean, default: false
20
+ attribute :dividing, :boolean, default: false
21
+ attribute :block_header, :boolean, default: false
22
+ attribute :inverted, :boolean, default: false
23
+ attribute :color, :string, default: nil
24
+ attribute :aligned, :string, default: nil
25
+ attribute :attached, :string, default: nil
26
+ attribute :disabled, :boolean, default: false
27
+ attribute :floating, :boolean, default: false
28
+
29
+ slot :header_image
30
+
31
+ def to_s
32
+ classes = class_names(
33
+ "ui",
34
+ color,
35
+ aligned && "#{aligned} aligned",
36
+ attached && "#{attached} attached",
37
+ { "sub" => sub, "dividing" => dividing, "block" => block_header,
38
+ "inverted" => inverted, "disabled" => disabled, "floating" => floating,
39
+ "icon" => icon && !@content, "image" => image },
40
+ "header"
41
+ )
42
+
43
+ tag_name = size.to_s.start_with?("h") ? size.to_s.to_sym : :h2
44
+ icon_el = icon ? tag.i(class: "#{icon} icon") : nil
45
+
46
+ if image && @slots[:header_image]
47
+ # Image header: image outside content div, text + sub header inside content div
48
+ tag.public_send(tag_name, class: classes) {
49
+ safe_join([
50
+ @slots[:header_image],
51
+ tag.div(class: "content") { @content }
52
+ ])
53
+ }
54
+ else
55
+ tag.public_send(tag_name, class: classes) {
56
+ safe_join([ icon_el, @content ])
57
+ }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Icon — icon element using Fomantic-UI icon font.
4
+ #
5
+ # Usage:
6
+ # Icon(name: "home")
7
+ # Icon(name: "heart", color: :red, size: :large)
8
+ # Icon(name: "spinner", loading: true)
9
+
10
+ class IconComponent < Component
11
+ attribute :name, :string, default: nil
12
+ attribute :size, :string, default: nil
13
+ attribute :color, :string, default: nil
14
+ attribute :disabled, :boolean, default: false
15
+ attribute :loading, :boolean, default: false
16
+ attribute :fitted, :boolean, default: false
17
+ attribute :link, :boolean, default: false
18
+ attribute :circular, :boolean, default: false
19
+ attribute :bordered, :boolean, default: false
20
+ attribute :inverted, :boolean, default: false
21
+ attribute :flipped, :string, default: nil
22
+ attribute :rotated, :string, default: nil
23
+ attribute :corner, :string, default: nil
24
+
25
+ def to_s
26
+ classes = class_names(
27
+ name,
28
+ size,
29
+ color,
30
+ flipped && "#{flipped} flipped",
31
+ rotated && "#{rotated} rotated",
32
+ corner && "#{corner} corner",
33
+ { "disabled" => disabled, "loading" => loading, "fitted" => fitted,
34
+ "link" => link, "circular" => circular, "bordered" => bordered,
35
+ "inverted" => inverted },
36
+ "icon"
37
+ )
38
+
39
+ tag.i(class: classes)
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Image — image element. Merges the holy spec primitive with Fomantic-UI image.
4
+ #
5
+ # Usage:
6
+ # Image(src: "photo.jpg")
7
+ # Image(src: "avatar.png", avatar: true, size: :mini)
8
+ # Image(src: "logo.svg", circular: true, centered: true)
9
+
10
+ class ImageComponent < Component
11
+ attribute :src, :string, default: nil
12
+ attribute :alt, :string, default: ""
13
+ attribute :size, :string, default: nil
14
+ attribute :width, :integer, default: nil
15
+ attribute :height, :integer, default: nil
16
+ attribute :shrink, :boolean, default: true
17
+ attribute :rounded, :boolean, default: false
18
+ attribute :circular, :boolean, default: false
19
+ attribute :bordered, :boolean, default: false
20
+ attribute :fluid, :boolean, default: false
21
+ attribute :avatar, :boolean, default: false
22
+ attribute :centered, :boolean, default: false
23
+ attribute :spaced, :string, default: nil
24
+ attribute :hidden, :boolean, default: false
25
+ attribute :disabled, :boolean, default: false
26
+ attribute :inline, :boolean, default: false
27
+
28
+ def to_s
29
+ classes = class_names(
30
+ "ui",
31
+ size,
32
+ spaced && "#{spaced} spaced",
33
+ { "rounded" => rounded, "circular" => circular, "bordered" => bordered,
34
+ "fluid" => fluid, "avatar" => avatar, "centered" => centered,
35
+ "hidden" => hidden, "disabled" => disabled, "inline" => inline },
36
+ "image"
37
+ )
38
+
39
+ opts = { class: classes, src: src, alt: alt }
40
+ opts[:width] = width if width
41
+ opts[:height] = height if height
42
+ opts[:style] = "flex-shrink:0" unless shrink
43
+
44
+ tag.img(**opts)
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Input — text input fields with icon, labeled, and action variants.
4
+ #
5
+ # Usage:
6
+ # Input(placeholder: "Search...", icon: "search")
7
+ # Input(icon: "users", icon_position: "left", placeholder: "Find users...")
8
+ # Input(labeled: true) { text "$" }
9
+
10
+ class InputComponent < Component
11
+ attribute :icon, :string, default: nil
12
+ attribute :icon_position, :string, default: "right"
13
+ attribute :labeled, :boolean, default: false
14
+ attribute :action, :boolean, default: false
15
+ attribute :transparent, :boolean, default: false
16
+ attribute :fluid, :boolean, default: false
17
+ attribute :size, :string, default: nil
18
+ attribute :loading, :boolean, default: false
19
+ attribute :disabled, :boolean, default: false
20
+ attribute :error, :boolean, default: false
21
+ attribute :inverted, :boolean, default: false
22
+ attribute :placeholder, :string, default: nil
23
+ attribute :input_type, :string, default: "text"
24
+ attribute :name, :string, default: nil
25
+ attribute :value, :string, default: nil
26
+
27
+ def to_s
28
+ classes = class_names(
29
+ "ui",
30
+ size,
31
+ { "left icon" => icon && icon_position == "left",
32
+ "icon" => icon && icon_position != "left",
33
+ "labeled" => labeled, "action" => action, "transparent" => transparent,
34
+ "fluid" => fluid, "loading" => loading, "disabled" => disabled,
35
+ "error" => error, "inverted" => inverted },
36
+ "input"
37
+ )
38
+
39
+ input_opts = { type: input_type }
40
+ input_opts[:placeholder] = placeholder if placeholder
41
+ input_opts[:name] = name if name
42
+ input_opts[:value] = value if value
43
+ input_opts[:disabled] = "disabled" if disabled
44
+
45
+ icon_el = icon ? tag.i(class: "#{icon} icon") : nil
46
+ input_el = tag.input(**input_opts)
47
+
48
+ tag.div(class: classes) {
49
+ safe_join([ @content, input_el, icon_el ])
50
+ }
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Item — individual content item within an ItemGroup.
4
+ #
5
+ # Usage:
6
+ # ItemGroup(divided: true) {
7
+ # Item { |c|
8
+ # c.image { Image(src: "product.jpg", size: "small") }
9
+ # c.header { text "Product Name" }
10
+ # c.meta { text "$19.99" }
11
+ # c.description { text "A great product." }
12
+ # c.extra { text "In stock" }
13
+ # }
14
+ # }
15
+
16
+ class ItemComponent < Component
17
+ slot :image
18
+ slot :header
19
+ slot :meta
20
+ slot :description
21
+ slot :extra
22
+
23
+ def to_s
24
+ image_el = @slots[:image] ? tag.div(class: "image") { @slots[:image] } : nil
25
+
26
+ content_parts = [
27
+ (@slots[:header] ? tag.div(class: "header") { @slots[:header] } : nil),
28
+ (@slots[:meta] ? tag.div(class: "meta") { @slots[:meta] } : nil),
29
+ (@slots[:description] ? tag.div(class: "description") { @slots[:description] } : nil),
30
+ (@slots[:extra] ? tag.div(class: "extra") { @slots[:extra] } : nil)
31
+ ].compact
32
+
33
+ content_el = content_parts.any? ? tag.div(class: "content") { safe_join(content_parts) } : nil
34
+
35
+ tag.div(class: "item") {
36
+ safe_join([ image_el, content_el, @content.presence ])
37
+ }
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ItemGroup — container for grouped content items.
4
+ #
5
+ # Usage:
6
+ # ItemGroup(divided: true, relaxed: true) {
7
+ # Item { |c|
8
+ # c.image { Image(src: "photo.jpg", size: "small") }
9
+ # c.header { text "Title" }
10
+ # c.description { text "Description" }
11
+ # }
12
+ # }
13
+
14
+ class ItemGroupComponent < Component
15
+ attribute :divided, :boolean, default: false
16
+ attribute :relaxed, :boolean, default: false
17
+ attribute :link, :boolean, default: false
18
+ attribute :unstackable, :boolean, default: false
19
+
20
+ def to_s
21
+ classes = class_names(
22
+ "ui",
23
+ { "divided" => divided, "relaxed" => relaxed,
24
+ "link" => link, "unstackable" => unstackable },
25
+ "items"
26
+ )
27
+
28
+ tag.div(class: classes) { @content }
29
+ end
30
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Label — labels and tags.
4
+ #
5
+ # Usage:
6
+ # Label(color: :blue) { text "New" }
7
+ # Label(tag_style: true, color: :teal) { text "Featured" }
8
+ # Label(ribbon: true, color: :red) { text "Sale" }
9
+ # Label(icon: "mail", detail: "23") { text "Mail" }
10
+
11
+ class LabelComponent < Component
12
+ include Attachable
13
+
14
+ attribute :color, :string, default: nil
15
+ attribute :size, :string, default: nil
16
+ attribute :pointing, :string, default: nil
17
+ attribute :corner, :string, default: nil
18
+ attribute :ribbon, :boolean, default: false
19
+ attribute :circular, :boolean, default: false
20
+ attribute :floating, :boolean, default: false
21
+ attribute :tag_style, :boolean, default: false
22
+ attribute :detail, :string, default: nil
23
+ attribute :icon, :string, default: nil
24
+ attribute :image, :boolean, default: false
25
+ attribute :basic, :boolean, default: false
26
+ attribute :horizontal, :boolean, default: false
27
+
28
+ def to_s
29
+ classes = class_names(
30
+ "ui",
31
+ color,
32
+ size,
33
+ pointing && "#{pointing} pointing",
34
+ corner && "#{corner} corner",
35
+ { "attached" => attached,
36
+ "ribbon" => ribbon, "circular" => circular, "floating" => floating,
37
+ "tag" => tag_style, "image" => image, "basic" => basic,
38
+ "horizontal" => horizontal },
39
+ "label"
40
+ )
41
+
42
+ icon_el = icon ? tag.i(class: "#{icon} icon") : nil
43
+ detail_el = detail ? tag.div(class: "detail") { detail } : nil
44
+
45
+ tag.div(class: classes) {
46
+ safe_join([ icon_el, @content, detail_el ])
47
+ }
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Link — anchor element.
4
+ #
5
+ # Usage:
6
+ # Link(href: "/about") { text "About Us" }
7
+ # Link(href: "/help", target: "_blank") { text "Help" }
8
+
9
+ class LinkComponent < Component
10
+ attribute :href, :string, default: "#"
11
+ attribute :target, :string, default: nil
12
+ attribute :rel, :string, default: nil
13
+ attribute :css_class, :string, default: nil
14
+
15
+ def to_s
16
+ opts = { href: href }
17
+ opts[:class] = css_class if css_class
18
+ opts[:target] = target if target
19
+ opts[:rel] = rel if rel
20
+
21
+ tag.a(**opts) { @content }
22
+ end
23
+ end