primer_view_components 0.0.60 → 0.0.61

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -1
  3. data/app/components/primer/alpha/layout.html.erb +5 -0
  4. data/app/components/primer/alpha/layout.rb +276 -0
  5. data/app/components/primer/base_button.rb +1 -5
  6. data/app/components/primer/base_component.rb +7 -2
  7. data/app/components/primer/beta/blankslate.html.erb +15 -0
  8. data/app/components/primer/beta/blankslate.rb +240 -0
  9. data/app/components/primer/blankslate_component.rb +1 -1
  10. data/app/components/primer/component.rb +2 -2
  11. data/app/components/primer/hellip_button.rb +39 -0
  12. data/app/components/primer/hidden_text_expander.rb +18 -6
  13. data/app/components/primer/subhead_component.rb +1 -1
  14. data/lib/primer/classify.rb +3 -3
  15. data/lib/primer/view_components/engine.rb +1 -1
  16. data/lib/primer/view_components/linters/base_linter.rb +3 -52
  17. data/lib/primer/view_components/linters/blankslate_api_migration.rb +146 -0
  18. data/lib/primer/view_components/linters/blankslate_component_migration_counter.rb +1 -1
  19. data/lib/primer/view_components/linters/close_button_component_migration_counter.rb +2 -4
  20. data/lib/primer/view_components/linters/helpers/rubocop_helpers.rb +14 -0
  21. data/lib/primer/view_components/linters/tag_tree_helpers.rb +61 -0
  22. data/lib/primer/view_components/linters/two_column_layout_migration_counter.rb +158 -0
  23. data/lib/primer/view_components/version.rb +1 -1
  24. data/lib/tasks/docs.rake +50 -1
  25. data/static/arguments.yml +52 -67
  26. data/static/audited_at.json +5 -0
  27. data/static/classes.yml +20 -2
  28. data/static/constants.json +103 -0
  29. data/static/statuses.json +6 -1
  30. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb6606f92936041aed71037f2590bdd1ea29778b963a5396f19dcf8d57b70dc5
4
- data.tar.gz: 2f0525e24ddb1e5fa304220caaec413b52e4543c81196e2401a33ca4fb13fdbc
3
+ metadata.gz: 61a72c86047d4820bb909bb7aba73505a1401868e362831ce3cfcd44f6c65da2
4
+ data.tar.gz: cedb04ce42976410ad380e5d39e448fc2078f07fb3d26b3d2ce4e7d79141f6ea
5
5
  SHA512:
6
- metadata.gz: 525f0e51074bc3490763836ce5060180d6222048333b53a768ae4cb287e6cff234ef69a5b570b760328fc4a26da165bf586694b3a0ecdf8287d750426c258541
7
- data.tar.gz: ba7fd3fbb1ab40e214fa6be6ceda52b84875d48c49f7aa75c790d3d594049b0a183e4a94959f36a82ccafa47acdc4ee9c1e0d065f33659ceb710adabbd50e933
6
+ metadata.gz: 714f04473ef45b3a80c4fcad50dbcd575a981c93b990f770844e903d561dc016cc88a94b09567deaa9954680add33581bc002c6f80b3a3590d2e64faf9397c63
7
+ data.tar.gz: 8d982a43c2fd2a0d5fe32287c129c8d4559ae2441e3124f9f50857ccbf25e2a4a3b63426e6f5a1c24572d6d8804b90849a3e600e3680de3efdbc59c92de0293e
data/CHANGELOG.md CHANGED
@@ -30,6 +30,76 @@ The category for changes related to documentation, testing and tooling. Also, fo
30
30
 
31
31
  ## main
32
32
 
