dsfr-view-components 2.1.1 → 2.2.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/app/components/dsfr_component/accordion_component/section_component.html.erb +1 -1
  3. data/app/components/dsfr_component/accordion_component/section_component.rb +3 -2
  4. data/app/components/dsfr_component/accordion_component.rb +4 -7
  5. data/app/components/dsfr_component/alert_component.rb +44 -19
  6. data/app/components/dsfr_component/badge_component.rb +3 -5
  7. data/app/components/dsfr_component/base.rb +1 -14
  8. data/app/components/dsfr_component/breadcrumbs_component.rb +0 -4
  9. data/app/components/dsfr_component/button_component.rb +2 -2
  10. data/app/components/dsfr_component/callout_component.rb +41 -0
  11. data/app/components/dsfr_component/header_component/direct_link_component.rb +2 -2
  12. data/app/components/dsfr_component/header_component/direct_link_dropdown_component.rb +2 -2
  13. data/app/components/dsfr_component/header_component/operator_image_component.rb +2 -2
  14. data/app/components/dsfr_component/header_component/tool_link_component.rb +2 -2
  15. data/app/components/dsfr_component/header_component.rb +2 -2
  16. data/app/components/dsfr_component/highlight_component.rb +2 -2
  17. data/app/components/dsfr_component/modal_component.rb +2 -2
  18. data/app/components/dsfr_component/notice_component.html.erb +14 -0
  19. data/app/components/dsfr_component/notice_component.rb +99 -0
  20. data/app/components/dsfr_component/search_component.html.erb +8 -0
  21. data/app/components/dsfr_component/search_component.rb +50 -0
  22. data/app/components/dsfr_component/skiplink_component.rb +2 -2
  23. data/app/components/dsfr_component/stepper_component.html.erb +1 -1
  24. data/app/components/dsfr_component/stepper_component.rb +2 -2
  25. data/app/components/dsfr_component/tabs_component/tab_component.rb +2 -2
  26. data/app/components/dsfr_component/tabs_component.rb +2 -2
  27. data/app/components/dsfr_component/tag_component.rb +2 -2
  28. data/app/components/dsfr_component/tile_component.rb +2 -2
  29. data/app/components/dsfr_component/traits/header_sizeable.rb +25 -0
  30. data/app/helpers/dsfr_components_helper.rb +3 -0
  31. data/lib/dsfr/components/version.rb +1 -1
  32. data/lib/generators/dsfr_component/templates/component.haml.erb +1 -1
  33. metadata +16 -34
  34. data/app/helpers/dsfr_skip_link_helper.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fd43d49e7920aa1ae37d143b9503c68b1d44aad46e5a3acde610b7a0ae9da96
4
- data.tar.gz: b3a7d11b90ff73cad4023f517bd3d567141648e25cdf200b1204daf2dcde8db7
3
+ metadata.gz: c3abead9a7b02e13cbbe5cfa9e1d9efa24db78ff92e6560920823e232da2686a
4
+ data.tar.gz: 1257756ffd30ee9e631a095967f7c430b7902516952b4f328e8ba9f54825c994
5
5
  SHA512:
6
- metadata.gz: 4508aef35cb1afd72ca39df0e89c74290f8a4f9ebfe47c8ff2b7ce8fbcb4fba3ff2d647ecfcc41eb585b515ef378716496c1355e917d2a7b59fc38ba3faa93a5
7
- data.tar.gz: f9391b2ef8ddfcdd985305d823ded7fb73add4d05eb05c8be8d30cec1a7cdf2d07b6d1df44bbd5ae18a609b5fb7d9934b54c192715449321ff2493638919f570
6
+ metadata.gz: 93de62ccadb3a81c1991ff9f85c42e4f52e0dad62513b958656b20eac58b6a376f67b5394ea395def361bb7cc22e08a26a16681783eec5d2c3889ae5bc3128da
7
+ data.tar.gz: 79b0c4176685a04c0875ccd70a015ba70aeae6ff948fcf907538211e98880cde86dae51097edd14e0a45f69f8f4866c6a2c6cfe7b60aa361b609a463e14aa67a
@@ -1,5 +1,5 @@
1
1
  <%= tag.section(**html_attributes) do %>
2
- <%= tag.send("h#{starting_header_level}", class: "fr-accordion__title") do %>
2
+ <%= tag.send(starting_header_tag, class: "fr-accordion__title") do %>
3
3
  <button
4
4
  class="fr-accordion__btn"
5
5
  aria-expanded="<%= expanded? ? "true" : "false" %>"
@@ -1,4 +1,6 @@
1
1
  class DsfrComponent::AccordionComponent::SectionComponent < DsfrComponent::Base
2
+ include DsfrComponent::Traits::HeaderSizeable
3
+
2
4
  attr_reader :title, :expanded, :starting_header_level
3
5
 
4
6
  alias_method :expanded?, :expanded
@@ -11,7 +13,6 @@ class DsfrComponent::AccordionComponent::SectionComponent < DsfrComponent::Base
11
13
  starting_header_level:,
12
14
  expanded: false,
13
15
  id: nil,
14
- classes: [],
15
16
  html_attributes: {}
16
17
  )
17
18
  @title = title
