dsfr-view-components 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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