33
+ ## 0.0.61
34
+
35
+ ### New
36
+
37
+ * Adding new Alpha component: `Layout` with `main` and `sidebar` slots
38
+
39
+ *Cameron Dutro*
40
+
41
+ * Add a two-column layout linter.
42
+
43
+ *Cameron Dutro*
44
+
45
+ * Add the `HellipButton` component
46
+
47
+ *Amélia Chavot*, *Owen Niblock*
48
+
49
+ ### Updates
50
+
51
+ * Bump Storybook version to include Skip to Content links for keyboard auditors
52
+
53
+ *Katie Foster @inkblotty*
54
+
55
+ * Update the `HiddenTextExpander` component to use the `HellipButton`.
56
+
57
+ *Amélia Chavot*, *Owen Niblock*
58
+
59
+ ### Misc
60
+
61
+ * Fix components not rendering in Storybook because of kebab case arguments.
62
+
63
+ *Amélia Chavot*, *Manuel Puyol*, *Owen Niblock*
64
+
65
+ * Fix a typo on a command on the contribution page.
66
+
67
+ *Amélia Chavot*, *Owen Niblock*
68
+
69
+ ### Bug Fixes
70
+
71
+ * Fix issue where tags were not self-closing when they are void elements
72
+
73
+ *Owen Niblock*
74
+
75
+ ### Deprecations
76
+
77
+ * Deprecate `Primer::BlankslateComponent` in favor of `Primer::Beta::Blankslate`.
78
+
79
+ *Manuel Puyol*
80
+
81
+ ### Breaking Changes
82
+
83
+ * Require an `aria-label` to be provided for the `HiddenTextExpander` component.
84
+
85
+ *Amélia Chavot*, *Owen Niblock*
86
+
87
+ * Rename `force_system_arguments` to `raise_on_invalid_options` to better reflect its functionality
88
+
89
+ *Owen Niblock*
90
+
91
+ * Renamed `Blankslate` `title` slot to `heading`.
92
+
93
+ *Manuel Puyol*
94
+
95
+ * Removed `Blankslate` `large` variant.
96
+
97
+ *Manuel Puyol*
98
+
99
+ * Renamed `Blankslate` `graphic` slot to `visual`.
100
+
101
+ *Manuel Puyol*
102
+
33
103
  ## 0.0.60
34
104
 
35
105
  ### Updates
@@ -535,7 +605,7 @@ The category for changes related to documentation, testing and tooling. Also, fo
535
605
 
536
606
  *Manuel Puyol*
537
607
 
538
- * Added a changelog authoring guide to `CHANGELOG.md`.
608
+ * Add a changelog authoring guide to `CHANGELOG.md`.
539
609
 
540
610
  *Amélia Chavot*
541
611
 