@@ -19,7 +20,7 @@ class DsfrComponent::AccordionComponent::SectionComponent < DsfrComponent::Base
19
20
  @id = id
20
21
  @starting_header_level = starting_header_level
21
22
 
22
- super(classes: classes, html_attributes: html_attributes)
23
+ super(html_attributes: html_attributes)
23
24
  end
24
25
 
25
26
  def id
@@ -1,11 +1,8 @@
1
1
  class DsfrComponent::AccordionComponent < DsfrComponent::Base
2
- DEFAULT_HEADER_LEVEL = 3
2
+ include DsfrComponent::Traits::HeaderSizeable
3
3
 
4
- attr_reader :starting_header_level
5
-
6
- renders_many :sections, ->(title: nil, expanded: false, id: nil, classes: [], html_attributes: {}, &block) do
4
+ renders_many :sections, ->(title: nil, expanded: false, id: nil, html_attributes: {}, &block) do
7
5
  DsfrComponent::AccordionComponent::SectionComponent.new(
8
- classes: classes,
9
6
  expanded: expanded,
10
7
  html_attributes: html_attributes,
11
8
  title: title,
@@ -15,10 +12,10 @@ class DsfrComponent::AccordionComponent < DsfrComponent::Base
15
12
  )
16
13
  end
17
14
 
18
- def initialize(classes: [], html_attributes: {}, starting_header_level: DEFAULT_HEADER_LEVEL)
15
+ def initialize(html_attributes: {}, starting_header_level: nil)
19
16
  @starting_header_level = starting_header_level
20
17
 
21
- super(classes: classes, html_attributes: html_attributes)
18
+ super(html_attributes: html_attributes)
22
19
  end
23
20
 
24
21
  private
@@ -7,18 +7,19 @@ class DsfrComponent::AlertComponent < DsfrComponent::Base
7
7
  # @param size [Symbol] alert size : `:md` (default) or `:sm`
8
8
  # @param close_button [Boolean] display a close button to remove the alert
9
9
  # @note in size MD the title is required but the content is optional. In size SM there should be not title but the content is required
10
- def initialize(type:, title: nil, size: :md, close_button: false, classes: [], html_attributes: {})
10
+ def initialize(type: nil, title: nil, size: :md, close_button: false, icon_name: nil, html_attributes: {})
11
11
  @title = title
12
12
  @type = type
13
13
  @size = size
14
14
  @close_button = close_button
15
+ @icon_name = icon_name
15
16
 
16
- super(classes: classes, html_attributes: html_attributes)
17
+ super(html_attributes: html_attributes)
17
18
  end
18
19
 
19
20
  def call
20
- raise ArgumentError, "SM alerts cannot have titles but must have a content" if @size == :sm && (@title.present? || content.blank?)
21
- raise ArgumentError, "MD Alerts must have a title" if @size == :md && @title.blank?
21
+ check_main_content!
22
+ check_icon_allowed!
22
23
 
23
24
  tag.div(**html_attributes) do
24
25
  safe_join([title_tag, content_tag, close_button_tag])
@@ -27,10 +28,36 @@ class DsfrComponent::AlertComponent < DsfrComponent::Base
27
28
 
28
29
  private
29
30
 
30
- attr_reader :title, :type, :size, :close_button
31
+ attr_reader :title, :type, :size, :close_button, :icon_name
31
32
 
32
33
  def default_attributes
33
- { class: %w(fr-alert) + [type_class, size_class].compact }
34
+ { class: %w(fr-alert) + [icon_class, type_class, size_class].compact }
35
+ end
36
+
37
+ def default_type?
38
+ type.nil?
39
+ end
40
+
41
+ def check_icon_allowed!
42
+ raise ArgumentError, "a custom icon can only be used on default alert types" if icon_name && !default_type?
43
+ end
44
+
45
+ def main_content?
46
+ case size
47
+ when :sm
48
+ content.present? && title.blank?
49
+ when :md
50
+ content.present? || title.present?
51
+ else
52
+ raise ArgumentError, "invalid alert size: '#{size}', supported sizes are #{SIZES.to_sentence}"
53
+ end
54
+ end
55
+
56
+ def check_main_content!
57
+ raise(
58
+ ArgumentError,
59
+ "You must provide a title for medium alerts, but you can't use it for small alerts (use content instead)"
60
+ ) if not main_content?
34
61
  end
35
62
 
36
63
  def title_tag
@@ -57,33 +84,31 @@ private
57
84
  end
58
85
  end
59
86
 
60
- def type_class
61
- fail(ArgumentError, type_error_message) unless valid_type?
87
+ def icon_class
88
+ return nil if icon_name.blank?
62
89
 
63
- "fr-alert--#{type}"
90
+ "fr-icon-#{icon_name}"
64
91
  end
65
92
 
66
93
  def valid_type?
67
94
  type.in?(TYPES)
68
95
  end
69
96
 
70
- def type_error_message
71
- "invalid alert type #{type}, supported types are #{TYPES.to_sentence}"
72
- end
73
-
74
- def size_class
75
- return nil if size == :md
97
+ def type_class
98
+ return nil if type.blank?
76
99
 
77
- fail(ArgumentError, size_error_message) unless valid_size?
100
+ raise ArgumentError, "invalid alert type #{type}, supported types are #{TYPES.to_sentence}" unless valid_type?
78
101
 
