dsfr-view-components 2.1.1 → 2.1.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fd43d49e7920aa1ae37d143b9503c68b1d44aad46e5a3acde610b7a0ae9da96
4
- data.tar.gz: b3a7d11b90ff73cad4023f517bd3d567141648e25cdf200b1204daf2dcde8db7
3
+ metadata.gz: f229c00b1559088e4682d9db544e8dca6580e14880b1b0d19ed273672212516b
4
+ data.tar.gz: b8210536354caa679404e52a8d03d5b61ed54457741781e35b09fa17141f18c8
5
5
  SHA512:
6
- metadata.gz: 4508aef35cb1afd72ca39df0e89c74290f8a4f9ebfe47c8ff2b7ce8fbcb4fba3ff2d647ecfcc41eb585b515ef378716496c1355e917d2a7b59fc38ba3faa93a5
7
- data.tar.gz: f9391b2ef8ddfcdd985305d823ded7fb73add4d05eb05c8be8d30cec1a7cdf2d07b6d1df44bbd5ae18a609b5fb7d9934b54c192715449321ff2493638919f570
6
+ metadata.gz: 6a740ab446fbb8b0ecfe73815adf0b4d5435b22dd8e1616b70ac6ce3301b85ae4e055d86979069d24de106c7ad0278ba9d61a356b9d6a30dad258ea6210f31ee
7
+ data.tar.gz: dd564f73407b19af98a2628df2a8736335a656a4ce902c4b3117490a427c962b89da5e992c33ceff70d36bbda21f97009e04b7ba520e183c5b3517a8fb99b87a
@@ -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
@@ -1,7 +1,5 @@
1
1
  class DsfrComponent::AccordionComponent < DsfrComponent::Base
2
- DEFAULT_HEADER_LEVEL = 3
3
-
4
- attr_reader :starting_header_level
2
+ include DsfrComponent::Traits::HeaderSizeable
5
3
 
6
4
  renders_many :sections, ->(title: nil, expanded: false, id: nil, classes: [], html_attributes: {}, &block) do
7
5
  DsfrComponent::AccordionComponent::SectionComponent.new(
@@ -15,7 +13,7 @@ class DsfrComponent::AccordionComponent < DsfrComponent::Base
15
13
  )
16
14
  end
17
15
 
18
- def initialize(classes: [], html_attributes: {}, starting_header_level: DEFAULT_HEADER_LEVEL)
16
+ def initialize(classes: [], html_attributes: {}, starting_header_level: nil)
19
17
  @starting_header_level = starting_header_level
20
18
 
21
19
  super(classes: classes, html_attributes: html_attributes)
@@ -7,11 +7,12 @@ 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, classes: [], 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
17
  super(classes: classes, html_attributes: html_attributes)
17
18
  end
@@ -19,6 +20,7 @@ class DsfrComponent::AlertComponent < DsfrComponent::Base
19
20
  def call
20
21
  raise ArgumentError, "SM alerts cannot have titles but must have a content" if @size == :sm && (@title.present? || content.blank?)
21
22
  raise ArgumentError, "MD Alerts must have a title" if @size == :md && @title.blank?
23
+ raise ArgumentError, "icon_name should be set only on default alert" if @type && @icon_name
22
24
 
23
25
  tag.div(**html_attributes) do
24
26
  safe_join([title_tag, content_tag, close_button_tag])
@@ -27,10 +29,10 @@ class DsfrComponent::AlertComponent < DsfrComponent::Base
27
29
 
28
30
  private
29
31
 
30
- attr_reader :title, :type, :size, :close_button
32
+ attr_reader :title, :type, :size, :close_button, :icon_name
31
33
 
32
34
  def default_attributes
33
- { class: %w(fr-alert) + [type_class, size_class].compact }
35
+ { class: %w(fr-alert) + [icon_class, type_class, size_class].compact }
34
36
  end
35
37
 
36
38
  def title_tag
@@ -57,7 +59,14 @@ private
57
59
  end
58
60
  end
59
61
 
62
+ def icon_class
63
+ return nil if icon_name.blank?
64
+
65
+ "fr-icon-#{icon_name}"
66
+ end
67
+
60
68
  def type_class
69
+ return nil if type.blank?
61
70
  fail(ArgumentError, type_error_message) unless valid_type?
62
71
 
63
72
  "fr-alert--#{type}"