@@ -0,0 +1,5 @@
1
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
+ <% if @first_in_source == :main %><%= main %><% end %>
3
+ <%= sidebar %>
4
+ <% if @first_in_source == :sidebar %><%= main %><% end %>
5
+ <% end %>
@@ -0,0 +1,276 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Alpha
5
+ # `Layout` provides foundational patterns for responsive pages.
6
+ # `Layout` can be used for simple two-column pages, or it can be nested to provide flexible 3-column experiences.
7
+ # On smaller screens, `Layout` uses vertically stacked rows to display content.
8
+ #
9
+ # `Layout` flows as both column, when there's enough horizontal space to render both `Main` and `Sidebar`side-by-side (on a desktop of tablet device, per instance);
10
+ # or it flows as a row, when `Main` and `Sidebar` are stacked vertically (e.g. on a mobile device).
11
+ # `Layout` should always work in any screen size.
12
+ #
13
+ # @accessibility
14
+ # Keyboard navigation follows the markup order. Decide carefully how the focus order should be be by deciding whether
15
+ # `main` or `sidebar` comes first in code. The code order won’t affect the visual position.
16
+ class Layout < Primer::Component
17
+ FIRST_IN_SOURCE_DEFAULT = :sidebar
18
+ FIRST_IN_SOURCE_OPTIONS = [FIRST_IN_SOURCE_DEFAULT, :main].freeze
19
+
20
+ SIDEBAR_COL_PLACEMENT_DEFAULT = :start
21
+ SIDEBAR_COL_PLACEMENT_OPTIONS = [SIDEBAR_COL_PLACEMENT_DEFAULT, :end].freeze
22
+
23
+ GUTTER_DEFAULT = :default
24
+ GUTTER_MAPPINGS = {
25
+ :none => "Layout--gutter-none",
26
+ :condensed => "Layout--gutter-condensed",
27
+ :spacious => "Layout--gutter-spacious",
28
+ GUTTER_DEFAULT => ""
29
+ }.freeze
30
+ GUTTER_OPTIONS = GUTTER_MAPPINGS.keys.freeze
31
+
32
+ STACKING_BREAKPOINT_DEFAULT = :md
33
+ STACKING_BREAKPOINT_MAPPINGS = {
34
+ :sm => "",
35
+ STACKING_BREAKPOINT_DEFAULT => "Layout--flowRow-until-md",
36
+ :lg => "Layout--flowRow-until-lg"
37
+ }.freeze
38
+ STACKING_BREAKPOINT_OPTIONS = STACKING_BREAKPOINT_MAPPINGS.keys.freeze
39
+
40
+ SIDEBAR_ROW_PLACEMENT_DEFAULT = :start
41
+ SIDEBAR_ROW_PLACEMENT_OPTIONS = [SIDEBAR_ROW_PLACEMENT_DEFAULT, :end, :none].freeze
42
+
43
+ SIDEBAR_WIDTH_DEFAULT = :default
44
+ SIDEBAR_WIDTH_MAPPINGS = {
45
+ SIDEBAR_WIDTH_DEFAULT => "",
46
+ :narrow => "Layout--sidebar-narrow",
47
+ :wide => "Layout--sidebar-wide"
48
+ }.freeze
49
+ SIDEBAR_WIDTH_OPTIONS = SIDEBAR_WIDTH_MAPPINGS.keys.freeze
50
+
51
+ # The layout's main content.
52
+ #
53
+ # @param width [Symbol] <%= one_of(Primer::Alpha::Layout::Main::WIDTH_OPTIONS) %>
54
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
55
+ renders_one :main, "Primer::Alpha::Layout::Main"
56
+
57
+ # The layout's sidebar.
58
+ #
59
+ # @param width [Symbol] <%= one_of(Primer::Alpha::Layout::SIDEBAR_WIDTH_OPTIONS) %>
60
+ # @param col_placement [Symbol] Sidebar placement when `Layout` is in column modes. <%= one_of(Primer::Alpha::Layout::SIDEBAR_COL_PLACEMENT_OPTIONS) %>
61
+ # @param row_placement [Symbol] Sidebar placement when `Layout` is in row mode. <%= one_of(Primer::Alpha::Layout::SIDEBAR_ROW_PLACEMENT_OPTIONS) %>
62
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
63
+ renders_one :sidebar, lambda { |
64
+ width: SIDEBAR_WIDTH_DEFAULT,
65
+ col_placement: SIDEBAR_COL_PLACEMENT_DEFAULT,
66
+ row_placement: SIDEBAR_ROW_PLACEMENT_DEFAULT,
67
+ **system_arguments
68
+ |
69
+ # These classes have to be set in the parent `Layout` element, so we modify its system arguments.
70
+ @system_arguments[:classes] = class_names(
71
+ @system_arguments[:classes],
72
+ "Layout--sidebarPosition-#{fetch_or_fallback(SIDEBAR_COL_PLACEMENT_OPTIONS, col_placement, SIDEBAR_COL_PLACEMENT_DEFAULT)}",
73
+ "Layout--sidebarPosition-flowRow-#{fetch_or_fallback(SIDEBAR_ROW_PLACEMENT_OPTIONS, row_placement, SIDEBAR_ROW_PLACEMENT_DEFAULT)}",
74
+ SIDEBAR_WIDTH_MAPPINGS[fetch_or_fallback(SIDEBAR_WIDTH_OPTIONS, width, SIDEBAR_WIDTH_DEFAULT)]
75
+ )
76
+
77
+ Primer::Alpha::Layout::Sidebar.new(**system_arguments)
78
+ }
79
+
80
+ # @example Default
81
+ #
82
+ # <%= render(Primer::Alpha::Layout.new) do |c| %>
83
+ # <% c.main(border: true) { "Main" } %>
84
+ # <% c.sidebar(border: true) { "Sidebar" } %>
85
+ # <% end %>
86
+ #
87
+ # @example Main widths
88
+ #
89
+ # @description
90
+ # When `full`, the main column will stretch to cover all the available width.
91
+ # Otherwise, the main column will try to be centered in the screen; it may appear aligned to the left when there isn't enough space.
92
+ #
93
+ # Use smaller maximum widths in the main column to facilitate interface scanning and reading.
94
+ #
95
+ # When flowing as a row, `Main` takes the full width.
96
+ #
97
+ # @code
98
+ # <%= render(Primer::Alpha::Layout.new) do |c| %>
99
+ # <% c.main(width: :full, border: true) { "Main" } %>
100
+ # <% c.sidebar(border: true) { "Sidebar" } %>
101
+ # <% end %>
102
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
103
+ # <% c.main(width: :md, border: true) { "Main" } %>
104
+ # <% c.sidebar(border: true) { "Sidebar" } %>
105
+ # <% end %>
106
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
107
+ # <% c.main(width: :lg, border: true) { "Main" } %>
108
+ # <% c.sidebar(border: true) { "Sidebar" } %>
109
+ # <% end %>
110
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
111
+ # <% c.main(width: :xl, border: true) { "Main" } %>
112
+ # <% c.sidebar(border: true) { "Sidebar" } %>
113
+ # <% end %>
114
+ #
115
+ # @example Sidebar widths
116
+ #
117
+ # @description
118
+ # Sets the sidebar width. The width is predetermined according to the breakpoint instead of it being percentage-based.
119
+ #
120
+ # - `default`: [md: 256px, lg: 296px, xl: 320px]
121
+ # - `narrow`: [md: 240px, lg: 256px, xl: 296px]
122
+ # - `wide`: [md: 296px, lg: 320px, xl: 344px]
123
+ #
124
+ # When flowing as a row, `Sidebar` takes the full width.
125
+ #
126
+ # @code
127
+ # <%= render(Primer::Alpha::Layout.new) do |c| %>
128
+ # <% c.main(border: true) { "Main" } %>
129
+ # <% c.sidebar(width: :default, border: true) { "Sidebar" } %>
130
+ # <% end %>
131
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
132
+ # <% c.main(border: true) { "Main" } %>
133
+ # <% c.sidebar(width: :narrow, border: true) { "Sidebar" } %>
134
+ # <% end %>
135
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
136
+ # <% c.main(border: true) { "Main" } %>
137
+ # <% c.sidebar(width: :wide, border: true) { "Sidebar" } %>
138
+ # <% end %>
139
+ #
140
+ # @example Sidebar placement
141
+ #
142
+ # @description
143
+ # Use `start` for sidebars that manipulate local navigation, while right-aligned `end` is useful for metadata and other auxiliary information.
144
+ #
145
+ # @code
146
+ # <%= render(Primer::Alpha::Layout.new) do |c| %>
147
+ # <% c.main(border: true) { "Main" } %>
148
+ # <% c.sidebar(col_placement: :start, border: true) { "Sidebar" } %>
149
+ # <% end %>
150
+ # <%= render(Primer::Alpha::Layout.new( mt: 5)) do |c| %>
151
+ # <% c.main(border: true) { "Main" } %>
152
+ # <% c.sidebar(col_placement: :end, border: true) { "Sidebar" } %>
153
+ # <% end %>
154
+ #
155
+ # @example Sidebar placement as row
156
+ #
157
+ # @description
158
+ # When flowing as a row, whether the sidebar is rendered first or last in the layout, or, if it's entirely hidden from the user.
159
+ #
160
+ # When `hidden`, make sure the experience is not degraded on smaller screens, and the user can still access the sidebar content somehow.
161
+ # For instance, the user may not see a Settings navigation sidebar when drilled down on a page, but they can still navigate to the Settings
162
+ # landing page to interact with the local navigation.
163
+ #
164
+ # @code
165
+ # <%= render(Primer::Alpha::Layout.new) do |c| %>
166
+ # <% c.main(border: true) { "Main" } %>
167
+ # <% c.sidebar(row_placement: :start, border: true) { "Sidebar" } %>
168
+ # <% end %>
169
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
170
+ # <% c.main(border: true) { "Main" } %>
171
+ # <% c.sidebar(row_placement: :end, border: true) { "Sidebar" } %>
172
+ # <% end %>
173
+ # <%= render(Primer::Alpha::Layout.new(mt: 5)) do |c| %>
174
+ # <% c.main(border: true) { "Main" } %>
175
+ # <% c.sidebar(row_placement: :none, border: true) { "Sidebar" } %>
176
+ # <% end %>
177
+ #
178
+ # @example Changing when to render `Layout` as columns
179
+ #
180
+ # @description
181
+ # You can specify when the `Layout` should change from rows into columns.
182
+ # Any screen size before this breakpoint will render the `Layout` in stacked rows.
183
+ #
184
+ # @code
185
+ # <%= render(Primer::Alpha::Layout.new(stacking_breakpoint: :sm)) do |c| %>
186
+ # <% c.main(border: true) { "Main" } %>
187
+ # <% c.sidebar(border: true) { "Sidebar" } %>
188
+ # <% end %>
189
+ # <%= render(Primer::Alpha::Layout.new(stacking_breakpoint: :md, mt: 5)) do |c| %>
190
+ # <% c.main(border: true) { "Main" } %>
191
+ # <% c.sidebar(border: true) { "Sidebar" } %>
192
+ # <% end %>
193
+ # <%= render(Primer::Alpha::Layout.new(stacking_breakpoint: :lg, mt: 5)) do |c| %>
194
+ # <% c.main(border: true) { "Main" } %>
195
+ # <% c.sidebar(border: true) { "Sidebar" } %>
196
+ # <% end %>
197
+ #
198
+ # @param stacking_breakpoint [Symbol] When the `Layout` should change from rows into columns. <%= one_of(Primer::Alpha::Layout::STACKING_BREAKPOINT_OPTIONS) %>
199
+ # @param first_in_source [Symbol] Which element to render first in the HTML. This will change the keyboard navigation order. <%= one_of(Primer::Alpha::Layout::FIRST_IN_SOURCE_OPTIONS) %>
200
+ # @param gutter [Symbol] The amount of space between the main section and the sidebar. <%= one_of(Primer::Alpha::Layout::GUTTER_OPTIONS) %>
201
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
202
+ def initialize(stacking_breakpoint: STACKING_BREAKPOINT_DEFAULT, first_in_source: FIRST_IN_SOURCE_DEFAULT, gutter: :default, **system_arguments)
203
+ @first_in_source = fetch_or_fallback(FIRST_IN_SOURCE_OPTIONS, first_in_source, FIRST_IN_SOURCE_OPTIONS)
204
+
205
+ @system_arguments = system_arguments
206
+ @system_arguments[:tag] = :div
207
+ @system_arguments[:classes] = class_names(
208
+ "Layout",
209
+ STACKING_BREAKPOINT_MAPPINGS[fetch_or_fallback(STACKING_BREAKPOINT_OPTIONS, stacking_breakpoint, STACKING_BREAKPOINT_DEFAULT)],
210
+ GUTTER_MAPPINGS[fetch_or_fallback(GUTTER_OPTIONS, gutter, GUTTER_DEFAULT)],
211
+ system_arguments[:classes]
212
+ )
213
+ end
214
+
215
+ def render?
216
+ main.present? && sidebar.present?
217
+ end
218
+
219
+ # The layout's main content.
220
+ class Main < Primer::Component
221
+ WIDTH_DEFAULT = :full
222
+ WIDTH_OPTIONS = [WIDTH_DEFAULT, :md, :lg, :xl].freeze
223
+
224
+ TAG_DEFAULT = :div
225
+ TAG_OPTIONS = [TAG_DEFAULT, :main].freeze
226
+
227
+ # @param width [Symbol] <%= one_of(Primer::Alpha::Layout::Main::WIDTH_OPTIONS) %>
228
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
229
+ def initialize(tag: TAG_DEFAULT, width: WIDTH_DEFAULT, **system_arguments)
230
+ @width = fetch_or_fallback(WIDTH_OPTIONS, width, WIDTH_DEFAULT)
231
+
232
+ @system_arguments = system_arguments
233
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, TAG_DEFAULT)
234
+ @system_arguments[:classes] = class_names(
235
+ "Layout-main",
236
+ system_arguments[:classes]
237
+ )
238
+ end
239
+
240
+ def call
241
+ render(Primer::BaseComponent.new(**@system_arguments)) do
242
+ if @width == :full
243
+ content
244
+ else
245
+ render(Primer::BaseComponent.new(tag: :div, classes: "Layout-main-centered-#{@width}")) do
246
+ render(Primer::BaseComponent.new(tag: :div, container: @width)) do
247
+ content
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ # The layout's sidebar content.
256
+ class Sidebar < Primer::Component
257
+ TAG_DEFAULT = :div
258
+ TAG_OPTIONS = [TAG_DEFAULT, :aside, :nav, :section].freeze
259
+
260
+ def initialize(tag: TAG_DEFAULT, **system_arguments)
261
+ @system_arguments = system_arguments
262
+
263
+ @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, TAG_DEFAULT)
264
+ @system_arguments[:classes] = class_names(
265
+ "Layout-sidebar",
266
+ @system_arguments[:classes]
267
+ )
268
+ end
269
+
270
+ def call
271
+ render(Primer::BaseComponent.new(**@system_arguments)) { content }
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
@@ -28,11 +28,7 @@ module Primer
28
28
  @system_arguments = system_arguments