79
- "fr-alert--#{size}"
102
+ "fr-alert--#{type}"
80
103
  end
81
104
 
82
105
  def valid_size?
83
106
  size.in?(SIZES)
84
107
  end
85
108
 
86
- def size_error_message
87
- "invalid alert size #{size}, supported sizes are #{SIZES.to_sentence}"
109
+ def size_class
110
+ return nil if size == :md
111
+
112
+ "fr-alert--#{size}"
88
113
  end
89
114
  end
@@ -3,14 +3,12 @@ module DsfrComponent
3
3
  STATUSES = %i[success error info warning new].freeze
4
4
 
5
5
  # @param status [BadgeComponent::STATUSES]
6
- def initialize(status:, classes: [], html_attributes: {})
6
+ def initialize(status:, html_attributes: {})
7
7
  raise(ArgumentError, "`status` should be one of #{STATUSES}") unless STATUSES.include?(status)
8
8
 
9
9
  @status = status
10
10
 
11
- classes.push("fr-badge--#{@status}")
12
-
13
- super(classes: classes, html_attributes: html_attributes)
11
+ super(html_attributes: html_attributes)
14
12
  end
15
13
 
16
14
  def call
@@ -24,7 +22,7 @@ module DsfrComponent
24
22
  attr_reader :status
25
23
 
26
24
  def default_attributes
27
- { class: 'fr-badge' }
25
+ { class: ['fr-badge', "fr-badge--#{status}"] }
28
26
  end
29
27
  end
30
28
  end
@@ -9,21 +9,8 @@ class DsfrComponent::Base < ViewComponent::Base
9
9
 
10
10
  SIZES = %i[sm md lg].freeze
11
11
 
12
- def initialize(classes:, html_attributes:)
13
- if classes.nil?
14
- Rails.logger.warn("classes is nil, if no custom classes are needed omit the param")
15
-
16
- classes = []
17
- end
18
- # FIXME: remove first merge when we deprecate classes
19
- #
20
- # This step only needs to be here while we still accept classes:, now
21
- # we're using html_attributes_utils we can start to move towards
22
- # supporting html_attributes: { class: 'xyz' } over taking them
23
- # separately
24
-
12
+ def initialize(html_attributes: {})
25
13
  @html_attributes = default_attributes
26
- .deep_merge_html_attributes({ class: classes })
27
14
  .deep_merge_html_attributes(html_attributes)
28
15
  .deep_tidy_html_attributes
29
16
 
@@ -9,10 +9,6 @@ class DsfrComponent::BreadcrumbsComponent < DsfrComponent::Base
9
9
  content_tag(:a, class: 'fr-breadcrumb__link', **attributes) { label }
10
10
  end
11
11
 
12
- def initialize(classes: [], html_attributes: {})
13
- super(classes: classes, html_attributes: html_attributes)
14
- end
15
-
16
12
  private
17
13
 
18
14
  def default_attributes
@@ -10,7 +10,7 @@ module DsfrComponent
10
10
  # @param title [String] Le titre du bouton permettant d’afficher une infobulle (optionnel)
11
11
  # @param level [String] Le niveau du bouton : :primary (défaut), :secondary ou :tertiary (optionnel)
12
12
  # @param size [String] La taille du bouton : :sm, :md (défaut), :lg (optionnel)
13
- def initialize(label: nil, title: nil, icon: nil, icon_position: :left, level: nil, size: nil, classes: [], html_attributes: {})
13
+ def initialize(label: nil, title: nil, icon: nil, icon_position: :left, level: nil, size: nil, html_attributes: {})
14
14
  @label = label
15
15
  @title = title
16
16
  @icon = icon
@@ -24,7 +24,7 @@ module DsfrComponent
24
24
  validate_level
25
25
  validate_label
26
26
 
27
- super(classes: classes, html_attributes: html_attributes)
27
+ super(html_attributes: html_attributes)
28
28
  end
29
29
 
30
30
  def call
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DsfrComponent
4
+ class CalloutComponent < DsfrComponent::Base
5
+ include DsfrComponent::Traits::HeaderSizeable
6
+
7
+ renders_one :action_zone
8
+
9
+ # @param title [String] Le titre de la mise en avant
10
+ # @param icon_name [String] Le nom de l’icône à afficher (exemple `arrow-right-line`) (optionnel)
11
+ # @param starting_header_level [Integer] Le niveau de titre (optionnel)
12
+ def initialize(
13
+ title:,
14
+ icon_name: "information-line",
15
+ starting_header_level: nil,
16
+ html_attributes: {}
17
+ )
18
+ @title = title
19
+ @icon_name = icon_name
20
+ @starting_header_level = starting_header_level
21
+
22
+ super(html_attributes: html_attributes)
23
+ end
24
+
25
+ def call
26
+ tag.div(**html_attributes) do
27
+ concat content_tag(starting_header_tag, @title, class: 'fr-callout__title')
28
+
29
+ concat content_tag(:p, content, class: 'fr-callout__text')
30
+
31
+ concat action_zone if action_zone?
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def default_attributes
38
+ { class: "fr-callout fr-icon-#{@icon_name}" }
39
+ end
40
+ end
41
+ end
@@ -1,10 +1,10 @@
1
1
  class DsfrComponent::HeaderComponent::DirectLinkComponent < DsfrComponent::Base