@@ -0,0 +1,42 @@
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
+ classes: [],
17
+ html_attributes: {}
18
+ )
19
+ @title = title
20
+ @icon_name = icon_name
21
+ @starting_header_level = starting_header_level
22
+
23
+ super(classes: classes, html_attributes: html_attributes)
24
+ end
25
+
26
+ def call
27
+ tag.div(**html_attributes) do
28
+ concat content_tag(starting_header_tag, @title, class: 'fr-callout__title')
29
+
30
+ concat content_tag(:p, content, class: 'fr-callout__text')
31
+
32
+ concat action_zone if action_zone?
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def default_attributes
39
+ { class: "fr-callout fr-icon-#{@icon_name}" }
40
+ end
41
+ end
42
+ end
@@ -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
+ classes: [], 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(classes: classes, 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,5 @@
1
+ <%= form_with(url: url, method: :get, **html_attributes) do |form| %>
2
+ <%= form.label(name, label_text, for: id, class: 'fr-label') %>
3
+ <%= form.search_field(name, id: id, placeholder: button_text, class: 'fr-input') %>
4
+ <%= form.button(type: :submit, name: nil, class: 'fr-btn') { button_text } %>
5
+ <% end %>
@@ -0,0 +1,42 @@
1
+ module DsfrComponent
2
+ class SearchBarComponent < 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
+ def initialize(url:, name: :search, size: :md, label_text: DEFAULT_LABEL_TEXT, button_text: DEFAULT_BUTTON_TEXT, **html_attributes)
12
+ @url = url
13
+ @name = name
14
+ @label_text = label_text
15
+ @button_text = button_text
16
+ @size = size
17
+ @html_attributes = html_attributes
18
+
19
+ validate_size!
20
+
21
+ super(classes: nil, html_attributes: html_attributes)
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :url, :size, :name, :label_text, :button_text, :html_attributes
27
+
28
+ def id
29
+ "#{name}_#{object_id}"
30
+ end
31
+
32
+ def default_attributes
33
+ classes = ['fr-search-bar']
34
+ classes << "fr-search-bar--#{size}" unless size == :md
35
+ { class: classes }
36
+ end
37
+
38
+ def validate_size!
39
+ raise(ArgumentError, "`size` should be one of #{SIZES}") if size.present? && SIZES.exclude?(size)
40
+ end
41
+ end
42
+ end
@@ -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"
@@ -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_bar: 'DsfrComponent::SearchBarComponent',
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.1.2'.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,14 @@
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.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - BetaGouv developers
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 2025-02-03 00:00:00.000000000 Z
11
+ date: 2025-04-14 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: html-attributes-utils
@@ -66,7 +67,7 @@ dependencies:
66
67
  - !ruby/object:Gem::Version
67
68
  version: '0'
68
69
  - !ruby/object:Gem::Dependency
69
- name: guard
70
+ name: debug
70
71
  requirement: !ruby/object:Gem::Requirement
71
72
  requirements:
72
73
  - - ">="
@@ -80,7 +81,7 @@ dependencies:
80
81
  - !ruby/object:Gem::Version
81
82
  version: '0'
82
83
  - !ruby/object:Gem::Dependency
83
- name: guard-rspec
84
+ name: guard
84
85
  requirement: !ruby/object:Gem::Requirement
85
86
  requirements:
86
87
  - - ">="
@@ -94,7 +95,7 @@ dependencies:
94
95
  - !ruby/object:Gem::Version
95
96
  version: '0'
96
97
  - !ruby/object:Gem::Dependency
97
- name: pry-byebug
98
+ name: guard-rspec
98
99
  requirement: !ruby/object:Gem::Requirement
99
100
  requirements:
100
101
  - - ">="
@@ -303,34 +304,6 @@ dependencies:
303
304
  - - "~>"
304
305
  - !ruby/object:Gem::Version
305
306
  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
307
  - !ruby/object:Gem::Dependency
335
308
  name: webrick
336
309
  requirement: !ruby/object:Gem::Requirement
@@ -368,6 +341,7 @@ files:
368
341
  - app/components/dsfr_component/breadcrumbs_component.html.erb
369
342
  - app/components/dsfr_component/breadcrumbs_component.rb
370
343
  - app/components/dsfr_component/button_component.rb
344
+ - app/components/dsfr_component/callout_component.rb
371
345
  - app/components/dsfr_component/header_component.html.erb
372
346
  - app/components/dsfr_component/header_component.rb
373
347
  - app/components/dsfr_component/header_component/direct_link_component.rb
@@ -377,6 +351,10 @@ files:
377
351
  - app/components/dsfr_component/highlight_component.rb
378
352
  - app/components/dsfr_component/modal_component.html.erb
379
353
  - app/components/dsfr_component/modal_component.rb
354
+ - app/components/dsfr_component/notice_component.html.erb
355
+ - app/components/dsfr_component/notice_component.rb
356
+ - app/components/dsfr_component/search_bar_component.html.erb
357
+ - app/components/dsfr_component/search_bar_component.rb
380
358
  - app/components/dsfr_component/skiplink_component.html.erb
381
359
  - app/components/dsfr_component/skiplink_component.rb
382
360
  - app/components/dsfr_component/stepper_component.html.erb
@@ -389,10 +367,10 @@ files:
389
367
  - app/components/dsfr_component/tile_component.rb
390
368
  - app/components/dsfr_component/traits.rb
391
369
  - app/components/dsfr_component/traits/custom_html_attributes.rb
370
+ - app/components/dsfr_component/traits/header_sizeable.rb
392
371
  - app/helpers/dsfr_back_to_top_link_helper.rb
393
372
  - app/helpers/dsfr_components_helper.rb
394
373
  - app/helpers/dsfr_link_helper.rb
395
- - app/helpers/dsfr_skip_link_helper.rb
396
374
  - config/routes.rb
397
375
  - lib/dsfr/components.rb
398
376
  - lib/dsfr/components/engine.rb
@@ -409,7 +387,13 @@ homepage: https://github.com/betagouv/dsfr-view-components
409
387
  licenses:
410
388
  - MIT
411
389
  metadata:
390
+ bug_tracker_uri: https://github.com/betagouv/dsfr-view-components/issues
391
+ changelog_uri: https://github.com/betagouv/dsfr-view-components/releases
392
+ documentation_uri: https://www.rubydoc.info/gems/dsfr-view-components/
393
+ homepage_uri: https://github.com/betagouv/dsfr-view-components
394
+ source_code_uri: https://github.com/betagouv/dsfr-view-components
412
395
  rubygems_mfa_required: 'true'
396
+ post_install_message:
413
397
  rdoc_options: []
414
398
  require_paths:
415
399
  - lib
@@ -424,7 +408,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
424
408
  - !ruby/object:Gem::Version
425
409
  version: '0'
426
410
  requirements: []
427
- rubygems_version: 3.6.2
411
+ rubygems_version: 3.5.13
412
+ signing_key:
428
413
  specification_version: 4
429
414
  summary: Composants ViewComponent pour le Système de Design de l'État (DSFR)
430
415
  test_files: []
@@ -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 }