dsfr-view-components 0.0.1

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 (66) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +214 -0
  3. data/Rakefile +22 -0
  4. data/app/components/dsfr_component/accordion_component/section_component.html.erb +11 -0
  5. data/app/components/dsfr_component/accordion_component/section_component.rb +39 -0
  6. data/app/components/dsfr_component/accordion_component.html.erb +5 -0
  7. data/app/components/dsfr_component/accordion_component.rb +33 -0
  8. data/app/components/dsfr_component/alert_component.rb +22 -0
  9. data/app/components/dsfr_component/back_link_component.rb +24 -0
  10. data/app/components/dsfr_component/base.rb +28 -0
  11. data/app/components/dsfr_component/breadcrumbs_component.html.erb +7 -0
  12. data/app/components/dsfr_component/breadcrumbs_component.rb +51 -0
  13. data/app/components/dsfr_component/cookie_banner_component/message_component.rb +62 -0
  14. data/app/components/dsfr_component/cookie_banner_component.rb +35 -0
  15. data/app/components/dsfr_component/details_component.rb +42 -0
  16. data/app/components/dsfr_component/footer_component.html.erb +49 -0
  17. data/app/components/dsfr_component/footer_component.rb +87 -0
  18. data/app/components/dsfr_component/header_component.html.erb +51 -0
  19. data/app/components/dsfr_component/header_component.rb +145 -0
  20. data/app/components/dsfr_component/inset_text_component.rb +28 -0
  21. data/app/components/dsfr_component/notification_banner_component.html.erb +14 -0
  22. data/app/components/dsfr_component/notification_banner_component.rb +93 -0
  23. data/app/components/dsfr_component/pagination_component/adjacent_page.rb +59 -0
  24. data/app/components/dsfr_component/pagination_component/item.rb +77 -0
  25. data/app/components/dsfr_component/pagination_component/next_page.rb +27 -0
  26. data/app/components/dsfr_component/pagination_component/previous_page.rb +21 -0
  27. data/app/components/dsfr_component/pagination_component.rb +129 -0
  28. data/app/components/dsfr_component/panel_component.rb +56 -0
  29. data/app/components/dsfr_component/phase_banner_component.html.erb +6 -0
  30. data/app/components/dsfr_component/phase_banner_component.rb +25 -0
  31. data/app/components/dsfr_component/section_break_component.rb +49 -0
  32. data/app/components/dsfr_component/start_button_component.rb +55 -0
  33. data/app/components/dsfr_component/summary_list_component/action_component.rb +42 -0
  34. data/app/components/dsfr_component/summary_list_component/key_component.rb +23 -0
  35. data/app/components/dsfr_component/summary_list_component/row_component.rb +57 -0
  36. data/app/components/dsfr_component/summary_list_component/value_component.rb +23 -0
  37. data/app/components/dsfr_component/summary_list_component.html.erb +5 -0
  38. data/app/components/dsfr_component/summary_list_component.rb +49 -0
  39. data/app/components/dsfr_component/tab_component.html.erb +11 -0
  40. data/app/components/dsfr_component/tab_component.rb +61 -0
  41. data/app/components/dsfr_component/table_component/body_component.html.erb +5 -0
  42. data/app/components/dsfr_component/table_component/body_component.rb +21 -0
  43. data/app/components/dsfr_component/table_component/caption_component.rb +37 -0
  44. data/app/components/dsfr_component/table_component/cell_component.rb +57 -0
  45. data/app/components/dsfr_component/table_component/head_component.html.erb +5 -0
  46. data/app/components/dsfr_component/table_component/head_component.rb +23 -0
  47. data/app/components/dsfr_component/table_component/row_component.html.erb +5 -0
  48. data/app/components/dsfr_component/table_component/row_component.rb +30 -0
  49. data/app/components/dsfr_component/table_component.html.erb +7 -0
  50. data/app/components/dsfr_component/table_component.rb +35 -0
  51. data/app/components/dsfr_component/tag_component.rb +44 -0
  52. data/app/components/dsfr_component/traits/custom_html_attributes.rb +7 -0
  53. data/app/components/dsfr_component/traits.rb +1 -0
  54. data/app/components/dsfr_component/warning_text_component.rb +37 -0
  55. data/app/components/dsfr_component.rb +1 -0
  56. data/app/helpers/dsfr_back_to_top_link_helper.rb +14 -0
  57. data/app/helpers/dsfr_components_helper.rb +33 -0
  58. data/app/helpers/dsfr_link_helper.rb +123 -0
  59. data/app/helpers/dsfr_skip_link_helper.rb +13 -0
  60. data/config/routes.rb +2 -0
  61. data/lib/dsfr/components/engine.rb +110 -0
  62. data/lib/dsfr/components/helpers/css_utilities.rb +22 -0
  63. data/lib/dsfr/components/version.rb +5 -0
  64. data/lib/dsfr/components.rb +6 -0
  65. data/lib/tasks/dsfr/components_tasks.rake +4 -0
  66. metadata +482 -0