2
- def initialize(title:, path:, active: false, classes: [], html_attributes: {})
2
+ def initialize(title:, path:, active: false, html_attributes: {})
3
3
  @title = title
4
4
  @path = path
5
5
  @active = active
6
6
 
7
- super(classes: classes, html_attributes: html_attributes)
7
+ super(html_attributes: html_attributes)
8
8
  end
9
9
 
10
10
  def call
@@ -1,11 +1,11 @@
1
1
  class DsfrComponent::HeaderComponent::DirectLinkDropdownComponent < DsfrComponent::Base
2
2
  renders_many :links, DsfrComponent::HeaderComponent::DirectLinkComponent
3
3
 
4
- def initialize(title:, active: false, classes: [], html_attributes: {})
4
+ def initialize(title:, active: false, html_attributes: {})
5
5
  @title = title
6
6
  @active = active
7
7
 
8
- super(classes: classes, html_attributes: html_attributes)
8
+ super(html_attributes: html_attributes)
9
9
  end
10
10
 
11
11
  def call
@@ -2,12 +2,12 @@ class DsfrComponent::HeaderComponent::OperatorImageComponent < DsfrComponent::Ba
2
2
  # @param title [String] Le title du lien vers la page d'accueil du site
3
3
  # @param src [String] L'attribut src qui sera passé au tag img
4
4
  # @param alt [String] Le texte alternatif qui sera passé au tag img. Il doit impérativement contenir le texte présent dans l’image.
5
- def initialize(title:, src:, alt:, classes: [], html_attributes: {})
5
+ def initialize(title:, src:, alt:, html_attributes: {})
6
6
  @title = title
7
7
  @src = src
8
8
  @alt = alt
9
9
 
10
- super(classes: classes, html_attributes: html_attributes)
10
+ super(html_attributes: html_attributes)
11
11
  end
12
12
 
13
13
  def call
@@ -1,9 +1,9 @@
1
1
  class DsfrComponent::HeaderComponent::ToolLinkComponent < DsfrComponent::Base
2
- def initialize(title:, path:, classes: [], html_attributes: {})
2
+ def initialize(title:, path:, html_attributes: {})
3
3
  @title = title
4
4
  @path = path
5
5
 
6
- super(classes: classes, html_attributes: html_attributes)
6
+ super(html_attributes: html_attributes)
7
7
  end
8
8
 
9
9
  def call
@@ -10,12 +10,12 @@ class DsfrComponent::HeaderComponent < DsfrComponent::Base
10
10
  # @param logo_text [String] Ce texte obligatoire sera affiché en dessous de la Marianne et au dessus de la devise française. C’est généralement un nom de ministère ou d’administration.
11
11
  # @param title [String] Le nom du service numérique, titre principal du site.
12
12
  # @param tagline [String] La description du service numérique, sous-titre du site (optionnelle).
13
- def initialize(logo_text:, title: nil, tagline: nil, classes: [], html_attributes: {})
13
+ def initialize(logo_text:, title: nil, tagline: nil, html_attributes: {})
14
14
  @logo_text = logo_text
15
15
  @title = title
16
16
  @tagline = tagline
17
17
 
18
- super(classes: classes, html_attributes: html_attributes)
18
+ super(html_attributes: html_attributes)
19
19
  end
20
20
 
21
21
  private
@@ -2,13 +2,13 @@ module DsfrComponent
2
2
  class HighlightComponent < DsfrComponent::Base
3
3
  # @param text [String] Le contenu textuel du composant
4
4
  # @param size [Symbol] La taille de la mise en exergue
5
- def initialize(text:, size: :md, classes: [], html_attributes: {})
5
+ def initialize(text:, size: :md, html_attributes: {})
6
6
  @text = text
7
7
  @size = size
8
8
 
9
9
  raise ArgumentError if not SIZES.include?(size)
10
10
 
11
- super(classes: classes, html_attributes: html_attributes)
11
+ super(html_attributes: html_attributes)
12
12
  end
13
13
 
14
14
  def call
@@ -5,12 +5,12 @@ module DsfrComponent
5
5
 
6
6
  # @param title [String] Titre de la modale
7
7
  # @param opened [Boolean] Ouvre la modale dès le chargement de la page
8
- def initialize(title:, opened: false, classes: [], html_attributes: {})
8
+ def initialize(title:, opened: false, html_attributes: {})
9
9
  @title = title
10
10
  @opened = opened
11
11
 
12
12
  @id = html_attributes[:id]
13
- super(classes: classes, html_attributes: html_attributes)
13
+ super(html_attributes: html_attributes)
14
14
  end
15
15
 
16
16
  private