29
29
  @system_arguments[:tag] = fetch_or_fallback(TAG_OPTIONS, tag, DEFAULT_TAG)
30
30
 
31
- if @system_arguments[:tag] == :button
32
- @system_arguments[:type] = fetch_or_fallback(TYPE_OPTIONS, type, DEFAULT_TYPE)
33
- else
34
- @system_arguments[:role] = :button
35
- end
31
+ @system_arguments[:type] = fetch_or_fallback(TYPE_OPTIONS, type, DEFAULT_TYPE) if @system_arguments[:tag] == :button
36
32
 
37
33
  @system_arguments[:classes] = class_names(
38
34
  system_arguments[:classes],
@@ -27,6 +27,7 @@ module Primer
27
27
  class BaseComponent < Primer::Component
28
28
  status :beta
29
29
 
30
+ SELF_CLOSING_TAGS = [:area, :base, :br, :col, :embed, :hr, :img, :input, :link, :meta, :param, :source, :track, :wbr].freeze
30
31
  # ## HTML attributes
31
32
  #
32
33
  # System arguments include most HTML attributes. For example:
@@ -155,7 +156,7 @@ module Primer
155
156
  raise ArgumentError, "`class` is an invalid argument. Use `classes` instead." if system_arguments.key?(:class) && !Rails.env.production?
156
157
 
157
158
  if (denylist = system_arguments[:system_arguments_denylist])
158
- if force_system_arguments? && !ENV["PRIMER_WARNINGS_DISABLED"]
159
+ if raise_on_invalid_options? && !ENV["PRIMER_WARNINGS_DISABLED"]
159
160
  # Convert denylist from:
160
161
  # { [:p, :pt] => "message" } to:
161
162
  # { p: "message", pt: "message" }
@@ -189,7 +190,11 @@ module Primer
189
190
  end
190
191
 
191
192
  def call
192
- content_tag(@tag, content, @content_tag_args.merge(@result))
193
+ if SELF_CLOSING_TAGS.include?(@tag)
194
+ tag(@tag, @content_tag_args.merge(@result))
195
+ else
196
+ content_tag(@tag, content, @content_tag_args.merge(@result))
197
+ end
193
198
  end
194
199
  end
195
200
  end
@@ -0,0 +1,15 @@
1
+ <%= wrapper do %>
2
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
3
+ <%= visual %>
4
+
5
+ <%= heading %>
6
+ <%= description %>
7
+
8
+ <%= primary_action %>
9
+ <% if secondary_action.present? %>
10
+ <p>
11
+ <%= secondary_action %>
12
+ </p>
13
+ <% end %>
14
+ <% end %>
15
+ <% end %>
@@ -0,0 +1,240 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Primer
4
+ module Beta
5
+ # Use `Blankslate` when there is a lack of content within a page or section. Use as placeholder to tell users why something isn't there.
6
+ #
7
+ # @accessibility
8
+ # - Set the `heading` level based on what is appropriate for your page hierarchy. <%= link_to_heading_practices %>
9
+ # - `secondary_action` can be set to provide more information that is relevant in the context of the `Blankslate`.
10
+ # - `secondary_action` text should be meaningful out of context and clearly describe the destination. Avoid using vague text like, "Learn more" or "Click here".
11
+ class Blankslate < Primer::Component
12
+ include ViewComponent::PolymorphicSlots
13
+
14
+ status :beta
15
+
16
+ VISUAL_OPTIONS = %i[icon spinner image].freeze
17
+
18
+ # Optional visual.
19
+ #
20
+ # Use:
21
+ #
22
+ # - `visual_icon` for an <%= link_to_component(Primer::OcticonComponent) %>.
23
+ # - `visual_image` for an <%= link_to_component(Primer::Image) %>.
24
+ # - `visual_spinner` for a <%= link_to_component(Primer::SpinnerComponent) %>.
25
+ #
26
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
27
+ renders_one :visual, types: {
28
+ icon: lambda { |**system_arguments|
29
+ system_arguments[:mb] = 3
30
+ system_arguments[:size] ||= :medium
31
+ system_arguments[:classes] = class_names("blankslate-icon", system_arguments[:classes])
32
+
33
+ Primer::OcticonComponent.new(**system_arguments)
34
+ },
35
+ spinner: lambda { |**system_arguments|
36
+ system_arguments[:mb] = 3
37
+
38
+ Primer::SpinnerComponent.new(**system_arguments)
39
+ },
40
+ image: lambda { |**system_arguments|
41
+ system_arguments[:mb] = 3
42
+ system_arguments[:size] = "56x56"
43
+
44
+ Primer::Image.new(**system_arguments)
45
+ }
46
+ }
47
+
48
+ # Required heading.
49
+ #
50
+ # @param tag [String] <%= one_of(Primer::HeadingComponent::TAG_OPTIONS) %>
51
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
52
+ renders_one :heading, lambda { |tag:, **system_arguments|
53
+ system_arguments[:tag] = tag
54
+ system_arguments[:mb] = 1
55
+ system_arguments[:classes] = class_names("h2", system_arguments[:classes])
56
+
57
+ Primer::HeadingComponent.new(**system_arguments)
58
+ }
59
+
60
+ # Optional description.
61
+ #
62
+ # - The description should always be informative and actionable.
63
+ # - Don't use phrases like "You can".
64
+ #
65
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
66
+ renders_one :description, lambda { |**system_arguments|
67
+ system_arguments[:tag] = :p
68
+
69
+ Primer::BaseComponent.new(**system_arguments)
70
+ }
71
+
72
+ # Optional primary action
73
+ #
74
+ # The `primary_action` slot renders an anchor link which is visually styled as a button to provide more emphasis to the
75
+ # Blankslate's primary action.
76
+ #
77
+ # @param href [String] URL to be used for the primary action.
78
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
79
+ renders_one :primary_action, lambda { |href:, **system_arguments|
80
+ system_arguments[:tag] = :a
81
+ system_arguments[:href] = href
82
+ system_arguments[:my] = 3
83
+ system_arguments[:variant] = :medium
84
+ system_arguments[:scheme] ||= :primary
85
+
86
+ Primer::ButtonComponent.new(**system_arguments)
87
+ }
88
+
89
+ # Optional secondary action
90
+ #
91
+ # The `secondary_action` slot renders a normal anchor link, which can be used to redirect the user to additional information
92
+ # (e.g. Help documentation).
93
+ #
94
+ # @param href [String] URL to be used for the secondary action.
95
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
96
+ renders_one :secondary_action, lambda { |href:, **system_arguments|
97
+ system_arguments[:href] = href
98
+ # Padding is needed to increase touch area.
99
+ system_arguments[:p] = 3
100
+
101
+ Primer::LinkComponent.new(**system_arguments)
102
+ }
103
+
104
+ # @example Basic
105
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
106
+ # <% c.heading(tag: :h2).with_content("Title") %>
107
+ # <% c.description { "Description"} %>
108
+ # <% end %>
109
+ #
110
+ # @example Icon
111
+ # @description
112
+ # Add an `icon` to give additional context. Refer to the [Octicons](https://primer.style/octicons/) documentation to choose an icon.
113
+ # @code
114
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
115
+ # <% c.visual_icon(icon: :globe) %>
116
+ # <% c.heading(tag: :h2).with_content("Title") %>
117
+ # <% c.description { "Description"} %>
118
+ # <% end %>
119
+ #
120
+ # @example Loading
121
+ # @description
122
+ # Add a [SpinnerComponent](https://primer.style/view-components/components/spinner) to the blankslate in place of an icon.
123
+ # @code
124
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
125
+ # <% c.visual_spinner(size: :large) %>
126
+ # <% c.heading(tag: :h2).with_content("Title") %>
127
+ # <% c.description { "Description"} %>
128
+ # <% end %>
129
+ #
130
+ # @example Using an image
131
+ # @description
132
+ # Add an `image` to give context that an Octicon couldn't.
133
+ # @code
134
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
135
+ # <% c.visual_image(src: "https://github.githubassets.com/images/modules/site/features/security-icon.svg", alt: "Security - secure vault") %>
136
+ # <% c.heading(tag: :h2).with_content("Title") %>
137
+ # <% c.description { "Description"} %>
138
+ # <% end %>
139
+ #
140
+ # @example Custom content
141
+ # @description
142
+ # Pass custom content to `description`.
143
+ # @code
144
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
145
+ # <% c.heading(tag: :h2).with_content("Title") %>
146
+ # <% c.description do %>
147
+ # <em>Your custom content here</em>
148
+ # <% end %>
149
+ # <% end %>
150
+ #
151
+ # @example Primary action
152
+ # @description
153
+ # Provide a `primary_action` to guide users to take action from the blankslate. The `primary_action` appears below the description and custom content.
154
+ # @code
155
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
156
+ # <% c.visual_icon(icon: :book) %>
157
+ # <% c.heading(tag: :h2).with_content("Welcome to the mona wiki!") %>
158
+ # <% c.description { "Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and document software better, together."} %>
159
+ # <% c.primary_action(href: "https://github.com/monalisa/mona/wiki/_new").with_content("Create the first page") %>
160
+ # <% end %>
161
+ #
162
+ # @example Secondary action
163
+ # @description
164
+ # Add an additional `secondary_action` to help users learn more about a feature. See <%= link_to_accessibility %>. `secondary_action` will be shown at the very bottom:
165
+ # @code
166
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
167
+ # <% c.visual_icon(icon: :book) %>
168
+ # <% c.heading(tag: :h2).with_content("Welcome to the mona wiki!") %>
169
+ # <% c.description { "Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and document software better, together."} %>
170
+ # <% c.secondary_action(href: "https://docs.github.com/en/github/building-a-strong-community/about-wikis").with_content("Learn more about wikis") %>
171
+ # <% end %>
172
+ #
173
+ # @example Primary and secondary actions
174
+ # @description
175
+ # `primary_action` and `secondary_action` can also be used together. The `primary_action` will always be rendered before the `secondary_action`:
176
+ # @code
177
+ # <%= render Primer::Beta::Blankslate.new do |c| %>
178
+ # <% c.visual_icon(icon: :book) %>
179
+ # <% c.heading(tag: :h2).with_content("Welcome to the mona wiki!") %>
180
+ # <% c.description { "Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and document software better, together."} %>
181
+ # <% c.primary_action(href: "https://github.com/monalisa/mona/wiki/_new").with_content("Create the first page") %>
182
+ # <% c.secondary_action(href: "https://docs.github.com/en/github/building-a-strong-community/about-wikis").with_content("Learn more about wikis") %>
183
+ # <% end %>
184
+ #
185
+ # @example Variations
186
+ # @description
187
+ # There are a few variations of how the Blankslate appears: `narrow` adds a maximum width of `485px`, and `spacious` increases the padding from `32px` to `80px 40px`.
188
+ # @code
189
+ # <%= render Primer::Beta::Blankslate.new(
190
+ # narrow: true,
191
+ # spacious: true,
192
+ # ) do |c| %>
193
+ # <% c.visual_icon(icon: :book) %>
194
+ # <% c.heading(tag: :h2).with_content("Welcome to the mona wiki!") %>
195
+ # <% c.description { "Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and document software better, together."} %>
196
+ # <% end %>
197
+ #
198
+ # @example With border
199
+ # @description
200
+ # It's possible to add a border around the Blankslate. This will wrap the Blankslate in a BorderBox.
201
+ # @code
202
+ # <%= render Primer::Beta::Blankslate.new(border: true) do |c| %>
203
+ # <% c.visual_icon(icon: :book) %>
204
+ # <% c.heading(tag: :h2).with_content("Welcome to the mona wiki!") %>
205
+ # <% c.description { "Wikis provide a place in your repository to lay out the roadmap of your project, show the current status, and document software better, together."} %>
206
+ # <% end %>
207
+ #
208
+ # @param narrow [Boolean] Adds a maximum width of `485px` to the Blankslate.
209
+ # @param spacious [Boolean] Increases the padding from `32px` to `80px 40px`.
210
+ # @param border [Boolean] Adds a border around the Blankslate.
211
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
212
+ def initialize(narrow: false, spacious: false, border: false, **system_arguments)
213
+ @border = border
214
+ @system_arguments = system_arguments
215
+ @system_arguments[:tag] = :div
216
+ @system_arguments[:classes] = class_names(
217
+ @system_arguments[:classes],
218
+ "blankslate",
219
+ "blankslate-narrow": narrow,
220
+ "blankslate-spacious": spacious
221
+ )
222
+ end
223
+
224
+ def render?
225
+ heading.present?
226
+ end
227
+
228
+ def wrapper
229
+ unless @border
230
+ yield
231
+ return # returning `yield` caused a double render
232
+ end
233
+
234
+ content_tag(:div, class: "Box") do
235
+ yield if block_given?
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end