@@ -0,0 +1,129 @@
1
+ class DsfrComponent::PaginationComponent < DsfrComponent::Base
2
+ include Pagy::UrlHelpers
3
+
4
+ attr_reader :pagy,
5
+ :next_text,
6
+ :previous_text,
7
+ :page_items,
8
+ :previous_content,
9
+ :next_content,
10
+ :block_mode,
11
+ :landmark_label
12
+
13
+ alias_method :block_mode?, :block_mode
14
+
15
+ renders_many :items, "DsfrComponent::PaginationComponent::Item"
16
+
17
+ renders_one :next_page, ->(href:, text: default_adjacent_text(:next), label_text: nil, classes: [], html_attributes: {}) do
18
+ DsfrComponent::PaginationComponent::NextPage.new(
19
+ text: text,
20
+ href: href,
21
+ label_text: label_text,
22
+ block_mode: block_mode?,
23
+ classes: classes,
24
+ html_attributes: html_attributes
25
+ )
26
+ end
27
+
28
+ renders_one :previous_page, ->(href:, text: default_adjacent_text(:prev), label_text: nil, classes: [], html_attributes: {}) do
29
+ DsfrComponent::PaginationComponent::PreviousPage.new(
30
+ text: text,
31
+ href: href,
32
+ label_text: label_text,
33
+ block_mode: block_mode?,
34
+ classes: classes,
35
+ html_attributes: html_attributes
36
+ )
37
+ end
38
+
39
+ def initialize(pagy: nil,
40
+ next_text: nil,
41
+ previous_text: nil,
42
+ block_mode: false,
43
+ landmark_label: config.default_pagination_landmark_label,
44
+ classes: [],
45
+ html_attributes: {})
46
+ @pagy = pagy
47
+ @next_text = next_text
48
+ @previous_text = previous_text
49
+ @block_mode = block_mode
50
+ @landmark_label = landmark_label
51
+
52
+ super(classes: classes, html_attributes: html_attributes)
53
+ end
54
+
55
+ def before_render
56
+ @page_items = if pagy.present?
57
+ build_items
58
+ elsif items.any?
59
+ items
60
+ else
61
+ []
62
+ end
63
+
64
+ @previous_content = previous_page || build_previous
65
+ @next_content = next_page || build_next
66
+ end
67
+
68
+ def call
69
+ attributes = html_attributes.tap { |ha| (ha[:class] << "govuk-pagination--block") if items.empty? }
70
+
71
+ tag.nav(**attributes) do
72
+ safe_join([
73
+ previous_content,
74
+ tag.ul(class: "govuk-pagination__list") { safe_join(page_items) },
75
+ next_content
76
+ ])
77
+ end
78
+ end
79
+
80
+ def render?
81
+ # probably isn't any point rendering if there's only one page
82
+ (pagy.present? && pagy.series.size > 1) || @previous_content.present? || @next_content.present?
83
+ end
84
+
85
+ private
86
+
87
+ def default_attributes
88
+ { role: "navigation", aria: { label: landmark_label }, class: %w(govuk-pagination) }
89
+ end
90
+
91
+ def build_previous
92
+ return unless pagy&.prev
93
+
94
+ kwargs = {
95
+ href: pagy_url_for(pagy, pagy.prev),
96
+ text: @previous_text,
97
+ }
98
+
99
+ previous_page(**kwargs.compact)
100
+ end
101
+
102
+ def build_next
103
+ return unless pagy&.next
104
+
105
+ kwargs = {
106
+ href: pagy_url_for(pagy, pagy.next),
107
+ text: @next_text,
108
+ }
109
+
110
+ next_page(**kwargs.compact)
111
+ end
112
+
113
+ def build_items
114
+ pagy.series.map { |i| item(number: i, href: pagy_url_for(pagy, i), from_pagy: true) }
115
+ end
116
+
117
+ def default_adjacent_text(side)
118
+ visible, hidden = *case side
119
+ when :next
120
+ config.default_pagination_next_text
121
+ when :prev
122
+ config.default_pagination_previous_text
123
+ end
124
+
125
+ return visible if hidden.blank?
126
+
127
+ (visible + tag.span(" #{hidden}", class: "govuk-visually-hidden")).html_safe
128
+ end
129
+ end
@@ -0,0 +1,56 @@
1
+ class DsfrComponent::PanelComponent < DsfrComponent::Base
2
+ attr_reader :id, :title_text, :text, :heading_level
3
+
4
+ renders_one :title_html
5
+
6
+ def initialize(title_text: nil, text: nil, heading_level: 1, id: nil, classes: [], html_attributes: {})
7
+ @heading_level = heading_level
8
+ @title_text = title_text
9
+ @text = text
10
+ @id = id
11
+
12
+ super(classes: classes, html_attributes: html_attributes)
13
+ end
14
+
15
+ def call
16
+ tag.div(id: id, **html_attributes) do
17
+ safe_join([panel_title, panel_body].compact)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def default_attributes
24
+ { class: %w(govuk-panel govuk-panel--confirmation) }
25
+ end
26
+
27
+ def heading_tag
28
+ "h#{heading_level}"
29
+ end
30
+
31
+ def panel_content
32
+ content || text
33
+ end
34
+
35
+ def title
36
+ title_html || title_text
37
+ end
38
+
39
+ def panel_title
40
+ return if title.blank?
41
+
42
+ content_tag(heading_tag, title, class: "govuk-panel__title")
43
+ end
44
+
45
+ def panel_body
46
+ return if panel_content.blank?
47
+
48
+ tag.div(class: "govuk-panel__body") do
49
+ panel_content
50
+ end
51
+ end
52
+
53
+ def render?
54
+ title.present? || panel_content.present?
55
+ end
56
+ end
@@ -0,0 +1,6 @@
1
+ <%= tag.div(**html_attributes) do %>
2
+ <p class="govuk-phase-banner__content">
3
+ <%= render(phase_tag_component) %>
4
+ <%= tag.span((content.presence || @text), class: "govuk-phase-banner__text") %>
5
+ </p>
6
+ <% end %>
@@ -0,0 +1,25 @@
1
+ class DsfrComponent::PhaseBannerComponent < DsfrComponent::Base
2
+ attr_reader :text, :phase_tag
3
+
4
+ def initialize(
5
+ tag: { text: config.default_phase_banner_tag },
6
+ text: config.default_phase_banner_text,
7
+ classes: [],
8
+ html_attributes: {}
9
+ )
10
+ @phase_tag = tag
11
+ @text = text
12
+
13
+ super(classes: classes, html_attributes: html_attributes)
14
+ end
15
+
16
+ def phase_tag_component
17
+ DsfrComponent::TagComponent.new(**phase_tag.deep_merge(classes: "govuk-phase-banner__content__tag"))
18
+ end
19
+
20
+ private
21
+
22
+ def default_attributes
23
+ { class: %w(govuk-phase-banner) }
24
+ end
25
+ end
@@ -0,0 +1,49 @@
1
+ class DsfrComponent::SectionBreakComponent < DsfrComponent::Base
2
+ SIZES = %w(m l xl).freeze
3
+
4
+ def initialize(
5
+ visible: config.default_section_break_visible,
6
+ size: config.default_section_break_size,
7
+ classes: [],
8
+ html_attributes: {}
9
+ )
10
+ @visible = visible
11
+ @size = size
12
+
13
+ super(classes: classes, html_attributes: html_attributes)
14
+ end
15
+
16
+ def call
17
+ tag.hr(**html_attributes)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :size, :visible
23
+
24
+ def default_attributes
25
+ { class: default_classes }
26
+ end
27
+
28
+ def default_classes
29
+ class_names(
30
+ "govuk-section-break",
31
+ size_class,
32
+ "govuk-section-break--visible" => visible?
33
+ ).split
34
+ end
35
+
36
+ def size_class
37
+ if size.blank?
38
+ ""
39
+ elsif size.in?(SIZES)
40
+ "govuk-section-break--#{size}"
41
+ else
42
+ raise ArgumentError, "invalid size #{size}, supported sizes are #{SIZES.to_sentence}"
43
+ end
44
+ end
45
+
46
+ def visible?
47
+ visible
48
+ end
49
+ end
@@ -0,0 +1,55 @@
1
+ class DsfrComponent::StartButtonComponent < DsfrComponent::Base
2
+ BUTTON_ATTRIBUTES = {
3
+ draggable: 'false',
4
+ data: { module: 'fr-btn' }
5
+ }.freeze
6
+
7
+ LINK_ATTRIBUTES = BUTTON_ATTRIBUTES.merge({ role: 'button' }).freeze
8
+
9
+ attr_reader :text, :href, :as_button
10
+
11
+ def initialize(text:, href:, as_button: config.default_start_button_as_button, classes: [], html_attributes: {})
12
+ @text = text
13
+ @href = href
14
+ @as_button = as_button
15
+
16
+ super(classes: classes, html_attributes: html_attributes)
17
+ end
18
+
19
+ def call
20
+ if as_button
21
+ button_to(href, **html_attributes) do
22
+ safe_join([text, icon])
23
+ end
24
+ else
25
+ link_to(href, **html_attributes) do
26
+ safe_join([text, icon])
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def default_attributes
34
+ (as_button ? BUTTON_ATTRIBUTES : LINK_ATTRIBUTES)
35
+ .merge({ class: %w(fr-btn fr-btn--start) })
36
+ end
37
+
38
+ def icon
39
+ tag.svg(**svg_attributes) do
40
+ tag.path(fill: "currentColor", d: "M0 0h13l20 20-20 20H0l20-20z")
41
+ end
42
+ end
43
+
44
+ def svg_attributes
45
+ {
46
+ class: "fr-btn__start-icon",
47
+ xmlns: "http://www.w3.org/2000/svg",
48
+ width: "17.5",
49
+ height: "19",
50
+ viewBox: "0 0 33 40",
51
+ focusable: "false",
52
+ aria: { hidden: "true" }
53
+ }
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ class DsfrComponent::SummaryListComponent::ActionComponent < DsfrComponent::Base
2
+ attr_reader :href, :text, :visually_hidden_text, :attributes, :classes
3
+
4
+ def initialize(href: nil, text: 'Change', visually_hidden_text: false, classes: [], html_attributes: {})
5
+ @visually_hidden_text = visually_hidden_text
6
+
7
+ if config.require_summary_list_action_visually_hidden_text && visually_hidden_text == false
8
+ fail(ArgumentError, "missing keyword: visually_hidden_text")
9
+ end
10
+
11
+ super(classes: classes, html_attributes: html_attributes)
12
+
13
+ @href = href
14
+ @text = text
15
+ end
16
+
17
+ def render?
18
+ href.present?
19
+ end
20
+
21
+ def call
22
+ link_to(href, **html_attributes) do
23
+ safe_join([action_text, visually_hidden_span].compact, " ")
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def default_attributes
30
+ link_classes = dsfr_link_classes.append(classes).flatten
31
+
32
+ { class: link_classes }
33
+ end
34
+
35
+ def action_text
36
+ content || text || fail(ArgumentError, "no text or content")
37
+ end
38
+
39
+ def visually_hidden_span
40
+ tag.span(visually_hidden_text, class: "govuk-visually-hidden") if visually_hidden_text.present?
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ class DsfrComponent::SummaryListComponent::KeyComponent < DsfrComponent::Base
2
+ attr_reader :text
3
+
4
+ def initialize(text: nil, classes: [], html_attributes: {})
5
+ @text = text
6
+
7
+ super(classes: classes, html_attributes: html_attributes)
8
+ end
9
+
10
+ def call
11
+ tag.dt(key_content, **html_attributes)
12
+ end
13
+
14
+ private
15
+
16
+ def default_attributes
17
+ { class: %w(govuk-summary-list__key) }
18
+ end
19
+
20
+ def key_content
21
+ content || text || fail(ArgumentError, "no text or content")
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ class DsfrComponent::SummaryListComponent::RowComponent < DsfrComponent::Base
2
+ attr_reader :href, :visually_hidden_text, :show_actions_column
3
+
4
+ renders_one :key, DsfrComponent::SummaryListComponent::KeyComponent
5
+ renders_one :value, DsfrComponent::SummaryListComponent::ValueComponent
6
+ renders_many :actions, DsfrComponent::SummaryListComponent::ActionComponent
7
+
8
+ def initialize(show_actions_column: nil, classes: [], html_attributes: {})
9
+ @show_actions_column = show_actions_column
10
+
11
+ super(classes: classes, html_attributes: html_attributes)
12
+ end
13
+
14
+ def call
15
+ tag.div(**html_attributes) do
16
+ safe_join([key, value, actions_content])
17
+ end
18
+ end
19
+
20
+ def before_render
21
+ if show_actions_column && actions.none?
22
+ html_attributes[:class] << no_actions_class
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def actions_content
29
+ return unless show_actions_column && actions.any?
30
+
31
+ (actions.one?) ? single_action : actions_list
32
+ end
33
+
34
+ def single_action
35
+ tag.dd(class: actions_class) { safe_join(actions) }
36
+ end
37
+
38
+ def actions_list
39
+ tag.dd(class: actions_class) do
40
+ tag.ul(class: "govuk-summary-list__actions-list") do
41
+ safe_join(actions.map { |action| tag.li(action, class: "govuk-summary-list__actions-list-item") })
42
+ end
43
+ end
44
+ end
45
+
46
+ def default_attributes
47
+ { class: %w(govuk-summary-list__row) }
48
+ end
49
+
50
+ def actions_class
51
+ "govuk-summary-list__actions"
52
+ end
53
+
54
+ def no_actions_class
55
+ "govuk-summary-list__row--no-actions"
56
+ end
57
+ end
@@ -0,0 +1,23 @@
1
+ class DsfrComponent::SummaryListComponent::ValueComponent < DsfrComponent::Base
2
+ attr_reader :text
3
+
4
+ def initialize(text: nil, classes: [], html_attributes: {})
5
+ super(classes: classes, html_attributes: html_attributes)
6
+
7
+ @text = text
8
+ end
9
+
10
+ def call
11
+ tag.dd(value_content, **html_attributes)
12
+ end
13
+
14
+ private
15
+
16
+ def default_attributes
17
+ { class: %w(govuk-summary-list__value) }
18
+ end
19
+
20
+ def value_content
21
+ content || text || ""
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ <%= tag.dl(**html_attributes) do %>
2
+ <% rows.each do |row| %>
3
+ <%= row %>
4
+ <% end %>
5
+ <% end %>
@@ -0,0 +1,49 @@
1
+ module DsfrComponent
2
+ class SummaryListComponent < DsfrComponent::Base
3
+ attr_reader :borders, :actions
4
+
5
+ renders_many :rows, ->(classes: [], html_attributes: {}, &block) do
6
+ DsfrComponent::SummaryListComponent::RowComponent.new(
7
+ show_actions_column: @show_actions_column,
8
+ classes: classes,
9
+ html_attributes: html_attributes,
10
+ &block
11
+ )
12
+ end
13
+
14
+ def initialize(rows: nil, actions: true, borders: config.default_summary_list_borders, classes: [], html_attributes: {})
15
+ @borders = borders
16
+ @show_actions_column = actions
17
+
18
+ super(classes: classes, html_attributes: html_attributes)
19
+
20
+ return unless rows.presence
21
+
22
+ build(rows)
23
+ end
24
+
25
+ private
26
+
27
+ def borders_class
28
+ %(govuk-summary-list--no-border) unless borders
29
+ end
30
+
31
+ def default_attributes
32
+ { class: ["govuk-summary-list", borders_class].compact }
33
+ end
34
+
35
+ def build(rows)
36
+ @show_actions_column = rows.any? { |r| r.key?(:actions) }
37
+
38
+ rows.each do |data|
39
+ k, v, a = data.values_at(:key, :value, :actions)
40
+
41
+ row(**data.slice(:classes, :html_attributes)) do |r|
42
+ r.key(**k)
43
+ r.value(**v)
44
+ Array.wrap(a).each { |ad| r.action(**ad) }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ <%= tag.div(**html_attributes) do %>
2
+ <%= tag.h2(title, class: "govuk-tabs__title") %>
3
+ <ul class="govuk-tabs__list">
4
+ <% tabs.each.with_index do |tab, i| %>
5
+ <%= tag.li(tab.li_link, class: tab.li_classes(i)) %>
6
+ <% end %>
7
+ </ul>
8
+ <% tabs.each.with_index do |tab, i| %>
9
+ <%= tag.div(tab, **tab.combined_attributes(i)) %>
10
+ <% end %>
11
+ <% end %>
@@ -0,0 +1,61 @@
1
+ class DsfrComponent::TabComponent < DsfrComponent::Base
2
+ using HTMLAttributesUtils
3
+
4
+ renders_many :tabs, "Tab"
5
+
6
+ attr_reader :title, :id
7
+
8
+ def initialize(title:, id: nil, classes: [], html_attributes: {})
9
+ @title = title
10
+ @id = id
11
+
12
+ super(classes: classes, html_attributes: html_attributes)
13
+ end
14
+
15
+ private
16
+
17
+ def default_attributes
18
+ { id: id, class: %w(govuk-tabs), data: { module: 'govuk-tabs' } }
19
+ end
20
+
21
+ class Tab < DsfrComponent::Base
22
+ attr_reader :label, :text
23
+
24
+ def initialize(label:, text: nil, classes: [], html_attributes: {})
25
+ @label = label
26
+ @text = text
27
+
28
+ super(classes: classes, html_attributes: html_attributes)
29
+ end
30
+
31
+ def id(prefix: nil)
32
+ [prefix, label.parameterize].join
33
+ end
34
+
35
+ def hidden_class(i = nil)
36
+ return [] if i&.zero?
37
+
38
+ %w(fr-tabs__panel--hidden)
39
+ end
40
+
41
+ def li_classes(i = nil)
42
+ class_names("govuk-tabs__list-item", "govuk-tabs__list-item--selected" => i&.zero?).split
43
+ end
44
+
45
+ def li_link
46
+ link_to(label, id(prefix: '#'), class: "govuk-tabs__tab")
47
+ end
48
+
49
+ def default_attributes
50
+ { id: id, class: %w(fr-tabs__panel) }
51
+ end
52
+
53
+ def combined_attributes(i)
54
+ html_attributes.deep_merge_html_attributes({ class: hidden_class(i) }).deep_tidy_html_attributes
55
+ end
56
+
57
+ def call
58
+ content || text || fail(ArgumentError, "no text or content")
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,5 @@
1
+ <%= tag.tbody(**html_attributes) do %>
2
+ <% rows.each do |row| %>
3
+ <%= row %>
4
+ <% end %>
5
+ <% end %>
@@ -0,0 +1,21 @@
1
+ class DsfrComponent::TableComponent::BodyComponent < DsfrComponent::Base
2
+ renders_many :rows, "DsfrComponent::TableComponent::RowComponent"
3
+
4
+ def initialize(rows: nil, first_cell_is_header: false, classes: [], html_attributes: {})
5
+ super(classes: classes, html_attributes: html_attributes)
6
+
7
+ build_rows_from_row_data(rows, first_cell_is_header)
8
+ end
9
+
10
+ private
11
+
12
+ def build_rows_from_row_data(data, first_cell_is_header)
13
+ return if data.blank?
14
+
15
+ data.each { |d| row(cell_data: d, first_cell_is_header: first_cell_is_header) }
16
+ end
17
+
18
+ def default_attributes
19
+ { class: %w(govuk-table__body) }
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ class DsfrComponent::TableComponent::CaptionComponent < DsfrComponent::Base
2
+ attr_reader :text, :size
3
+
4
+ SIZES = %w(s m l xl).freeze
5
+
6
+ def initialize(text: nil, id: nil, size: 'm', classes: [], html_attributes: {})
7
+ @id = id
8
+ @text = text
9
+ @size = size
10
+
11
+ super(classes: classes, html_attributes: html_attributes)
12
+ end
13
+
14
+ def call
15
+ tag.caption(caption_content, **html_attributes)
16
+ end
17
+
18
+ def render?
19
+ caption_content.present?
20
+ end
21
+
22
+ private
23
+
24
+ def caption_content
25
+ @caption_content ||= (content || text)
26
+ end
27
+
28
+ def default_attributes
29
+ { class: class_names("govuk-table__caption", caption_size_class => size).split }
30
+ end
31
+
32
+ def caption_size_class
33
+ fail(ArgumentError, "bad size #{size}, must be in #{SIZES}") unless size.in?(SIZES)
34
+
35
+ %(govuk-table__caption--#{size})
36
+ end
37
+ end