@@ -0,0 +1,14 @@
1
+ <%= tag.div(**html_attributes) do %>
2
+ <div class="fr-container">
3
+ <div class="fr-notice__body">
4
+ <%= tag.send(description_tag) do %>
5
+ <span class="fr-notice__title <%= icon_class %>"><%= title %></span>
6
+ <span class="fr-notice__desc"><%= description %></span>
7
+ <%= link_tag %>
8
+ <% end %>
9
+ <% if dismissible %>
10
+ <button onclick="const notice = this.parentNode.parentNode.parentNode; notice.parentNode.removeChild(notice)" title="<%= @dismiss_label %>" type="button" class="fr-btn--close fr-btn"><%= @dismiss_label %></button>
11
+ <% end %>
12
+ </div>
13
+ </div>
14
+ <% end %>
@@ -0,0 +1,99 @@
1
+ module DsfrComponent
2
+ class NoticeComponent < DsfrComponent::Base
3
+ GENERIC_TYPES = %w[info waring alert].freeze
4
+ WEATHER_TYPES = %w[weather-orange weather-red weather-purple].freeze
5
+ ALERT_TYPES = %w[kidnapping cyberattack attack witness].freeze
6
+ TYPES = GENERIC_TYPES + WEATHER_TYPES + ALERT_TYPES
7
+
8
+ WEATHER_ICONS = %w[windy-fill thunderstorms-fill heavy-showers-fill flood-fill temp-cold-fill sun-fill avalanches-fill snowy-fill].freeze
9
+
10
+ DESCRIPTION_TAGS = %i[p h1 h2 h3 h4 h5 h6].freeze
11
+
12
+ # @param title [String] Titre du bandeau
13
+ # @param description [String] Description du bandeau pour apporter du contexte (optionnel)
14
+ # @param type [String] Type de bandeau (info, waring, alert, weather-orange, weather-red, weather-purple, kidnapping, cyberattack, attack, witness)
15
+ # @param description_tag [Symbol] Balise HTML à utiliser pour la description (p, h1, h2, h3, h4, h5, h6)
16
+ # @param use_icon [Boolean] Afficher ou non une icône, uniquement pour les bandeaux génériques
17
+ # @param icon_name [String] Nom de l'icône à afficher
18
+ # @param use_notice [Boolean] Ajout l’attribut role="notice" (si insertion du bandeau à la volée)
19
+ # @param dismissible [Boolean] Ajouter un bouton de fermeture
20
+ # @param dismiss_label [String] Libellé du bouton de fermeture (optionnel)
21
+ # @param link_label [String] Libellé du lien (optionnel)
22
+ # @param link_href [String] URL du lien (optionnel)
23
+ # @param link_title [String] Titre du lien (optionnel)
24
+ # @param link_blank [Boolean] Ouvrir le lien dans un nouvel onglet (optionnel)
25
+ def initialize(title:, description:, type: "info", description_tag: :p, use_icon: true, icon_name: nil, use_notice: false, dismissible: false, dismiss_label: "Masquer le message", link_label: nil, link_href: nil, link_title: nil, link_blank: true,
26
+ html_attributes: {})
27
+ @title = title
28
+ @description = description
29
+ @type = type
30
+ @description_tag = description_tag
31
+ @icon_name = icon_name
32
+ @use_notice = use_notice
33
+ @dismissible = dismissible
34
+ @dismiss_label = dismiss_label
35
+ @link_label = link_label
36
+ @link_href = link_href
37
+ @link_title = link_title
38
+ @link_blank = link_blank
39
+
40
+ # D’après les règles du DSFR les types non génériques ont forcément une icône
41
+ @use_icon = if GENERIC_TYPES.include?(type)
42
+ use_icon
43
+ else
44
+ true
45
+ end
46
+
47
+ raise ArgumentError, "Invalid type: #{type}. Valid types: #{TYPES.join(', ')}" unless type_valid?
48
+
49
+ raise ArgumentError, "Invalid description tag: #{description_tag}" unless DESCRIPTION_TAGS.include?(description_tag)
50
+
51
+ if icon_name
52
+ raise ArgumentError, "L’icône n’est pas personnalisable sur les bandeaux d’alertes" if ALERT_TYPES.include?(type)
53
+ raise ArgumentError, "L’icône d’un bandeau de type météo doit être une icône météo (#{WEATHER_ICONS.join(', ')})" if WEATHER_TYPES.include?(type) && WEATHER_ICONS.exclude?(icon_name)
54
+ end
55
+
56
+ super(html_attributes: html_attributes)
57
+ end
58
+
59
+ private
60
+
61
+ attr_reader :type, :title, :description, :description_tag, :use_icon, :icon_name, :use_notice, :dismissible, :link_label, :link_href, :link_title, :link_blank
62
+
63
+ def notice_class
64
+ "fr-notice--#{type}"
65
+ end
66
+
67
+ def type_valid?
68
+ TYPES.include?(type)
69
+ end
70
+
71
+ def link_display?
72
+ link_label.present? && link_href.present?
73
+ end
74
+
75
+ def link_tag
76
+ return unless link_display?
77
+
78
+ target = @link_blank ? "_blank" : "_self"
79
+
80
+ link_to(link_label, link_href, title: link_title, target: target)
81
+ end
82
+
83
+ def icon_class
84
+ return unless icon_name
85
+
86
+ "fr-icon-#{icon_name}"
87
+ end
88
+
89
+ def default_attributes
90
+ class_attr = "fr-notice #{notice_class}"
91
+ class_attr += " fr-notice--no-icon" unless use_icon
92
+
93
+ attr = { class: class_attr }
94
+ attr[:role] = "notice" if use_notice
95
+
96
+ attr
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,8 @@
1
+ <%= form_with(url: url, method: :get, **html_attributes) do |form| %>
2
+ <% hidden_fields.each do |name, value| %>
3
+ <%= form.hidden_field(name, value: value) %>
4
+ <% end %>
5
+ <%= form.label(name, label_text, for: id, class: 'fr-label') %>
6
+ <%= form.search_field(name, id: id, placeholder: button_text, value: value, class: 'fr-input') %>
7
+ <%= form.button(type: :submit, name: nil, class: 'fr-btn') { button_text } %>
8
+ <% end %>
@@ -0,0 +1,50 @@
1
+ module DsfrComponent
2
+ class SearchComponent < DsfrComponent::Base
3
+ DEFAULT_LABEL_TEXT = 'Recherche'.freeze
4
+ DEFAULT_BUTTON_TEXT = 'Rechercher'.freeze
5
+
6
+ # @param url [String] form destination
7
+ # @param name [String|Symbol] input name
8
+ # @param size [Symbol] component size : `:md` (default) or `:sm`/`:lg`
9
+ # @param label_text [String] Label text, default: "Recherche"
10
+ # @param button_text [String] Button and placeholder text, default: "Rechercher"
11
+ # @param value [String] Current input value (optional, defaults to request.params[name])
12
+ # @param hidden_fields [Hash] Extra fields (optional)
13
+ def initialize(url:, name: :search, size: :md, label_text: DEFAULT_LABEL_TEXT, button_text: DEFAULT_BUTTON_TEXT, **html_attributes)
14
+ @url = url
15
+ @name = name
16
+ @label_text = label_text
17
+ @button_text = button_text
18
+ @size = size
19
+ @value = html_attributes.delete(:value)
20
+ @hidden_fields = html_attributes.delete(:hidden_fields) || {}
21
+ @html_attributes = html_attributes
22
+
23
+ validate_size!
24
+
25
+ super(html_attributes: html_attributes)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :url, :size, :name, :label_text, :button_text, :hidden_fields, :html_attributes
31
+
32
+ def id
33
+ "#{name}_#{object_id}"
34
+ end
35
+
36
+ def value
37
+ @value || (request && request.params[name])
38
+ end
39
+
40
+ def default_attributes
41
+ classes = ['fr-search-bar']
42
+ classes << "fr-search-bar--#{size}" unless size == :md
43
+ { class: classes }
44
+ end
45
+
46
+ def validate_size!
47
+ raise(ArgumentError, "`size` should be one of #{SIZES}") if size.present? && SIZES.exclude?(size)
48
+ end
49
+ end
50
+ end
@@ -2,11 +2,11 @@ module DsfrComponent
2
2
  class SkiplinkComponent < DsfrComponent::Base
