design_system 0.7.0 → 0.8.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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/app/controllers/concerns/design_system/branded.rb +7 -0
  4. data/app/helpers/design_system_helper.rb +0 -2
  5. data/app/views/layouts/govuk/application.html.erb +14 -25
  6. data/app/views/layouts/nhsuk/application.html.erb +24 -37
  7. data/app/views/nhsuk/_navigation.html.erb +9 -12
  8. data/lib/design_system/engine.rb +0 -3
  9. data/lib/design_system/generic/builders/base.rb +2 -1
  10. data/lib/design_system/generic/builders/button.rb +0 -2
  11. data/lib/design_system/generic/builders/fixed_elements.rb +0 -5
  12. data/lib/design_system/generic/builders/link.rb +0 -2
  13. data/lib/design_system/generic/builders/summary_list.rb +0 -3
  14. data/lib/design_system/generic/builders/tab.rb +0 -3
  15. data/lib/design_system/generic/builders/table.rb +0 -3
  16. data/lib/design_system/generic/form_builder.rb +2 -12
  17. data/lib/design_system/govuk/builders/button.rb +0 -2
  18. data/lib/design_system/govuk/builders/fixed_elements.rb +0 -4
  19. data/lib/design_system/govuk/builders/link.rb +0 -2
  20. data/lib/design_system/govuk/builders/summary_list.rb +0 -2
  21. data/lib/design_system/govuk/builders/tab.rb +0 -2
  22. data/lib/design_system/govuk/builders/table.rb +0 -2
  23. data/lib/design_system/govuk/form_builder.rb +0 -1
  24. data/lib/design_system/govuk/test_helpers/form_builder_assertions_helper.rb +99 -0
  25. data/lib/design_system/govuk/test_helpers/form_builder_testable.rb +1300 -0
  26. data/lib/design_system/helpers/css_helper.rb +20 -0
  27. data/lib/design_system/nhsuk/builders/fixed_elements.rb +0 -3
  28. data/lib/design_system/nhsuk/builders/summary_list.rb +0 -2
  29. data/lib/design_system/nhsuk/form_builder.rb +0 -2
  30. data/lib/design_system/version.rb +1 -1
  31. data/lib/design_system.rb +9 -0
  32. metadata +21 -5
  33. data/app/helpers/css_helper.rb +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57a4557e70bd03738470403e3267f8e4cd7560950d17619ec86f766d5284dbba
4
- data.tar.gz: 6b3bb953b829306cdcf28a1a5d35bbf9ca102b352d24e116e771b66f974915ca
3
+ metadata.gz: 2df0ff381525e15513457d76f8d2a44852ee4e64fcca301ae96cdbfb207a98fc
4
+ data.tar.gz: 2a8c01fee4df51855a47a996f1061132f01335e7df72fe9911867a4c5f01a668
5
5
  SHA512:
6
- metadata.gz: 39fe6fd17562c4f3afcce0b19a25172a275fc7435e2a5bd8c029e6550d975aad5ff6e8dfc95b925fd3530338d690f7ec3ff00fb2d0bb3ecd8f9192c7434b8cac
7
- data.tar.gz: be52770529a9cea0f95fdd3c8d21f0194b8bc1757a120476794132a824ba12e298bd9e153f8285c19455db9eeb4280844530b1b5820ce79873589eadb9ea30f3
6
+ metadata.gz: fabfc45353d5c2148d201e98464277692146da0eecc46417aa7ad2ff46719fe00030823746cbeecbf63021702a85d5aafddfa12e267d671e8b3b786f4cd21a97
7
+ data.tar.gz: aa747e752e7f645c15c4dcd62c18005a0144a1530ddb16b99b2aeb1208d7f816c7dec27fedc4fd85acf975c14d76fe9da8b94a0d76c5879a481ec7208dc10bdb
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # DesignSystem
2
2
 
