primer_view_components 0.29.0 → 0.31.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/app/assets/javascripts/app/components/primer/alpha/select_panel_element.d.ts +0 -1
  4. data/app/assets/javascripts/app/components/primer/alpha/toggle_switch.d.ts +4 -0
  5. data/app/assets/javascripts/primer_view_components.js +1 -1
  6. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  7. data/app/assets/styles/primer_view_components.css +1 -1
  8. data/app/assets/styles/primer_view_components.css.map +1 -1
  9. data/app/components/primer/alpha/action_list/item.html.erb +1 -5
  10. data/app/components/primer/alpha/action_list/item.rb +10 -3
  11. data/app/components/primer/alpha/action_list.css +1 -1
  12. data/app/components/primer/alpha/action_list.css.map +1 -1
  13. data/app/components/primer/alpha/action_list.pcss +1 -1
  14. data/app/components/primer/alpha/segmented_control.css +1 -1
  15. data/app/components/primer/alpha/segmented_control.css.map +1 -1
  16. data/app/components/primer/alpha/segmented_control.pcss +1 -0
  17. data/app/components/primer/alpha/select_panel.rb +2 -1
  18. data/app/components/primer/alpha/select_panel_element.d.ts +0 -1
  19. data/app/components/primer/alpha/select_panel_element.js +72 -18
  20. data/app/components/primer/alpha/select_panel_element.ts +77 -22
  21. data/app/components/primer/alpha/toggle_switch.d.ts +4 -0
  22. data/app/components/primer/alpha/toggle_switch.js +16 -4
  23. data/app/components/primer/alpha/toggle_switch.rb +4 -2
  24. data/app/components/primer/alpha/toggle_switch.ts +19 -4
  25. data/app/components/primer/alpha/tool_tip.js +2 -2
  26. data/app/components/primer/alpha/tool_tip.ts +2 -2
  27. data/app/components/primer/beta/relative_time.rb +4 -4
  28. data/app/components/primer/beta/state.css +1 -1
  29. data/app/components/primer/beta/state.css.map +1 -1
  30. data/app/components/primer/beta/state.pcss +4 -0
  31. data/lib/primer/view_components/linters/details_menu_migration.rb +30 -1
  32. data/lib/primer/view_components/linters/tooltipped_migration.rb +1 -1
  33. data/lib/primer/view_components/version.rb +1 -1
  34. data/previews/primer/alpha/action_list_preview.rb +1 -1
  35. data/previews/primer/alpha/select_panel_preview/list_of_links.html.erb +16 -0
  36. data/previews/primer/alpha/select_panel_preview/playground.html.erb +2 -1
  37. data/previews/primer/alpha/select_panel_preview.rb +12 -1
  38. data/previews/primer/alpha/toggle_switch_preview.rb +4 -0
  39. data/static/arguments.json +11 -5
  40. data/static/info_arch.json +77 -11
  41. data/static/previews.json +27 -0
  42. metadata +3 -2
@@ -11,7 +11,6 @@ type SelectedItem = {
11
11
  label: string | null | undefined
12
12
  value: string | null | undefined
13
13
  inputName: string | null | undefined
14
- element: SelectPanelItem
15
14
  }
16
15
 
17
16
  const validSelectors = ['[role="option"]']
@@ -54,6 +53,7 @@ const updateWhenVisible = (() => {
54
53
  })
55
54
  resizeObserver.observe(el.ownerDocument.documentElement)
56
55
  el.addEventListener('dialog:close', () => {
56
+ el.invokerElement?.setAttribute('aria-expanded', 'false')
57
57
  anchors.delete(el)
58
58
  })