3
3
  # @param label [String] le texte utilisé pour le aria-label de la nav qui affiche les liens d’évitement (par exemple « Accès rapide »)
4
4
  # @param links [Array] liste de liens HTML
5
- def initialize(label:, links:, classes: [], html_attributes: {})
5
+ def initialize(label:, links:, html_attributes: {})
6
6
  @label = label
7
7
  @links = links
8
8
 
9
- super(classes: classes, html_attributes: html_attributes)
9
+ super(html_attributes: html_attributes)
10
10
  end
11
11
 
12
12
  private
@@ -1,9 +1,9 @@
1
1
  <%= tag.div(**html_attributes) do %>
2
2
  <h2 class="fr-stepper__title">
3
+ <%= title %>
3
4
  <span class="fr-stepper__state">
4
5
  Étape <%= value %> sur <%= max %>
5
6
  </span>
6
- <%= title %>
7
7
  </h2>
8
8
  <div
9
9
  class="fr-stepper__steps"
@@ -4,7 +4,7 @@ module DsfrComponent
4
4
  # @param value [Integer] Numéro de l’étape en cours (commence à 1)
5
5
  # @param max [Integer] Nombre d’étapes total
6
6
  # @param next_title [String] Titre de l’étape suivante (sauf pour la dernière étape)
7
- def initialize(title:, value:, max:, next_title: nil, classes: [], html_attributes: {})
7
+ def initialize(title:, value:, max:, next_title: nil, html_attributes: {})
8
8
  @title = title
9
9
  @value = value
10
10
  @max = max
@@ -12,7 +12,7 @@ module DsfrComponent
12
12
 
13
13
  raise ArgumentError, "Les étapes doivent aller de 1 jusqu´à 8 au maximum" if @value < 1 || @value > @max || @max > 8
14
14
 
15
- super(classes: classes, html_attributes: html_attributes)
15
+ super(html_attributes: html_attributes)
16
16
  end
17
17
 
18
18
  private
@@ -3,13 +3,13 @@ class DsfrComponent::TabsComponent::TabComponent < DsfrComponent::Base
3
3
  # @param active [Boolean] Définit si l’onglet est actif ou non
4
4
  # @param path [String] (optionnel) chemin vers lequel l’onglet pointe, utilisable avec Turbo Drive, transforme le bouton en lien, avec une turbo action `advance`
5
5
  # @param icon [String] (optionnel) icône affichée à gauche du titre de l’onglet (sans le préfixe `fr-icon-`)
6
- def initialize(title:, active: false, path: nil, icon: nil, classes: [], html_attributes: {})
6
+ def initialize(title:, active: false, path: nil, icon: nil, html_attributes: {})
7
7
  @title = title
