primer_view_components 0.0.60 → 0.0.61

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