59
59
  el.addEventListener('dialog:open', () => {
@@ -84,6 +84,7 @@ export class SelectPanelElement extends HTMLElement {
84
84
  #selectedItems: Map<string, SelectedItem> = new Map()
85
85
  #loadingDelayTimeoutId: number | null = null
86
86
  #loadingAnnouncementTimeoutId: number | null = null
87
+ #hasLoadedData = false
87
88
 
88
89
  get open(): boolean {
89
90
  return this.dialog.open
@@ -215,6 +216,17 @@ export class SelectPanelElement extends HTMLElement {
215
216
  for (const entry of entries) {
216
217
  const elem = entry.target
217
218
  if (entry.isIntersecting && elem === this.dialog) {
219
+ // Focus on the filter input when the dialog opens to work around a Safari limitation
220
+ // that prevents the autofocus attribute from working as it does in other browsers
221
+ if (this.filterInputTextField) {
222
+ if (document.activeElement !== this.filterInputTextField) {
223
+ this.filterInputTextField.focus()
224
+ }
225
+ }
226
+
227
+ // signal that any focus hijinks are finished (thanks Safari)
228
+ this.dialog.setAttribute('data-ready', 'true')
229
+
218
230
  this.updateAnchorPosition()
219
231
 
220
232
  if (this.#fetchStrategy === FetchStrategy.LOCAL) {
@@ -227,12 +239,12 @@ export class SelectPanelElement extends HTMLElement {
227
239
  this.#waitForCondition(
228
240
  () => Boolean(this.dialog),
229
241
  () => {
242
+ this.#dialogIntersectionObserver.observe(this.dialog)
243
+ this.dialog.addEventListener('close', this, {signal})
244
+
230
245
  if (this.getAttribute('data-open-on-load') === 'true') {
231
246
  this.show()
232
247
  }
233
-
234
- this.#dialogIntersectionObserver.observe(this.dialog)
235
- this.dialog.addEventListener('close', this, {signal})
236
248
  },
237
249
  )
238
250
 
@@ -295,7 +307,7 @@ export class SelectPanelElement extends HTMLElement {
295
307
  }
296
308
 
297
309
  // <li> elements should not themselves be tabbable
298
- item.setAttribute('tabindex', '-1')
310
+ item.removeAttribute('tabindex')
299
311
  }
300
312
  } else {
301
313
  for (const item of this.items) {
@@ -309,7 +321,7 @@ export class SelectPanelElement extends HTMLElement {
309
321
  }
310
322
 
311
323
  // <li> elements should not themselves be tabbable
312
- item.setAttribute('tabindex', '-1')
324
+ item.removeAttribute('tabindex')
313
325
  }
314
326
  }
315
327
 
@@ -369,6 +381,7 @@ export class SelectPanelElement extends HTMLElement {
369
381
  }
370
382
  }
371
383
  }
384
+
372
385
  this.#updateInput()
373
386
  }
374
387
 
@@ -383,14 +396,15 @@ export class SelectPanelElement extends HTMLElement {
383
396
  value,
384
397
  label: itemContent.querySelector('.ActionListItem-label')?.textContent?.trim(),
385
398
  inputName: itemContent.getAttribute('data-input-name'),
386
- element: item,
387
399
  })
388
400
  }
389
401
  }
390
402
 
391
- #removeSelectedItem(item: Element) {
392
- const value = item.getAttribute('data-value')
403
+ #removeSelectedItem(item: SelectPanelItem) {
404
+ const itemContent = this.#getItemContent(item)
405
+ if (!itemContent) return
393
406
 
407
+ const value = itemContent.getAttribute('data-value')
394
408
  if (value) {
395
409
  this.#selectedItems.delete(value)
396
410
  }
@@ -450,6 +464,10 @@ export class SelectPanelElement extends HTMLElement {
450
464
  }
451
465
 