8
8
  @active = active
9
9
  @path = path
10
10
  @icon = icon
11
11
 
12
- super(classes: classes, html_attributes: html_attributes)
12
+ super(html_attributes: html_attributes)
13
13
  end
14
14
 
15
15
  def nav_id
@@ -2,11 +2,11 @@ class DsfrComponent::TabsComponent < DsfrComponent::Base
2
2
  renders_many :tabs, "DsfrComponent::TabsComponent::TabComponent"
3
3
 
4
4
  # @param label [String] Le nom du système d’onglets, sera uniquement affiché comme aria-label (optionnel)
5
- def initialize(label: nil, classes: [], html_attributes: {})
5
+ def initialize(label: nil, html_attributes: {})
6
6
  @label = label
7
7
  @tabs = []
8
8
 
9
- super(classes: classes, html_attributes: html_attributes)
9
+ super(html_attributes: html_attributes)
10
10
  end
11
11
 
12
12
  private
@@ -8,7 +8,7 @@ module DsfrComponent
8
8
  # @param url [String] for clickable tags only (optional)
9
9
  # @param selected [Boolean] adds a check, useful for filters list, cannot be used with `url` (optional)
10
10
  # @param dismissable [Boolean] adds a close icon on the right, cannot be used with `url` or `icon` (optional)
11
- def initialize(title:, icon: nil, size: nil, url: nil, selected: nil, dismissable: nil, classes: [], html_attributes: {})
11
+ def initialize(title:, icon: nil, size: nil, url: nil, selected: nil, dismissable: nil, html_attributes: {})
12
12
  @title = title
13
13
  @icon = icon
14
14
  @size = size
@@ -16,7 +16,7 @@ module DsfrComponent
16
16
  @selected = selected
17
17
  @dismissable = dismissable
18
18
 
19
- super(classes: classes, html_attributes: html_attributes)
19
+ super(html_attributes: html_attributes)
20
20
  end
21
21
 
22
22
  def call
@@ -7,7 +7,7 @@ module DsfrComponent
7
7
  # @param description [String] description (optional)
8
8
  # @param orientation [String] :horizontal or :vertical
9
9
  # @param heading_level [Integer] 1, 2, 3, 4 (default), 5, 6
10
- def initialize(title:, url:, image_src: nil, image_alt: "", description: nil, orientation: :vertical, heading_level: 4, classes: [], html_attributes: {})
10
+ def initialize(title:, url:, image_src: nil, image_alt: "", description: nil, orientation: :vertical, heading_level: 4, html_attributes: {})
11
11
  @title = title
12
12
  @url = url
13
13
  @image_src = image_src
@@ -18,7 +18,7 @@ module DsfrComponent
18
18
 
19
19
  raise ArgumentError if HEADING_LEVELS.exclude?(heading_level)
20
20
 
21
- super(classes: classes, html_attributes: html_attributes)
21
+ super(html_attributes: html_attributes)
22
22
  end
23
23
 
24
24
  private
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DsfrComponent
4
+ module Traits
5
+ # HeaderSizeable is meant for every component that exhibits a
6
+ # header, which level can be overriden via the
7
+ # `default_header_level` method. Make sure you store the attribute
8
+ # `@starting_header_level` in your component's constructor.
9
+ module HeaderSizeable
10
+ DEFAULT_HEADER_LEVEL = 3
11
+
12
+ def starting_header_level
13
+ @starting_header_level || default_header_level
14
+ end
15
+
16
+ def default_header_level
17
+ DEFAULT_HEADER_LEVEL
18
+ end
19
+
20
+ def starting_header_tag
21
+ ["h", starting_header_level].join
22
+ end
23
+ end
24
+ end
25
+ end
@@ -18,6 +18,9 @@ module DsfrComponentsHelper
18
18
  dsfr_tabs: 'DsfrComponent::TabsComponent',
19
19
  dsfr_highlight: 'DsfrComponent::HighlightComponent',
20
20
  dsfr_skiplink: 'DsfrComponent::SkiplinkComponent',
21
+ dsfr_callout: 'DsfrComponent::CalloutComponent',
22
+ dsfr_notice: 'DsfrComponent::NoticeComponent',
23
+ dsfr_search: 'DsfrComponent::SearchComponent',
21
24
  # DO NOT REMOVE: new component mapping here
22
25
  }.freeze
23
26
  HELPER_NAME_TO_CLASS_NAME.each do |name, klass|
@@ -1,5 +1,5 @@
1
1
  module Dsfr
2
2
  module Components
3
- VERSION = '2.1.1'.freeze
3
+ VERSION = '2.2.0'.freeze
4
4
  end
5
5
  end
@@ -10,4 +10,4 @@ title: <%= name %>
10
10
  :markdown
11
11
  Le rendu de base du <%= name %>Component
12
12
 
13
- = render('/partials/related-info.haml', links: dsfr_component_doc_link("<%= name %>"))
13
+ = render('/partials/related-info.haml', links: dsfr_component_doc_links("doc-id", "storybook-id"))
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsfr-view-components
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BetaGouv developers
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-03 00:00:00.000000000 Z
10
+ date: 2025-04-24 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: html-attributes-utils
@@ -66,7 +66,7 @@ dependencies:
66
66
  - !ruby/object:Gem::Version