3
- DesignSystem is a Ruby on Rails engine that enables consistent, compliant web applications across design systems including GOV.UK, NHS. It is designed to be extensible, and design systems are being developed for the National Disease Registration Service (NDRS), and Health Data Insight (HDI).
3
+ [![Gem Version](https://badge.fury.io/rb/design_system.svg)](https://badge.fury.io/rb/design_system)
4
+
5
+ DesignSystem is an extensible Ruby on Rails engine that enables consistent, compliant web applications across design systems. It includes GOV.UK and NHS UK design systems and plugins are being developed for the National Disease Registration Service (NDRS), and Health Data Insight (HDI).
4
6
 
5
7
  The gem provides unified form builders, UI components (tables, buttons, navigation, tabs), layouts, and styling that automatically adapt to each design system's specific requirements and branding guidelines.
6
8
 
@@ -5,6 +5,8 @@ module DesignSystem
5
5
 
6
6
  included do
7
7
  attr_reader :navigation_items
8
+ attr_reader :footer_links
9
+ attr_accessor :copyright_notice
8
10
 
9
11
  helper DesignSystemHelper
10
12
  end
@@ -17,5 +19,10 @@ module DesignSystem
17
19
  @navigation_items ||= []
18
20
  @navigation_items << { label:, path:, options: }
19
21
  end
22
+
23
+ def add_footer_link(name, href, options = {})
24
+ @footer_links ||= []
25
+ @footer_links << { name:, href:, options: }
26
+ end
20
27
  end
21
28
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/registry'
4
-
5
3
  # The helpers for the design system
6
4
  module DesignSystemHelper
7
5
  include ActionView::Helpers::FormHelper
@@ -133,26 +133,15 @@
133
133
  <div class="govuk-footer__meta-item govuk-footer__meta-item--grow">
134
134
  <h2 class="govuk-visually-hidden">Support links</h2>
135
135
  <ul class="govuk-footer__inline-list">
136
- <li class="govuk-footer__inline-list-item">
137
- <a class="govuk-footer__link" href="#">
138
- Help
139
- </a>
140
- </li>
141
- <li class="govuk-footer__inline-list-item">
142
- <a class="govuk-footer__link" href="#">
143
- Cookies
144
- </a>
145
- </li>
146
- <li class="govuk-footer__inline-list-item">
147
- <a class="govuk-footer__link" href="#">
148
- Contact
149
- </a>
150
- </li>
151
- <li class="govuk-footer__inline-list-item">
152
- <a class="govuk-footer__link" href="#">
153
- Terms and conditions
154
- </a>
155
- </li>
136
+ <% if controller.footer_links.present? %>
137
+ <% controller.footer_links.each do |link| %>
138
+ <li class="govuk-footer__inline-list-item">
139
+ <%= link_to link[:name],
140
+ link[:href],
141
+ { class: "govuk-footer__link" }.merge(link[:options] || {}) %>
142
+ </li>
143
+ <% end %>
144
+ <% end %>
156
145
  </ul>
157
146
  <svg
158
147
  aria-hidden="true"
@@ -175,11 +164,11 @@
175
164
  </span>
176
165
  </div>
177
166
  <div class="govuk-footer__meta-item">
178
- <a
179
- class="govuk-footer__link govuk-footer__copyright-logo"
180
- href="https://www.nationalarchives.gov.uk/information-management/re-using-public-sector-information/uk-government-licensing-framework/crown-copyright/">
181
- © Crown copyright
182
- </a>
167
+ <% if controller.copyright_notice.present? %>
168
+ <span class="govuk-footer__link govuk-footer__copyright-logo">
169
+ <%= controller.copyright_notice %>
170
+ </span>
171
+ <% end %>
183
172
  </div>
184
173
  </div>
185
174
  </div>
@@ -86,46 +86,33 @@
86
86
  </main>
87
87
  </div>
88
88
 
89
- <footer role="contentinfo">
90
- <div class="nhsuk-footer-container">
91
- <div class="nhsuk-width-container">
92
- <h2 class="nhsuk-u-visually-hidden">Support links</h2>
93
- <div class="nhsuk-footer">
94
-
95
- <ul class="nhsuk-footer__list">
96
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
97
- <a class="nhsuk-footer__list-item-link" href="#">NHS sites</a>
98
- </li>
99
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
100
- <a class="nhsuk-footer__list-item-link" href="#">About us</a>
101
- </li>
102
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
103
- <a class="nhsuk-footer__list-item-link" href="#">Contact us</a>
104
- </li>
105
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
106
- <a class="nhsuk-footer__list-item-link" href="#">Profile editor login</a>
107
- </li>
108
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
109
- <a class="nhsuk-footer__list-item-link" href="#">Site map</a>
110
- </li>
111
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
112
- <a class="nhsuk-footer__list-item-link" href="#">Accessibility statement</a>
113
- </li>
114
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
115
- <a class="nhsuk-footer__list-item-link" href="#">Our policies</a>
116
- </li>
117
- <li class="nhsuk-footer__list-item nhsuk-footer-default__list-item">
118
- <a class="nhsuk-footer__list-item-link" href="#">Cookies</a>
119
- </li>
120
- </ul>
121
- <div>
122
- <p class="nhsuk-footer__copyright">&copy; NHS England</p>
123
- </div>
89
+ <footer role="contentinfo">
90
+ <div class="nhsuk-footer-container">
91
+ <div class="nhsuk-width-container">
92
+ <h2 class="nhsuk-u-visually-hidden">Support links</h2>
93
+ <div class="nhsuk-footer">
94
+ <ul class="nhsuk-footer__list">
95
+ <% if controller.footer_links.present? %>
96
+ <% controller.footer_links.each do |link| %>
97
+ <li class="nhsuk-footer__list-item">
98
+ <%= link_to link[:name],
99
+ link[:href],
100
+ { class: "nhsuk-footer__list-item-link" }.merge(link[:options] || {}) %>
101
+ </li>
102
+ <% end %>
103
+ <% end %>
104
+ </ul>
105
+ <div>
106
+ <p class="nhsuk-footer__copyright">
107
+ <% if controller.copyright_notice.present? %>
108
+ <%= controller.copyright_notice %>
109
+ <% end %>
110
+ </p>
124
111
  </div>
125
-
126
112
  </div>
127
113
  </div>
128
- </footer>
114
+ </div>
115
+ </footer>
129
116
 
130
117
  </body>
131
118
 
@@ -7,18 +7,15 @@
7
7
  <%= link_to(item[:label], item[:path], item[:options].merge(class: 'nhsuk-header__navigation-link')) %>
8
8
  </li>
9
9
  <% end %>
10
- <li class="nhsuk-header__navigation-item nhsuk-header__navigation-item--home">
11
- <%= link_to('Home', root_path, class: 'nhsuk-header__navigation-link') %>
12
- </li>
13
- <li class="nhsuk-mobile-menu-container">
14
- <button class="nhsuk-header__menu-toggle nhsuk-header__navigation-link" id="toggle-menu" aria-expanded="false">
15
- <span class="nhsuk-u-visually-hidden">Browse</span>
16
- More
17
- <svg class="nhsuk-icon nhsuk-icon__chevron-down" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
18
- <path d="M15.5 12a1 1 0 0 1-.29.71l-5 5a1 1 0 0 1-1.42-1.42l4.3-4.29-4.3-4.29a1 1 0 0 1 1.42-1.42l5 5a1 1 0 0 1 .29.71z"></path>
19
- </svg>
20
- </button>
21
- </li>
10
+ <li class="nhsuk-mobile-menu-container">
11
+ <button class="nhsuk-header__menu-toggle nhsuk-header__navigation-link" id="toggle-menu" aria-expanded="false">
12
+ <span class="nhsuk-u-visually-hidden">Browse</span>
13
+ More
14
+ <svg class="nhsuk-icon nhsuk-icon__chevron-down" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
15
+ <path d="M15.5 12a1 1 0 0 1-.29.71l-5 5a1 1 0 0 1-1.42-1.42l4.3-4.29-4.3-4.29a1 1 0 0 1 1.42-1.42l5 5a1 1 0 0 1 .29.71z"></path>
16
+ </svg>
17
+ </button>
18
+ </li>
22
19
  </ul>
23
20
  </nav>
24
21
  </div>
@@ -4,9 +4,6 @@ require 'stimulus-rails'
4
4
  module DesignSystem
5
5
  # This is the main engine class for the design system.
6
6
  class Engine < ::Rails::Engine
7
- # Allow changes to the design system to be reloaded in development.
8
- config.autoload_paths << File.expand_path('..', __dir__) if Rails.env.development?
9
-
10
7
  initializer 'design_system.importmap', before: 'importmap' do |app|
11
8
  app.config.importmap.paths << Engine.root.join('config/importmap.rb')
12
9
  end
@@ -5,8 +5,9 @@ module DesignSystem
5
5
  module Builders
6
6
  # This is the base class for design system builders.
7
7
  class Base
8
- include CssHelper
9
8
  include DesignSystem::Generic::Builders::Concerns::BrandDerivable
9
+ include DesignSystem::Helpers::CssHelper
10
+
10
11
  delegate :button_tag, :capture, :content_for, :content_tag, :link_to, :link_to_unless_current, to: :@context
11
12
 
12
13
  def initialize(context)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module DesignSystem
6
4
  module Generic
7
5
  module Builders
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
- require_relative 'elements/breadcrumbs'
5
- require_relative 'elements/form'
6
- require_relative 'elements/headings'
7
-
8
3
  module DesignSystem
9
4
  module Generic
10
5
  module Builders
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base'
4
-
5
3
  module DesignSystem
6
4
  module Generic
7
5
  module Builders
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/components/summary_list'
4
- require_relative 'base'
5
-
6
3
  module DesignSystem
7
4
  module Generic
8
5
  module Builders
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/components/tab'
4
- require_relative 'base'
5
-
6
3
  module DesignSystem
7
4
  module Generic
8
5
  module Builders
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/components/table'
4
- require_relative 'base'
5
-
6
3
  module DesignSystem
7
4
  module Generic
8
5
  module Builders
@@ -2,6 +2,8 @@ module DesignSystem
2
2
  module Generic
3
3
  # The generic version of the form builder
4
4
  class FormBuilder < ActionView::Helpers::FormBuilder
5
+ include DesignSystem::Helpers::CssHelper
6
+
5
7
  delegate :content_tag, :tag, :safe_join, :link_to, :capture, to: :@template
6
8
 
7
9
  def self.brand
@@ -10,18 +12,6 @@ module DesignSystem
10
12
 
11
13
  private
12
14
 
13
- # Helper copied from https://github.com/NHSDigital/ndr_ui/blob/main/app/helpers/ndr_ui/css_helper.rb with thanks.
14
- # This method merges the specified css_classes into the options hash
15
- def css_class_options_merge(options, css_classes = [])
16
- options = options.symbolize_keys
17
- css_classes += options[:class].split if options.include?(:class)
18
- yield(css_classes) if block_given?
19
- options[:class] = css_classes.join(' ') unless css_classes.empty?
20
- raise "Multiple css class definitions: #{css_classes.inspect}" unless css_classes == css_classes.uniq
21
-
22
- options
23
- end
24
-
25
15
  # This method exposes some useful Rails magic as a helper method
26
16
  def separate_content_or_options(content_or_options = nil, options = nil)
27
17
  content = nil
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/generic/builders/button'
4
-
5
3
  module DesignSystem
6
4
  module Govuk
7
5
  module Builders
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/generic/builders/fixed_elements'
4
- require_relative 'elements/breadcrumbs'
5
- require_relative 'elements/headings'
6
-
7
3
  module DesignSystem
8
4
  module Govuk
9
5
  module Builders
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/generic/builders/link'
4
-
5
3
  module DesignSystem
6
4
  module Govuk
7
5
  module Builders
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/generic/builders/summary_list'
4
-
5
3
  module DesignSystem
6
4
  module Govuk
7
5
  module Builders
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/generic/builders/tab'
4
-
5
3
  module DesignSystem
6
4
  module Govuk
7
5
  module Builders
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'design_system/generic/builders/table'
4
-
5
3
  module DesignSystem
6
4
  module Govuk
7
5
  module Builders
@@ -1,4 +1,3 @@
1
- require 'design_system/generic/form_builder'
2
1
  require 'govuk_design_system_formbuilder'
3
2
 
4
3
  module DesignSystem
@@ -0,0 +1,99 @@
1
+ module DesignSystem
2
+ module Govuk
3
+ module TestHelpers
4
+ # This module provides assertion methods for form elements using the GOV.UK Design System
5
+ module FormBuilderAssertionsHelper
6
+ # Asserts the presence and attributes of a file upload input
7
+ def assert_file_upload(field = nil, type: nil, value: nil, classes: [], attributes: {}, model: 'assistant')
8
+ assert_form_element('input', 'file-upload', field, type:, value:, classes:, attributes:, model:)
9
+ end
10
+
11
+ # Assert the presence of a form group
12
+ def assert_form_group(classes = [])
13
+ assert_select('form') do
14
+ form_group = assert_select("div.#{@brand}-form-group#{classes.map { |c| ".#{c}" }.join}").first
15
+ assert form_group, "Form group not found with classes: #{classes.join(', ')}"
16
+ yield(form_group) if block_given?
17
+ end
18
+ end
19
+
20
+ # Asserts the presence and attributes of a hint
21
+ def assert_hint(field = nil, value = nil, text = nil, classes: [], model: 'assistant')
22
+ base_class = "div.#{@brand}-hint"
23
+ class_selector = base_class + classes.map { |c| ".#{c}" }.join
24
+
25
+ selector = if value
26
+ "#{class_selector}[id='#{model}_#{field}_#{value}_hint']"
27
+ else
28
+ "#{class_selector}[id='#{model}_#{field}_hint']"
29
+ end
30
+
31
+ assert_select(selector, text)
32
+ end
33
+
34
+ # Asserts the presence and attributes of an input field
35
+ def assert_input(field = nil, type: nil, value: nil, classes: [], attributes: {}, model: 'assistant')
36
+ assert_form_element('input', 'input', field, type:, value:, classes:, attributes:, model:)
37
+ end
38
+
39
+ # Asserts the presence and attributes of a label
40
+ # TODO: support special labels like checkbox_label?
41
+ def assert_label(field = nil, value = nil, text = nil, model: 'assistant', classes: [])
42
+ selector = if value
43
+ "label.#{@brand}-label[for='#{model}_#{field}_#{value}']"
44
+ else
45
+ "label.#{@brand}-label[for='#{model}_#{field}']"
46
+ end
47
+ selector << classes.map { |c| ".#{c}" }.join
48
+ assert_select(selector, text)
49
+ end
50
+
51
+ # Asserts the presence and attributes of a text area
52
+ def assert_text_area(field = nil, value: nil, classes: [], attributes: {}, model: 'assistant')
53
+ assert_form_element('textarea', 'textarea', field, value:, classes:, attributes:, model:)
54
+ end
55
+
56
+ private
57
+
58
+ # Asserts the presence and attributes of a form element
59
+ #
60
+ # @param element_type [Symbol] The type of HTML element to assert (:input, :textarea, :file_upload)
61
+ # @param base_class [String] The class name of the element in the design system, prefixed by the brand name (e.g., 'input', 'textarea', 'file-upload')
62
+ # @param field [Symbol] The form field name (e.g., :title, :description)
63
+ # @param options [Hash] Additional options for the element
64
+ # @option options [String] :type The HTML input type (e.g., 'text', 'email', 'tel')
65
+ # @option options [String] :value The expected value of the field
66
+ # @option options [Array<String>] :classes Additional CSS classes to check for
67
+ # @option options [Hash] :attributes Additional HTML attributes to verify
68
+ # @option options [String] :model The model name for the field (defaults to 'assistant')
69
+ #
70
+ # @example
71
+ # assert_form_element(:input, :title, type: :text, value: 'Lorem ipsum dolor sit amet')
72
+ # assert_form_element(:textarea, :description, classes: ['custom-class'])
73
+ # assert_form_element(:file_upload, :cv, type: :file, attributes: { accept: 'application/pdf' })
74
+ def assert_form_element(element_type, base_class, field, options = {})
75
+ base_classes = ["#{@brand}-#{base_class}"]
76
+ classes = (base_classes + Array(options[:classes])).flatten.compact
77
+
78
+ attributes = {
79
+ id: "#{options[:model] || 'assistant'}_#{field}",
80
+ name: "#{options[:model] || 'assistant'}[#{field}]"
81
+ }.merge(options[:attributes] || {})
82
+
83
+ # Build the selector with each class as a separate class attribute
84
+ class_selector = classes.map { |c| ".#{c}" }.join
85
+ element = assert_select("#{element_type}#{class_selector}").first
86
+ assert element,
87
+ "#{element_type.capitalize} not found with type: #{options[:type]} and classes: #{classes.join(', ')}"
88
+
89
+ attributes.each do |key, expected_value|
90
+ assert_equal expected_value.to_s, element[key.to_s],
91
+ "Expected #{key} to be '#{expected_value}' but was '#{element[key.to_s]}'"
92
+ end
93
+
94
+ assert_equal options[:value], element['value'] if options[:value]
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end