452
466
  if (event.target === this.dialog && event.type === 'close') {
467
+ // Remove data-ready so it can be set the next time the panel is opened
468
+ this.dialog.removeAttribute('data-ready')
469
+ this.invokerElement?.setAttribute('aria-expanded', 'false')
470
+
453
471
  this.dispatchEvent(
454
472
  new CustomEvent('panelClosed', {
455
473
  detail: {panel: this},
@@ -606,12 +624,41 @@ export class SelectPanelElement extends HTMLElement {
606
624
  }
607
625
 
608
626
  #handleSearchFieldEvent(event: Event) {
609
- if (event.type === 'keydown' && (event as KeyboardEvent).key === 'ArrowDown') {
610
- if (this.focusableItem) {
611
- this.focusableItem.focus()
612
- event.preventDefault()
627
+ if (event.type === 'keydown') {
628
+ const key = (event as KeyboardEvent).key
629
+
630
+ if (key === 'Enter') {
631
+ const item = this.visibleItems[0] as HTMLLIElement | null
632
+
633
+ if (item) {
634
+ const itemContent = this.#getItemContent(item)
635
+ if (itemContent) itemContent.click()
636
+ }
637
+ } else if (key === 'ArrowDown') {
638
+ const item = (this.focusableItem || this.#getItemContent(this.visibleItems[0])) as HTMLLIElement
639
+
640
+ if (item) {
641
+ item.focus()
642
+ event.preventDefault()
643
+ }
644
+ } else if (key === 'Home') {
645
+ const item = this.visibleItems[0] as HTMLLIElement | null
646
+
647
+ if (item) {
648
+ const itemContent = this.#getItemContent(item)
649
+ if (itemContent) itemContent.focus()
650
+ event.preventDefault()
651
+ }
652
+ } else if (key === 'End') {
653
+ if (this.visibleItems.length > 0) {
654
+ const item = this.visibleItems[this.visibleItems.length - 1] as HTMLLIElement
655
+ const itemContent = this.#getItemContent(item)
656
+ if (itemContent) itemContent.focus()
657
+ event.preventDefault()
658
+ }
613
659
  }
614
660
  }
661
+
615
662
  if (event.type !== 'input') return
616
663
 
617
664
  // remote-input-element does not trigger another loadstart event if a request is
@@ -660,8 +707,12 @@ export class SelectPanelElement extends HTMLElement {
660
707
  if (!itemContent) continue
661
708
 
662
709
  const value = itemContent.getAttribute('data-value')
663
-
664
- if (value && !this.#selectedItems.has(value) && this.isItemChecked(item)) {
710
+ if (this.#hasLoadedData) {
711
+ if (value && !this.#selectedItems.has(value)) {
712
+ itemContent.setAttribute(this.ariaSelectionType, 'false')
713
+ }
714
+ } else if (value && !this.#selectedItems.has(value) && this.isItemChecked(item)) {
715
+ this.#hasLoadedData = true
665
716
  this.#addSelectedItem(item)
666
717
  }
667
718
  }
@@ -823,19 +874,21 @@ export class SelectPanelElement extends HTMLElement {
823
874
  const itemContent = this.#getItemContent(item)
824
875
 
825
876
  if (this.selectVariant === 'single') {
877
+ const value = this.selectedItems[0]?.value
878
+ const element = this.visibleItems.find(el => this.#getItemContent(el)?.getAttribute('data-value') === value)
879
+
880
+ if (element) {
881
+ this.#getItemContent(element)?.setAttribute(this.ariaSelectionType, 'false')
882
+ }
883
+
884
+ this.#selectedItems.clear()
885
+
826
886
  // Only check, never uncheck here. Single-select mode does not allow unchecking a checked item.
827
887
  if (checked) {
828
888
  this.#addSelectedItem(item)
829
889
  itemContent?.setAttribute(this.ariaSelectionType, 'true')
830
890
  }
831
891
 
832
- for (const checkedItem of this.querySelectorAll(`[${this.ariaSelectionType}]`)) {
833
- if (checkedItem !== itemContent) {
834
- this.#removeSelectedItem(checkedItem)
835
- checkedItem.setAttribute(this.ariaSelectionType, 'false')
836
- }
837
- }
838
-
839
892
  this.#setDynamicLabel()
840
893
  } else {
841
894
  // multi-select mode allows unchecking a checked item
@@ -862,6 +915,7 @@ export class SelectPanelElement extends HTMLElement {
862
915
  show() {
863
916
  this.updateAnchorPosition()
864
917
  this.dialog.showModal()
918
+ this.invokerElement?.setAttribute('aria-expanded', 'true')
865
919
  const event = new CustomEvent('dialog:open', {
866
920
  detail: {dialog: this.dialog},
867
921
  })
@@ -952,6 +1006,7 @@ export class SelectPanelElement extends HTMLElement {
952
1006
  element => element.parentElement! as SelectPanelItem,
953
1007
  )
954
1008
  }
1009
+
955
1010
  get focusableItem(): HTMLElement | undefined {
956
1011
  for (const item of this.items) {
957
1012
  const itemContent = this.#getItemContent(item)
@@ -2,6 +2,7 @@ declare class ToggleSwitchElement extends HTMLElement {
2
2
  switch: HTMLElement;
3
3
  loadingSpinner: HTMLElement;
4
4
  errorIcon: HTMLElement;
5
+ turbo: boolean;
5
6
  private toggling;
6
7
  get src(): string | null;
7
8
  get csrf(): string | null;
@@ -25,6 +26,9 @@ declare class ToggleSwitchElement extends HTMLElement {
25
26
  declare global {
26
27
  interface Window {
27
28
  ToggleSwitchElement: typeof ToggleSwitchElement;
29
+ Turbo: {
30
+ renderStreamMessage: (message: string) => void;
31
+ };
28
32
  }
29
33
  }
30
34
  export {};
@@ -4,10 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { controller, target } from '@github/catalyst';
7
+ import { controller, target, attr } from '@github/catalyst';
8
8
  let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
9
9
  constructor() {
10
10
  super(...arguments);
11
+ this.turbo = false;
11
12
  this.toggling = false;
12
13
  }
13
14
  get src() {
@@ -129,13 +130,17 @@ let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
129
130
  if (!this.src)
130
131
  throw new Error('invalid src');
131
132
  let response;
133
+ const requestHeaders = {
134
+ 'Requested-With': 'XMLHttpRequest',
135
+ };
136
+ if (this.turbo) {
137
+ requestHeaders['Accept'] = 'text/vnd.turbo-stream.html';
138
+ }
132
139
  try {
133
140
  response = await fetch(this.src, {
134
141
  credentials: 'same-origin',
135
142
  method: 'POST',
136
- headers: {
137
- 'Requested-With': 'XMLHttpRequest',
138
- },
143
+ headers: requestHeaders,
139
144
  body,
140
145
  });
141
146
  }
@@ -145,6 +150,10 @@ let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
145
150
  if (!response.ok) {
146
151
  throw new Error(await response.text());
147
152
  }
153
+ const contentType = response.headers.get('Content-Type');
154
+ if (window.Turbo && this.turbo && contentType?.startsWith('text/vnd.turbo-stream.html')) {
155
+ window.Turbo.renderStreamMessage(await response.text());
156
+ }
148
157
  }
149
158
  };
150
159
  __decorate([
@@ -156,6 +165,9 @@ __decorate([
156
165
  __decorate([
157
166
  target
158
167
  ], ToggleSwitchElement.prototype, "errorIcon", void 0);
168
+ __decorate([
169
+ attr
170
+ ], ToggleSwitchElement.prototype, "turbo", void 0);
159
171
  ToggleSwitchElement = __decorate([
160
172
  controller
161
173
  ], ToggleSwitchElement);
@@ -26,12 +26,14 @@ module Primer
26
26
  # @param enabled [Boolean] Whether or not the toggle switch responds to user input.
27
27
  # @param size [Symbol] What size toggle switch to render. <%= one_of(Primer::Alpha::ToggleSwitch::SIZE_OPTIONS) %>
28
28
  # @param status_label_position [Symbol] Which side of the toggle switch to render the status label. <%= one_of(Primer::Alpha::ToggleSwitch::STATUS_LABEL_POSITION_OPTIONS) %>
29
+ # @param turbo [Boolean] Whether or not to request a turbo stream and render the response as such.
29
30
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
30
- def initialize(src: nil, csrf_token: nil, checked: false, enabled: true, size: SIZE_DEFAULT, status_label_position: STATUS_LABEL_POSITION_DEFAULT, **system_arguments)
31
+ def initialize(src: nil, csrf_token: nil, checked: false, enabled: true, size: SIZE_DEFAULT, status_label_position: STATUS_LABEL_POSITION_DEFAULT, turbo: false, **system_arguments)
31
32
  @src = src
32
33
  @csrf_token = csrf_token
33
34
  @checked = checked
34
35
  @enabled = enabled
36
+ @turbo = turbo
35
37
  @system_arguments = system_arguments
36
38
 
37
39
  @size = fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)
@@ -82,7 +84,7 @@ module Primer
82
84
 
83
85
  @system_arguments[:data] = merge_data(
84
86
  @system_arguments,
85
- { data: { csrf: @csrf_token } }
87
+ { data: { csrf: @csrf_token, turbo: @turbo } }
86
88
  )
87
89
  end
88
90
  end
@@ -1,10 +1,11 @@
1
- import {controller, target} from '@github/catalyst'
1
+ import {controller, target, attr} from '@github/catalyst'
2
2
 
3
3
  @controller
4
4
  class ToggleSwitchElement extends HTMLElement {
5
5
  @target switch: HTMLElement
6
6
  @target loadingSpinner: HTMLElement
7
7
  @target errorIcon: HTMLElement
8
+ @attr turbo = false
8
9
 
9
10
  private toggling = false
10
11
 
@@ -158,13 +159,19 @@ class ToggleSwitchElement extends HTMLElement {
158
159
 
159
160
  let response
160
161
 
162
+ const requestHeaders: {[key: string]: string} = {
163
+ 'Requested-With': 'XMLHttpRequest',
164
+ }
165
+
166
+ if (this.turbo) {
167
+ requestHeaders['Accept'] = 'text/vnd.turbo-stream.html'
168
+ }
169
+
161
170
  try {
162
171
  response = await fetch(this.src, {
163
172
  credentials: 'same-origin',
164
173
  method: 'POST',
165
- headers: {
166
- 'Requested-With': 'XMLHttpRequest',
167
- },
174
+ headers: requestHeaders,
168
175
  body,
169
176
  })
170
177
  } catch (error) {
@@ -174,12 +181,20 @@ class ToggleSwitchElement extends HTMLElement {
174
181
  if (!response.ok) {
175
182
  throw new Error(await response.text())
176
183
  }
184
+
185
+ const contentType = response.headers.get('Content-Type')
186
+ if (window.Turbo && this.turbo && contentType?.startsWith('text/vnd.turbo-stream.html')) {
187
+ window.Turbo.renderStreamMessage(await response.text())
188
+ }
177
189
  }
178
190
  }
179
191
 
180
192
  declare global {
181
193
  interface Window {
182
194
  ToggleSwitchElement: typeof ToggleSwitchElement
195
+ Turbo: {
196
+ renderStreamMessage: (message: string) => void
197
+ }
183
198
  }
184
199
  }
185
200
 
@@ -86,7 +86,7 @@ class ToolTipElement extends HTMLElement {
86
86
  --tooltip-left: var(--tool-tip-position-left, 0);
87
87
  padding: var(--overlay-paddingBlock-condensed) var(--overlay-padding-condensed) !important;
88
88
  font: var(--text-body-shorthand-small);
89
- color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)) !important;
89
+ color: var(--tooltip-fgColor, var(--fgColor-onEmphasis)) !important;
90
90
  text-align: center;
91
91
  text-decoration: none;
92
92
  text-shadow: none;
@@ -94,7 +94,7 @@ class ToolTipElement extends HTMLElement {
94
94
  letter-spacing: normal;
95
95
  word-wrap: break-word;
96
96
  white-space: pre;
97
- background: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus)) !important;
97
+ background: var(--tooltip-bgColor, var(--bgColor-emphasis)) !important;
98
98
  border-radius: var(--borderRadius-medium);
99
99
  border: 0 !important;
100
100
  opacity: 0;
@@ -71,7 +71,7 @@ class ToolTipElement extends HTMLElement {
71
71
  --tooltip-left: var(--tool-tip-position-left, 0);
72
72
  padding: var(--overlay-paddingBlock-condensed) var(--overlay-padding-condensed) !important;
73
73
  font: var(--text-body-shorthand-small);
74
- color: var(--fgColor-onEmphasis, var(--color-fg-on-emphasis)) !important;
74
+ color: var(--tooltip-fgColor, var(--fgColor-onEmphasis)) !important;
75
75
  text-align: center;
76
76
  text-decoration: none;
77
77
  text-shadow: none;
@@ -79,7 +79,7 @@ class ToolTipElement extends HTMLElement {
79
79
  letter-spacing: normal;
80
80
  word-wrap: break-word;
81
81
  white-space: pre;
82
- background: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus)) !important;
82
+ background: var(--tooltip-bgColor, var(--bgColor-emphasis)) !important;
83
83
  border-radius: var(--borderRadius-medium);
84
84
  border: 0 !important;
85
85
  opacity: 0;
@@ -86,7 +86,7 @@ module Primer
86
86
 
87
87
  # @param datetime [Time] The time to be formatted.
88
88
  # @param tense [Symbol] Which tense to use. <%= one_of(Primer::Beta::RelativeTime::TENSE_OPTIONS) %>
89
- # @param prefix [sring] What to prefix the relative ime display with.
89
+ # @param prefix [String] What to prefix the relative time display with.
90
90
  # @param second [Symbol] What format seconds should take. <%= one_of(Primer::Beta::RelativeTime::SECOND_OPTIONS) %>
91
91
  # @param minute [Symbol] What format minues should take. <%= one_of(Primer::Beta::RelativeTime::MINUTE_OPTIONS) %>
92
92
  # @param hour [Symbol] What format hours should take. <%= one_of(Primer::Beta::RelativeTime::HOUR_OPTIONS) %>
@@ -95,12 +95,12 @@ module Primer
95
95
  # @param month [Symbol] What format months should take. <%= one_of(Primer::Beta::RelativeTime::MONTH_OPTIONS) %>
96
96
  # @param year [Symbol] What format years should take. <%= one_of(Primer::Beta::RelativeTime::YEAR_OPTIONS) %>
97
97
  # @param time_zone_name [Symbol] What format the time zone should take. <%= one_of(Primer::Beta::RelativeTime::TIMEZONENAME_OPTIONS) %>
98
- # @param threshold [string] The threshold, in ISO-8601 'durations' format, at which relative time displays become absolute.
98
+ # @param threshold [String] The threshold, in ISO-8601 'durations' format, at which relative time displays become absolute.
99
99
  # @param precision [Symbol] The precision elapsed time should display. <%= one_of(Primer::Beta::RelativeTime::PRECISION_OPTIONS) %>
100
100
  # @param format [Symbol] The format the display should take. <%= one_of(Primer::Beta::RelativeTime::FORMAT_OPTIONS) %>
101
101
  # @param format_style [Symbol] The format the display should take. <%= one_of(Primer::Beta::RelativeTime::FORMAT_STYLE_OPTIONS) %>
102
- # @param lang [string] The language to use.
103
- # @param title [string] Provide a custom title to the element.
102
+ # @param lang [String] The language to use.
103
+ # @param title [String] Provide a custom title to the element.
104
104
  # @param no_title [Boolean] Removes the `title` attribute provided on the element by default.
105
105
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
106
106
  def initialize(
@@ -1 +1 @@
1
- .State,.state{border-radius:2em;display:inline-block;font-size:var(--text-body-size-medium);font-weight:var(--base-text-weight-medium);line-height:var(--control-medium-lineBoxHeight);padding:5px var(--control-medium-paddingInline-normal);text-align:center;white-space:nowrap}.State,.State--draft,.state{background-color:var(--bgColor-neutral-emphasis);border:var(--borderWidth-thin) solid #0000;color:var(--fgColor-onEmphasis)}.State--open{background-color:var(--bgColor-open-emphasis,var(--color-open-emphasis))}.State--merged,.State--open{color:var(--fgColor-onEmphasis)}.State--merged{background-color:var(--bgColor-done-emphasis,var(--color-done-emphasis))}.State--closed{background-color:var(--bgColor-closed-emphasis,var(--color-closed-emphasis));color:var(--fgColor-onEmphasis)}.State--small{font-size:var(--text-body-size-small);line-height:var(--base-size-24);padding:0 10px}.State--small .octicon{width:1em}
1
+ .State,.state{border-radius:2em;display:inline-block;font-size:var(--text-body-size-medium);font-weight:var(--base-text-weight-medium);line-height:var(--control-medium-lineBoxHeight);padding:5px var(--control-medium-paddingInline-normal);text-align:center;white-space:nowrap}.State,.State--draft,.state{background-color:var(--bgColor-neutral-emphasis);border:var(--borderWidth-thin) solid #0000;box-shadow:var(--boxShadow-thin) var(--borderColor-neutral-emphasis);color:var(--fgColor-onEmphasis)}.State--open{background-color:var(--bgColor-open-emphasis,var(--color-open-emphasis));box-shadow:var(--boxShadow-thin) var(--borderColor-open-emphasis)}.State--merged,.State--open{color:var(--fgColor-onEmphasis)}.State--merged{background-color:var(--bgColor-done-emphasis,var(--color-done-emphasis));box-shadow:var(--boxShadow-thin) var(--borderColor-done-emphasis)}.State--closed{background-color:var(--bgColor-closed-emphasis,var(--color-closed-emphasis));box-shadow:var(--boxShadow-thin) var(--borderColor-closed-emphasis);color:var(--fgColor-onEmphasis)}.State--small{font-size:var(--text-body-size-small);line-height:var(--base-size-24);padding:0 10px}.State--small .octicon{width:1em}
@@ -1 +1 @@
1
- {"version":3,"sources":["state.pcss"],"names":[],"mappings":"AAIA,cASE,iBAAkB,CAPlB,oBAAqB,CAErB,sCAAuC,CACvC,0CAA2C,CAC3C,+CAAgD,CAHhD,sDAAuD,CAIvD,iBAAkB,CAClB,kBAEF,CAEA,4BAIE,gDAAiD,CACjD,0CAAiD,CAFjD,+BAGF,CAEA,aAEE,wEACF,CAEA,4BAJE,+BAOF,CAHA,eAEE,wEACF,CAEA,eAEE,4EAA8E,CAD9E,+BAEF,CAIA,cAEE,qCAAsC,CACtC,+BAAgC,CAFhC,cAOF,CAHE,uBACE,SACF","file":"state.css","sourcesContent":["/* State */\n\n/* Default 32px */\n\n.state, /* TODO: Deprecate */\n.State {\n display: inline-block;\n padding: 5px var(--control-medium-paddingInline-normal);\n font-size: var(--text-body-size-medium);\n font-weight: var(--base-text-weight-medium);\n line-height: var(--control-medium-lineBoxHeight);\n text-align: center;\n white-space: nowrap;\n border-radius: 2em;\n}\n\n.state, /* TODO: Deprecate */\n.State,\n.State--draft {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-neutral-emphasis);\n border: var(--borderWidth-thin) solid transparent;\n}\n\n.State--open {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-open-emphasis, var(--color-open-emphasis));\n}\n\n.State--merged {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-done-emphasis, var(--color-done-emphasis));\n}\n\n.State--closed {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-closed-emphasis, var(--color-closed-emphasis));\n}\n\n/* Small 24px */\n\n.State--small {\n padding: 0 10px;\n font-size: var(--text-body-size-small);\n line-height: var(--base-size-24);\n\n & .octicon {\n width: 1em; /* Ensures different icons don't change State indicator width */\n }\n}\n"]}
1
+ {"version":3,"sources":["state.pcss"],"names":[],"mappings":"AAIA,cASE,iBAAkB,CAPlB,oBAAqB,CAErB,sCAAuC,CACvC,0CAA2C,CAC3C,+CAAgD,CAHhD,sDAAuD,CAIvD,iBAAkB,CAClB,kBAEF,CAEA,4BAIE,gDAAiD,CACjD,0CAAiD,CACjD,oEAAqE,CAHrE,+BAIF,CAEA,aAEE,wEAA0E,CAC1E,iEACF,CAEA,4BALE,+BASF,CAJA,eAEE,wEAA0E,CAC1E,iEACF,CAEA,eAEE,4EAA8E,CAC9E,mEAAoE,CAFpE,+BAGF,CAIA,cAEE,qCAAsC,CACtC,+BAAgC,CAFhC,cAOF,CAHE,uBACE,SACF","file":"state.css","sourcesContent":["/* State */\n\n/* Default 32px */\n\n.state, /* TODO: Deprecate */\n.State {\n display: inline-block;\n padding: 5px var(--control-medium-paddingInline-normal);\n font-size: var(--text-body-size-medium);\n font-weight: var(--base-text-weight-medium);\n line-height: var(--control-medium-lineBoxHeight);\n text-align: center;\n white-space: nowrap;\n border-radius: 2em;\n}\n\n.state, /* TODO: Deprecate */\n.State,\n.State--draft {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-neutral-emphasis);\n border: var(--borderWidth-thin) solid transparent;\n box-shadow: var(--boxShadow-thin) var(--borderColor-neutral-emphasis);\n}\n\n.State--open {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-open-emphasis, var(--color-open-emphasis));\n box-shadow: var(--boxShadow-thin) var(--borderColor-open-emphasis);\n}\n\n.State--merged {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-done-emphasis, var(--color-done-emphasis));\n box-shadow: var(--boxShadow-thin) var(--borderColor-done-emphasis);\n}\n\n.State--closed {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-closed-emphasis, var(--color-closed-emphasis));\n box-shadow: var(--boxShadow-thin) var(--borderColor-closed-emphasis);\n}\n\n/* Small 24px */\n\n.State--small {\n padding: 0 10px;\n font-size: var(--text-body-size-small);\n line-height: var(--base-size-24);\n\n & .octicon {\n width: 1em; /* Ensures different icons don't change State indicator width */\n }\n}\n"]}
@@ -20,21 +20,25 @@
20
20
  color: var(--fgColor-onEmphasis);
21
21
  background-color: var(--bgColor-neutral-emphasis);
22
22
  border: var(--borderWidth-thin) solid transparent;
23
+ box-shadow: var(--boxShadow-thin) var(--borderColor-neutral-emphasis);
23
24
  }
24
25
 
25
26
  .State--open {
26
27
  color: var(--fgColor-onEmphasis);
27
28
  background-color: var(--bgColor-open-emphasis, var(--color-open-emphasis));
29
+ box-shadow: var(--boxShadow-thin) var(--borderColor-open-emphasis);
28
30
  }
29
31
 
30
32
  .State--merged {
31
33
  color: var(--fgColor-onEmphasis);
32
34
  background-color: var(--bgColor-done-emphasis, var(--color-done-emphasis));
35
+ box-shadow: var(--boxShadow-thin) var(--borderColor-done-emphasis);
33
36
  }
34
37
 
35
38
  .State--closed {
36
39
  color: var(--fgColor-onEmphasis);
37
40
  background-color: var(--bgColor-closed-emphasis, var(--color-closed-emphasis));
41
+ box-shadow: var(--boxShadow-thin) var(--borderColor-closed-emphasis);
38
42
  }
39
43
 
40
44
  /* Small 24px */
@@ -14,6 +14,13 @@ module ERBLint
14
14
  " https://primer.style/design/components/action-menu/rails/alpha"
15
15
  DETAILS_MENU_RUBY_PATTERN = /tag:?\s+:"details-menu"/.freeze
16
16
 
17
+ # Allow custom pattern matching for ERB nodes
18
+ class ConfigSchema < LinterConfig
19
+ property :custom_erb_pattern, accepts: array_of?(Regexp),
20
+ default: -> { [] }
21
+ end
22
+ self.config_schema = ConfigSchema
23
+
17
24
  def run(processed_source)
18
25
  # HTML tags
19
26
  tags(processed_source).each do |tag|
@@ -25,9 +32,31 @@ module ERBLint
25
32
  # ERB nodes
26
33
  erb_nodes(processed_source).each do |node|
27
34
  code = extract_ruby_from_erb_node(node)
28
- generate_node_offense(self.class, processed_source, node, MIGRATE_FROM_DETAILS_MENU) if code.match?(DETAILS_MENU_RUBY_PATTERN)
35
+
36
+ if contains_offense?(code)
37
+ generate_node_offense(self.class, processed_source, node, MIGRATE_FROM_DETAILS_MENU)
38
+ end
29
39
  end
30
40
  end
41
+
42
+ def contains_offense?(code)
43
+ return true if code.match?(DETAILS_MENU_RUBY_PATTERN)
44
+ return code.match?(custom_erb_pattern) if custom_erb_pattern
45
+ false
46
+ end
47
+
48
+ def custom_erb_pattern
49
+ unless defined?(@custom_erb_pattern)
50
+ @custom_erb_pattern =
51
+ if @config.custom_erb_pattern.empty?
52
+ nil
53
+ else
54
+ Regexp.new(@config.custom_erb_pattern.join("|"), true)
55
+ end
56
+ end
57
+
58
+ @custom_erb_pattern
59
+ end
31
60
  end
32
61
  end
33
62
  end
@@ -10,7 +10,7 @@ module ERBLint
10
10
  include LinterRegistry
11
11
  include Helpers::RuleHelpers
12
12
 
13
- MIGRATE_TO_NEWER_TOOLTIP = ".tooltipped has been deprecated. There are major accessibility concerns with using this tooltip so please take action. See https://primer.style/guides/rails/migration-guides/primer-css-tooltipped."
13
+ MIGRATE_TO_NEWER_TOOLTIP = ".tooltipped has been deprecated. Due to major accessibility concerns with using this tooltip, please migrate to a Primer Tooltip component or rework the design to eliminate the tooltip. See https://primer.style/guides/rails/migration-guides/primer-css-tooltipped."
14
14
  TOOLTIPPED_RUBY_PATTERN = /classes:.*tooltipped|class:.*tooltipped/.freeze
15
15
 
16
16
  def run(processed_source)
@@ -5,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 29
8
+ MINOR = 31
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -380,7 +380,7 @@ module Primer
380
380
  aria: { label: "List heading" }
381
381
  )) do |component|
382
382
  component.with_item(label: "Default item", href: "/") do |item|
383
- item.with_description.with_content("This is a description")
383
+ item.with_description(test_selector: "some-selector").with_content("This is a description")
384
384
  end
385
385
  end
386
386
  end
@@ -0,0 +1,16 @@
1
+ <% subject_id = SecureRandom.hex %>
2
+
3
+ <%= render(Primer::Alpha::SelectPanel.new(
4
+ data: { interaction_subject: subject_id },
5
+ select_variant: :single,
6
+ fetch_strategy: :local,
7
+ open_on_load: open_on_load
8
+ )) do |panel| %>
9
+ <% panel.with_show_button { "Panel" } %>
10
+ <% panel.with_item(label: "GitHub", href: "https://github.com") %>
11
+ <% panel.with_item(label: "Microsoft", href: "https://microsoft.com") %>
12
+ <% panel.with_item(label: "Primer", href: "https://primer.style") %>
13
+ <% panel.with_item(label: "Catalyst", href: "https://catalyst.rocks") %>
14
+ <% end %>
15
+
16
+ <%= render partial: "primer/alpha/select_panel_preview/interaction_subject_js", locals: { subject_id: subject_id } %>
@@ -8,7 +8,8 @@
8
8
  src: select_panel_items_path(
9
9
  select_variant: :single,
10
10
  show_results: !simulate_no_results,
11
- fail: simulate_failure
11
+ fail: simulate_failure,
12
+ select_items: select_items
12
13
  ),
13
14
  select_variant: :single,
14
15
  fetch_strategy: :remote,
@@ -18,6 +18,7 @@ module Primer
18
18
  # @param open_on_load toggle
19
19
  # @param anchor_align [Symbol] select [start, center, end]
20
20
  # @param anchor_side [Symbol] select [outside_bottom, outside_top, outside_left, outside_right]
21
+ # @param select_items toggle
21
22
  def playground(
22
23
  title: "Sci-fi equipment",
23
24
  subtitle: "Various tools from your favorite shows",
@@ -31,10 +32,12 @@ module Primer
31
32
  show_filter: true,
32
33
  open_on_load: false,
33
34
  anchor_align: :start,
34
- anchor_side: :outside_bottom
35
+ anchor_side: :outside_bottom,
36
+ select_items: true
35
37
  )
36
38
  render_with_template(locals: {
37
39
  subtitle: subtitle,
40
+ select_items: select_items,
38
41
  system_arguments: {
39
42
  title: title,
40
43
  size: size,
@@ -234,6 +237,14 @@ module Primer
234
237
  def multiselect_form(open_on_load: false, route_format: :html)
235
238
  render_with_template(locals: { open_on_load: open_on_load, route_format: route_format })
236
239
  end
240
+
241
+ # @label List of links
242
+ #
243
+ # @snapshot interactive
244
+ # @param open_on_load toggle
245
+ def list_of_links(open_on_load: false)
246
+ render_with_template(locals: { open_on_load: open_on_load })
247
+ end
237
248
  end
238
249
  end
239
250
  end
@@ -58,6 +58,10 @@ module Primer
58
58
  def with_bad_csrf_token
59
59
  render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.toggle_switch_index_path, csrf_token: "i_am_a_criminal"))
60
60
  end
61
+
62
+ def with_turbo
63
+ render(Primer::Alpha::ToggleSwitch.new(src: UrlHelpers.toggle_switch_index_path, turbo: true))
64
+ end
61
65
  end
62
66
  end
63
67
  end