67
67
  version: '0'
68
68
  - !ruby/object:Gem::Dependency
69
- name: guard
69
+ name: debug
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - ">="
@@ -80,7 +80,7 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  - !ruby/object:Gem::Dependency
83
- name: guard-rspec
83
+ name: guard
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
@@ -94,7 +94,7 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  - !ruby/object:Gem::Dependency
97
- name: pry-byebug
97
+ name: guard-rspec
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - ">="
@@ -303,34 +303,6 @@ dependencies:
303
303
  - - "~>"
304
304
  - !ruby/object:Gem::Version
305
305
  version: 0.7.0
306
- - !ruby/object:Gem::Dependency
307
- name: slim
308
- requirement: !ruby/object:Gem::Requirement
309
- requirements:
310
- - - "~>"
311
- - !ruby/object:Gem::Version
312
- version: 4.1.0
313
- type: :development
314
- prerelease: false
315
- version_requirements: !ruby/object:Gem::Requirement
316
- requirements:
317
- - - "~>"
318
- - !ruby/object:Gem::Version
319
- version: 4.1.0
320
- - !ruby/object:Gem::Dependency
321
- name: slim_lint
322
- requirement: !ruby/object:Gem::Requirement
323
- requirements:
324
- - - "~>"
325
- - !ruby/object:Gem::Version
326
- version: 0.22.0
327
- type: :development
328
- prerelease: false
329
- version_requirements: !ruby/object:Gem::Requirement
330
- requirements:
331
- - - "~>"
332
- - !ruby/object:Gem::Version
333
- version: 0.22.0
334
306
  - !ruby/object:Gem::Dependency
335
307
  name: webrick
336
308
  requirement: !ruby/object:Gem::Requirement
@@ -368,6 +340,7 @@ files:
368
340
  - app/components/dsfr_component/breadcrumbs_component.html.erb
369
341
  - app/components/dsfr_component/breadcrumbs_component.rb
370
342
  - app/components/dsfr_component/button_component.rb
343
+ - app/components/dsfr_component/callout_component.rb
371
344
  - app/components/dsfr_component/header_component.html.erb
372
345
  - app/components/dsfr_component/header_component.rb
373
346
  - app/components/dsfr_component/header_component/direct_link_component.rb
@@ -377,6 +350,10 @@ files:
377
350
  - app/components/dsfr_component/highlight_component.rb
378
351
  - app/components/dsfr_component/modal_component.html.erb
379
352
  - app/components/dsfr_component/modal_component.rb
353
+ - app/components/dsfr_component/notice_component.html.erb
354
+ - app/components/dsfr_component/notice_component.rb
355
+ - app/components/dsfr_component/search_component.html.erb
356
+ - app/components/dsfr_component/search_component.rb
380
357
  - app/components/dsfr_component/skiplink_component.html.erb
381
358
  - app/components/dsfr_component/skiplink_component.rb
382
359
  - app/components/dsfr_component/stepper_component.html.erb
@@ -389,10 +366,10 @@ files:
389
366
  - app/components/dsfr_component/tile_component.rb
390
367
  - app/components/dsfr_component/traits.rb
391
368
  - app/components/dsfr_component/traits/custom_html_attributes.rb
369
+ - app/components/dsfr_component/traits/header_sizeable.rb
392
370
  - app/helpers/dsfr_back_to_top_link_helper.rb
393
371
  - app/helpers/dsfr_components_helper.rb
394
372
  - app/helpers/dsfr_link_helper.rb
395
- - app/helpers/dsfr_skip_link_helper.rb
396
373
  - config/routes.rb
397
374
  - lib/dsfr/components.rb
398
375
  - lib/dsfr/components/engine.rb
@@ -409,6 +386,11 @@ homepage: https://github.com/betagouv/dsfr-view-components
409
386
  licenses:
410
387
  - MIT
411
388
  metadata:
389
+ bug_tracker_uri: https://github.com/betagouv/dsfr-view-components/issues
390
+ changelog_uri: https://github.com/betagouv/dsfr-view-components/releases
391
+ documentation_uri: https://www.rubydoc.info/gems/dsfr-view-components/
392
+ homepage_uri: https://github.com/betagouv/dsfr-view-components
393
+ source_code_uri: https://github.com/betagouv/dsfr-view-components
412
394
  rubygems_mfa_required: 'true'
413
395
  rdoc_options: []
414
396
  require_paths:
@@ -1,13 +0,0 @@
1
- module DsfrSkipLinkHelper
2
- def dsfr_skip_link(text: 'Skip to main content', href: '#main-content', classes: [], **html_attributes, &block)
3
- link_classes = Array.wrap(classes).append('govuk-skip-link')
4
-
5
- html_attributes_with_data_module = { data: { module: "govuk-skip-link" } }.deep_merge(html_attributes)
6
-
7
- return link_to(href, class: link_classes, **html_attributes_with_data_module, &block) if block_given?
8
-
9
- link_to(text, href, class: link_classes, **html_attributes_with_data_module)
10
- end
11
- end
12
-
13
- ActiveSupport.on_load(:action_view) { include DsfrSkipLinkHelper }