openproject-primer_view_components 0.33.2 → 0.35.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/app/assets/javascripts/lib/primer/forms/primer_text_field.d.ts +27 -0
  4. data/app/assets/javascripts/primer_view_components.js +1 -1
  5. data/app/assets/javascripts/primer_view_components.js.map +1 -1
  6. data/app/assets/styles/primer_view_components.css +1 -1
  7. data/app/assets/styles/primer_view_components.css.map +1 -1
  8. data/app/components/primer/alpha/action_menu/action_menu_element.js +13 -43
  9. data/app/components/primer/alpha/action_menu/action_menu_element.ts +13 -51
  10. data/app/components/primer/alpha/tab_nav.css +1 -1
  11. data/app/components/primer/alpha/tab_nav.css.json +2 -0
  12. data/app/components/primer/alpha/tab_nav.css.map +1 -1
  13. data/app/components/primer/alpha/tab_nav.pcss +11 -1
  14. data/app/components/primer/alpha/tab_panels.html.erb +5 -9
  15. data/app/components/primer/alpha/tab_panels.rb +4 -13
  16. data/app/components/primer/alpha/text_field.rb +1 -0
  17. data/app/components/primer/alpha/underline_nav.css +1 -1
  18. data/app/components/primer/alpha/underline_nav.css.json +2 -0
  19. data/app/components/primer/alpha/underline_nav.css.map +1 -1
  20. data/app/components/primer/alpha/underline_nav.pcss +7 -1
  21. data/app/components/primer/alpha/underline_panels.css +1 -0
  22. data/app/components/primer/alpha/underline_panels.css.json +6 -0
  23. data/app/components/primer/alpha/underline_panels.css.map +1 -0
  24. data/app/components/primer/alpha/underline_panels.html.erb +6 -8
  25. data/app/components/primer/alpha/underline_panels.pcss +4 -0
  26. data/app/components/primer/alpha/underline_panels.rb +6 -14
  27. data/app/components/primer/beta/icon_button.rb +5 -0
  28. data/app/components/primer/beta/relative_time.rb +3 -0
  29. data/app/components/primer/beta/spinner.html.erb +3 -0
  30. data/app/components/primer/beta/spinner.rb +15 -1
  31. data/app/components/primer/open_project/page_header.html.erb +3 -0
  32. data/app/components/primer/open_project/page_header.rb +16 -1
  33. data/app/components/primer/primer.pcss +1 -0
  34. data/lib/primer/forms/dsl/text_field_input.rb +8 -1
  35. data/lib/primer/forms/primer_text_field.d.ts +27 -0
  36. data/lib/primer/forms/primer_text_field.js +17 -5
  37. data/lib/primer/forms/primer_text_field.ts +24 -5
  38. data/lib/primer/forms/text_field.html.erb +6 -2
  39. data/lib/primer/view_components/version.rb +2 -2
  40. data/previews/primer/alpha/text_field_preview.rb +11 -0
  41. data/previews/primer/beta/breadcrumbs_preview.rb +9 -0
  42. data/previews/primer/beta/icon_button_preview/summary_as_button.html.erb +12 -0
  43. data/previews/primer/beta/icon_button_preview.rb +95 -14
  44. data/previews/primer/beta/relative_time_preview/link_with_tooltip.html.erb +13 -0
  45. data/previews/primer/beta/relative_time_preview.rb +12 -0
  46. data/previews/primer/beta/spinner_preview.rb +2 -2
  47. data/previews/primer/open_project/page_header_preview.rb +53 -5
  48. data/static/arguments.json +12 -12
  49. data/static/constants.json +1 -0
  50. data/static/info_arch.json +104 -13
  51. data/static/previews.json +91 -0
  52. metadata +9 -3
@@ -15,7 +15,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
15
15
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
16
16
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
17
17
  };
18
- var _ActionMenuElement_instances, _ActionMenuElement_abortController, _ActionMenuElement_originalLabel, _ActionMenuElement_inputName, _ActionMenuElement_invokerBeingClicked, _ActionMenuElement_softDisableItems, _ActionMenuElement_potentiallyDisallowActivation, _ActionMenuElement_isKeyboardActivation, _ActionMenuElement_isKeyboardActivationViaEnter, _ActionMenuElement_isKeyboardActivationViaSpace, _ActionMenuElement_isMouseActivation, _ActionMenuElement_isActivation, _ActionMenuElement_handleInvokerActivated, _ActionMenuElement_handleDialogItemActivated, _ActionMenuElement_handleItemActivated, _ActionMenuElement_activateItem, _ActionMenuElement_handleIncludeFragmentReplaced, _ActionMenuElement_handleFocusOut, _ActionMenuElement_show, _ActionMenuElement_hide, _ActionMenuElement_isOpen, _ActionMenuElement_setDynamicLabel, _ActionMenuElement_updateInput, _ActionMenuElement_firstItem_get;
18
+ var _ActionMenuElement_instances, _ActionMenuElement_abortController, _ActionMenuElement_originalLabel, _ActionMenuElement_inputName, _ActionMenuElement_invokerBeingClicked, _ActionMenuElement_softDisableItems, _ActionMenuElement_potentiallyDisallowActivation, _ActionMenuElement_isAnchorActivationViaSpace, _ActionMenuElement_isActivation, _ActionMenuElement_handleInvokerActivated, _ActionMenuElement_handleDialogItemActivated, _ActionMenuElement_handleItemActivated, _ActionMenuElement_handleIncludeFragmentReplaced, _ActionMenuElement_handleFocusOut, _ActionMenuElement_show, _ActionMenuElement_hide, _ActionMenuElement_isOpen, _ActionMenuElement_setDynamicLabel, _ActionMenuElement_updateInput, _ActionMenuElement_firstItem_get;
19
19
  import { controller, target } from '@github/catalyst';
20
20
  import '@oddbird/popover-polyfill';
21
21
  const validSelectors = ['[role="menuitem"]', '[role="menuitemcheckbox"]', '[role="menuitemradio"]'];
@@ -152,18 +152,13 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
152
152
  return;
153
153
  }
154
154
  }
155
- __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_activateItem).call(this, event, item);
156
- __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_handleItemActivated).call(this, item);
157
- // Pressing the space key on a button or link will cause the page to scroll unless preventDefault()
158
- // is called. While calling preventDefault() appears to have no effect on link navigation, it skips
159
- // form submission. The code below therefore only calls preventDefault() if the button has been
160
- // activated by the space key, and manually submits the form if the button is a submit button.
161
- if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivationViaSpace).call(this, event)) {
155
+ // Pressing the space key on a link will cause the page to scroll unless preventDefault() is called.
156
+ // We then click it manually to navigate.
157
+ if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isAnchorActivationViaSpace).call(this, event)) {
162
158
  event.preventDefault();
163
- if (item.getAttribute('type') === 'submit') {
164
- item.closest('form')?.submit();
165
- }
159
+ item.click();
166
160
  }
161
+ __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_handleItemActivated).call(this, item);
167
162
  return;
168
163
  }
169
164
  if (event.type === 'include-fragment-replaced') {
@@ -269,26 +264,18 @@ _ActionMenuElement_potentiallyDisallowActivation = function _ActionMenuElement_p
269
264
  }
270
265
  return false;
271
266
  };
272
- _ActionMenuElement_isKeyboardActivation = function _ActionMenuElement_isKeyboardActivation(event) {
273
- return __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivationViaEnter).call(this, event) || __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivationViaSpace).call(this, event);
274
- };
275
- _ActionMenuElement_isKeyboardActivationViaEnter = function _ActionMenuElement_isKeyboardActivationViaEnter(event) {
276
- return (event instanceof KeyboardEvent &&
277
- event.type === 'keydown' &&
278
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
279
- event.key === 'Enter');
280
- };
281
- _ActionMenuElement_isKeyboardActivationViaSpace = function _ActionMenuElement_isKeyboardActivationViaSpace(event) {
282
- return (event instanceof KeyboardEvent &&
267
+ _ActionMenuElement_isAnchorActivationViaSpace = function _ActionMenuElement_isAnchorActivationViaSpace(event) {
268
+ return (event.target instanceof HTMLAnchorElement &&
269
+ event instanceof KeyboardEvent &&
283
270
  event.type === 'keydown' &&
284
271
  !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
285
272
  event.key === ' ');
286
273
  };
287
- _ActionMenuElement_isMouseActivation = function _ActionMenuElement_isMouseActivation(event) {
288
- return event instanceof MouseEvent && event.type === 'click';
289
- };
290
274
  _ActionMenuElement_isActivation = function _ActionMenuElement_isActivation(event) {
291
- return __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isMouseActivation).call(this, event) || __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivation).call(this, event);
275
+ // Some browsers fire MouseEvents (Firefox) and others fire PointerEvents (Chrome). Activating an item via
276
+ // enter or space counterintuitively fires one of these rather than a KeyboardEvent. Since PointerEvent
277
+ // inherits from MouseEvent, it is enough to check for MouseEvent here.
278
+ return (event instanceof MouseEvent && event.type === 'click') || __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isAnchorActivationViaSpace).call(this, event);
292
279
  };
293
280
  _ActionMenuElement_handleInvokerActivated = function _ActionMenuElement_handleInvokerActivated(event) {
294
281
  event.preventDefault();
@@ -363,23 +350,6 @@ _ActionMenuElement_handleItemActivated = function _ActionMenuElement_handleItemA
363
350
  detail: { item: item.parentElement, checked: this.isItemChecked(item.parentElement) },
364
351
  }));
365
352
  };
366
- _ActionMenuElement_activateItem = function _ActionMenuElement_activateItem(event, item) {
367
- const eventWillActivateByDefault = (event instanceof MouseEvent && event.type === 'click') ||
368
- (event instanceof KeyboardEvent &&
369
- event.type === 'keydown' &&
370
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
371
- event.key === 'Enter');
372
- // if the event will result in activating the current item by default, i.e. is a
373
- // mouse click or keyboard enter, bail out
374
- if (eventWillActivateByDefault)
375
- return;
376
- // otherwise, event will not result in activation by default, so we stop it and
377
- // simulate a click
378
- /* eslint-disable-next-line no-restricted-syntax */
379
- event.stopPropagation();
380
- const elem = item;
381
- elem.click();
382
- };
383
353
  _ActionMenuElement_handleIncludeFragmentReplaced = function _ActionMenuElement_handleIncludeFragmentReplaced() {
384
354
  if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get))
385
355
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "a", _ActionMenuElement_firstItem_get).focus();
@@ -151,21 +151,9 @@ export class ActionMenuElement extends HTMLElement {
151
151
  return false
152
152
  }
153
153
 
154
- #isKeyboardActivation(event: Event): boolean {
155
- return this.#isKeyboardActivationViaEnter(event) || this.#isKeyboardActivationViaSpace(event)
156
- }
157
-
158
- #isKeyboardActivationViaEnter(event: Event): boolean {
159
- return (
160
- event instanceof KeyboardEvent &&
161
- event.type === 'keydown' &&
162
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
163
- event.key === 'Enter'
164
- )
165
- }
166
-
167
- #isKeyboardActivationViaSpace(event: Event): boolean {
154
+ #isAnchorActivationViaSpace(event: Event): boolean {
168
155
  return (
156
+ event.target instanceof HTMLAnchorElement &&
169
157
  event instanceof KeyboardEvent &&
170
158
  event.type === 'keydown' &&
171
159
  !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
@@ -173,12 +161,11 @@ export class ActionMenuElement extends HTMLElement {
173
161
  )
174
162
  }
175
163
 
176
- #isMouseActivation(event: Event): boolean {
177
- return event instanceof MouseEvent && event.type === 'click'
178
- }
179
-
180
164
  #isActivation(event: Event): boolean {
181
- return this.#isMouseActivation(event) || this.#isKeyboardActivation(event)
165
+ // Some browsers fire MouseEvents (Firefox) and others fire PointerEvents (Chrome). Activating an item via
166
+ // enter or space counterintuitively fires one of these rather than a KeyboardEvent. Since PointerEvent
167
+ // inherits from MouseEvent, it is enough to check for MouseEvent here.
168
+ return (event instanceof MouseEvent && event.type === 'click') || this.#isAnchorActivationViaSpace(event)
182
169
  }
183
170
 
184
171
  handleEvent(event: Event) {
@@ -237,21 +224,15 @@ export class ActionMenuElement extends HTMLElement {
237
224
  }
238
225
  }
239
226
 
240
- this.#activateItem(event, item)
241
- this.#handleItemActivated(item)
242
-
243
- // Pressing the space key on a button or link will cause the page to scroll unless preventDefault()
244
- // is called. While calling preventDefault() appears to have no effect on link navigation, it skips
245
- // form submission. The code below therefore only calls preventDefault() if the button has been
246
- // activated by the space key, and manually submits the form if the button is a submit button.
247
- if (this.#isKeyboardActivationViaSpace(event)) {
227
+ // Pressing the space key on a link will cause the page to scroll unless preventDefault() is called.
228
+ // We then click it manually to navigate.
229
+ if (this.#isAnchorActivationViaSpace(event)) {
248
230
  event.preventDefault()
249
-
250
- if (item.getAttribute('type') === 'submit') {
251
- item.closest('form')?.submit()
252
- }
231
+ ;(item as HTMLElement).click()
253
232
  }
254
233
 
234
+ this.#handleItemActivated(item)
235
+
255
236
  return
256
237
  }
257
238
 
@@ -335,6 +316,7 @@ export class ActionMenuElement extends HTMLElement {
335
316
  }
336
317
 
337
318
  this.#updateInput()
319
+
338
320
  this.dispatchEvent(
339
321
  new CustomEvent('itemActivated', {
340
322
  detail: {item: item.parentElement, checked: this.isItemChecked(item.parentElement)},
@@ -342,26 +324,6 @@ export class ActionMenuElement extends HTMLElement {
342
324
  )
343
325
  }
344
326
 
345
- #activateItem(event: Event, item: Element) {
346
- const eventWillActivateByDefault =
347
- (event instanceof MouseEvent && event.type === 'click') ||
348
- (event instanceof KeyboardEvent &&
349
- event.type === 'keydown' &&
350
- !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
351
- event.key === 'Enter')
352
-
353
- // if the event will result in activating the current item by default, i.e. is a
354
- // mouse click or keyboard enter, bail out
355
- if (eventWillActivateByDefault) return
356
-
357
- // otherwise, event will not result in activation by default, so we stop it and
358
- // simulate a click
359
- /* eslint-disable-next-line no-restricted-syntax */
360
- event.stopPropagation()
361
- const elem = item as HTMLElement
362
- elem.click()
363
- }
364
-
365
327
  #handleIncludeFragmentReplaced() {
366
328
  if (this.#firstItem) this.#firstItem.focus()
367
329
  this.#softDisableItems()
@@ -1 +1 @@
1
- .tabnav{border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal);margin-top:0}.tabnav-tabs{display:flex;margin-bottom:calc(var(--borderWidth-thin)*-1);overflow:auto}.tabnav-tab{background-color:initial;border:var(--borderWidth-thin) solid #0000;border-bottom:0;color:var(--fgColor-muted);display:inline-block;flex-shrink:0;font-size:var(--text-body-size-medium);line-height:23px;padding:var(--base-size-8) var(--control-medium-paddingInline-spacious);-webkit-text-decoration:none;text-decoration:none;transition:color .2s cubic-bezier(.3,0,.5,1)}.tabnav-tab.selected,.tabnav-tab[aria-current]:not([aria-current=false]),.tabnav-tab[aria-selected=true]{background-color:var(--bgColor-default);border-color:var(--borderColor-default);border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0;color:var(--fgColor-default)}.tabnav-tab.selected .octicon,.tabnav-tab[aria-current]:not([aria-current=false]) .octicon,.tabnav-tab[aria-selected=true] .octicon{color:inherit}.tabnav-tab:hover{color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition-duration:.1s}.tabnav-tab:focus,.tabnav-tab:focus-visible{border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0!important;outline-offset:-6px}.tabnav-tab .octicon,.tabnav-tab:active{color:var(--fgColor-muted)}.tabnav-tab .octicon{margin-right:var(--control-small-gap)}.tabnav-tab .Counter{color:inherit;margin-left:var(--control-small-gap)}.tabnav-extra{color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-small);margin-left:10px;padding-top:10px}.tabnav-extra>.octicon{margin-right:2px}a.tabnav-extra:hover{color:var(--fgColor-accent);-webkit-text-decoration:none;text-decoration:none}.tabnav-btn{margin-left:var(--controlStack-medium-gap-condensed)}
1
+ .tabnav{border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal);margin-top:0}.tabnav-tabs{display:flex;margin-bottom:calc(var(--borderWidth-thin)*-1);overflow:hidden}.tabnav::part(tablist-wrapper){border-bottom:var(--borderWidth-thin) solid var(--borderColor-default);margin-bottom:var(--stack-gap-normal)}.tabnav-tab{background-color:initial;border:var(--borderWidth-thin) solid #0000;border-bottom:0;color:var(--fgColor-muted);display:inline-block;flex-shrink:0;font-size:var(--text-body-size-medium);line-height:23px;padding:var(--base-size-8) var(--control-medium-paddingInline-spacious);-webkit-text-decoration:none;text-decoration:none;transition:color .2s cubic-bezier(.3,0,.5,1)}.tabnav-tab.selected,.tabnav-tab[aria-current]:not([aria-current=false]),.tabnav-tab[aria-selected=true]{background-color:var(--bgColor-default);border-color:var(--borderColor-default);border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0;color:var(--fgColor-default)}.tabnav-tab.selected .octicon,.tabnav-tab[aria-current]:not([aria-current=false]) .octicon,.tabnav-tab[aria-selected=true] .octicon{color:inherit}.tabnav-tab:hover{color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition-duration:.1s}.tabnav-tab:focus,.tabnav-tab:focus-visible{border-radius:var(--borderRadius-medium) var(--borderRadius-medium) 0 0!important;outline-offset:-6px}.tabnav-tab .octicon,.tabnav-tab:active{color:var(--fgColor-muted)}.tabnav-tab .octicon{margin-right:var(--control-small-gap)}.tabnav-tab .Counter{color:inherit;margin-left:var(--control-small-gap)}tab-container .tabnav-tab{margin-bottom:-1px}.tabnav-extra{color:var(--fgColor-muted);display:inline-block;font-size:var(--text-body-size-small);margin-left:10px;padding-top:10px}.tabnav-extra>.octicon{margin-right:2px}a.tabnav-extra:hover{color:var(--fgColor-accent);-webkit-text-decoration:none;text-decoration:none}.tabnav-btn{margin-left:var(--controlStack-medium-gap-condensed)}
@@ -3,6 +3,7 @@
3
3
  "selectors": [
4
4
  ".tabnav",
5
5
  ".tabnav-tabs",
6
+ ".tabnav::part(tablist-wrapper)",
6
7
  ".tabnav-tab",
7
8
  ".tabnav-tab.selected",
8
9
  ".tabnav-tab[aria-current]:not([aria-current=false])",
@@ -16,6 +17,7 @@
16
17
  ".tabnav-tab .octicon",
17
18
  ".tabnav-tab:active",
18
19
  ".tabnav-tab .Counter",
20
+ "tab-container .tabnav-tab",
19
21
  ".tabnav-extra",
20
22
  ".tabnav-extra>.octicon",
21
23
  "a.tabnav-extra:hover",
@@ -1 +1 @@
1
- {"version":3,"sources":["tab_nav.pcss"],"names":[],"mappings":"AAGA,QAGE,sEAAuE,CADvE,qCAAsC,CADtC,YAGF,CAEA,aACE,YAAa,CACb,8CAAiD,CACjD,aACF,CAEA,YAQE,wBAA6B,CAE7B,0CAAgB,CAAhB,eAAgB,CAJhB,0BAA2B,CAL3B,oBAAqB,CACrB,aAAc,CAEd,sCAAuC,CACvC,gBAAiB,CAFjB,uEAAwE,CAIxE,4BAAqB,CAArB,oBAAqB,CAIrB,4CAwCF,CAtCE,yGAIE,uCAAwC,CACxC,uCAAwC,CACxC,uEAAwE,CAHxE,4BAQF,CAHE,oIACE,aACF,CAGF,kBACE,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CACrB,uBACF,CAEA,4CAEE,iFAAmF,CACnF,mBACF,CAMA,wCAHE,0BAMF,CAHA,qBACE,qCAEF,CAEA,qBAEE,aAAc,CADd,oCAEF,CAQF,cAKE,0BAA2B,CAJ3B,oBAAqB,CAGrB,qCAAsC,CADtC,gBAAiB,CADjB,gBAQF,CAHE,uBACE,gBACF,CAKF,qBACE,2BAA4B,CAC5B,4BAAqB,CAArB,oBACF,CAOA,YACE,oDACF","file":"tab_nav.css","sourcesContent":["/* tabnav */\n\n/* Outer wrapper */\n.tabnav {\n margin-top: 0;\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);\n}\n\n.tabnav-tabs {\n display: flex;\n margin-bottom: calc(var(--borderWidth-thin) * -1);\n overflow: auto;\n}\n\n.tabnav-tab {\n display: inline-block;\n flex-shrink: 0;\n padding: var(--base-size-8) var(--control-medium-paddingInline-spacious);\n font-size: var(--text-body-size-medium);\n line-height: 23px;\n color: var(--fgColor-muted);\n text-decoration: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid transparent;\n border-bottom: 0;\n transition: color 0.2s cubic-bezier(0.3, 0, 0.5, 1);\n\n &.selected,\n &[aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n color: var(--fgColor-default);\n background-color: var(--bgColor-default); /* cover bottom border */\n border-color: var(--borderColor-default);\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0;\n\n & .octicon {\n color: inherit;\n }\n }\n\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n transition-duration: 0.1s;\n }\n\n &:focus,\n &:focus-visible {\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0 !important;\n outline-offset: -6px;\n }\n\n &:active {\n color: var(--fgColor-muted);\n }\n\n & .octicon {\n margin-right: var(--control-small-gap);\n color: var(--fgColor-muted);\n }\n\n & .Counter {\n margin-left: var(--control-small-gap);\n color: inherit;\n }\n}\n\n/* Tabnav extras\n**\n** Tabnav extras are non-tab elements that sit in the tabnav. Usually they're\n** inline text or links. */\n\n.tabnav-extra {\n display: inline-block;\n padding-top: 10px;\n margin-left: 10px;\n font-size: var(--text-body-size-small);\n color: var(--fgColor-muted);\n\n & > .octicon {\n margin-right: 2px;\n }\n}\n\n/* When tabnav-extra are anchors\n** stylelint-disable-next-line selector-no-qualifying-type */\na.tabnav-extra:hover {\n color: var(--fgColor-accent);\n text-decoration: none;\n}\n\n/* Tabnav buttons\n**\n** For when there are multiple buttons, space them out appropriately. Requires\n** the buttons to be floated or inline-block. */\n\n.tabnav-btn {\n margin-left: var(--controlStack-medium-gap-condensed);\n}\n"]}
1
+ {"version":3,"sources":["tab_nav.pcss"],"names":[],"mappings":"AAGA,QAGE,sEAAuE,CADvE,qCAAsC,CADtC,YAGF,CAEA,aACE,YAAa,CACb,8CAAiD,CACjD,eACF,CAEA,+BAEE,sEAAuE,CADvE,qCAEF,CAEA,YAQE,wBAA6B,CAE7B,0CAAgB,CAAhB,eAAgB,CAJhB,0BAA2B,CAL3B,oBAAqB,CACrB,aAAc,CAEd,sCAAuC,CACvC,gBAAiB,CAFjB,uEAAwE,CAIxE,4BAAqB,CAArB,oBAAqB,CAIrB,4CAwCF,CAtCE,yGAIE,uCAAwC,CACxC,uCAAwC,CACxC,uEAAwE,CAHxE,4BAQF,CAHE,oIACE,aACF,CAGF,kBACE,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CACrB,uBACF,CAEA,4CAEE,iFAAmF,CACnF,mBACF,CAMA,wCAHE,0BAMF,CAHA,qBACE,qCAEF,CAEA,qBAEE,aAAc,CADd,oCAEF,CAGF,0BACE,kBACF,CAQA,cAKE,0BAA2B,CAJ3B,oBAAqB,CAGrB,qCAAsC,CADtC,gBAAiB,CADjB,gBAQF,CAHE,uBACE,gBACF,CAKF,qBACE,2BAA4B,CAC5B,4BAAqB,CAArB,oBACF,CAOA,YACE,oDACF","file":"tab_nav.css","sourcesContent":["/* tabnav */\n\n/* Outer wrapper */\n.tabnav {\n margin-top: 0;\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);\n}\n\n.tabnav-tabs {\n display: flex;\n margin-bottom: calc(var(--borderWidth-thin) * -1);\n overflow: hidden;\n}\n\n.tabnav::part(tablist-wrapper) {\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);\n}\n\n.tabnav-tab {\n display: inline-block;\n flex-shrink: 0;\n padding: var(--base-size-8) var(--control-medium-paddingInline-spacious);\n font-size: var(--text-body-size-medium);\n line-height: 23px;\n color: var(--fgColor-muted);\n text-decoration: none;\n background-color: transparent;\n border: var(--borderWidth-thin) solid transparent;\n border-bottom: 0;\n transition: color 0.2s cubic-bezier(0.3, 0, 0.5, 1);\n\n &.selected,\n &[aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n color: var(--fgColor-default);\n background-color: var(--bgColor-default); /* cover bottom border */\n border-color: var(--borderColor-default);\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0;\n\n & .octicon {\n color: inherit;\n }\n }\n\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n transition-duration: 0.1s;\n }\n\n &:focus,\n &:focus-visible {\n border-radius: var(--borderRadius-medium) var(--borderRadius-medium) 0 0 !important;\n outline-offset: -6px;\n }\n\n &:active {\n color: var(--fgColor-muted);\n }\n\n & .octicon {\n margin-right: var(--control-small-gap);\n color: var(--fgColor-muted);\n }\n\n & .Counter {\n margin-left: var(--control-small-gap);\n color: inherit;\n }\n}\n\ntab-container .tabnav-tab {\n margin-bottom: -1px;\n}\n\n\n/* Tabnav extras\n**\n** Tabnav extras are non-tab elements that sit in the tabnav. Usually they're\n** inline text or links. */\n\n.tabnav-extra {\n display: inline-block;\n padding-top: 10px;\n margin-left: 10px;\n font-size: var(--text-body-size-small);\n color: var(--fgColor-muted);\n\n & > .octicon {\n margin-right: 2px;\n }\n}\n\n/* When tabnav-extra are anchors\n** stylelint-disable-next-line selector-no-qualifying-type */\na.tabnav-extra:hover {\n color: var(--fgColor-accent);\n text-decoration: none;\n}\n\n/* Tabnav buttons\n**\n** For when there are multiple buttons, space them out appropriately. Requires\n** the buttons to be floated or inline-block. */\n\n.tabnav-btn {\n margin-left: var(--controlStack-medium-gap-condensed);\n}\n"]}
@@ -10,7 +10,12 @@
10
10
  .tabnav-tabs {
11
11
  display: flex;
12
12
  margin-bottom: calc(var(--borderWidth-thin) * -1);
13
- overflow: auto;
13
+ overflow: hidden;
14
+ }
15
+
16
+ .tabnav::part(tablist-wrapper) {
17
+ margin-bottom: var(--stack-gap-normal);
18
+ border-bottom: var(--borderWidth-thin) solid var(--borderColor-default);
14
19
  }
15
20
 
16
21
  .tabnav-tab {
@@ -66,6 +71,11 @@
66
71
  }
67
72
  }
68
73
 
74
+ tab-container .tabnav-tab {
75
+ margin-bottom: -1px;
76
+ }
77
+
78
+
69
79
  /* Tabnav extras
70
80
  **
71
81
  ** Tabnav extras are non-tab elements that sit in the tabnav. Usually they're
@@ -1,13 +1,9 @@
1
- <%= tab_container_wrapper(with_panel: true, **@wrapper_arguments) do %>
2
- <%= render Primer::BaseComponent.new(**@system_arguments) do %>
3
- <%= extra if @align == :left %>
4
- <%= render Primer::BaseComponent.new(**@body_arguments) do %>
5
- <% tabs.each do |tab| %>
6
- <%= tab %>
7
- <% end %>
8
- <% end %>
9
- <%= extra if @align == :right %>
1
+ <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
+ <%= extra if @align == :left %>
3
+ <% tabs.each do |tab| %>
4
+ <%= tab %>
10
5
  <% end %>
6
+ <%= extra if @align == :right %>
11
7
  <% tabs.each do |tab| %>
12
8
  <%= tab.panel %>
13
9
  <% end %>
@@ -26,7 +26,7 @@ module Primer
26
26
  Primer::Alpha::Navigation::Tab.new(
27
27
  selected: selected,
28
28
  with_panel: true,
29
- list: true,
29
+ list: false,
30
30
  panel_id: "panel-#{id}",
31
31
  **system_arguments
32
32
  )
@@ -43,23 +43,14 @@ module Primer
43
43
 
44
44
  # @param label [String] Sets an `aria-label` that helps assistive technology users understand the purpose of the tabs.
45
45
  # @param align [Symbol] <%= one_of(Primer::TabNavHelper::EXTRA_ALIGN_OPTIONS) %> - Defaults to <%= Primer::TabNavHelper::EXTRA_ALIGN_DEFAULT %>
46
- # @param body_arguments [Hash] <%= link_to_system_arguments_docs %> for the body wrapper.
47
- # @param wrapper_arguments [Hash] <%= link_to_system_arguments_docs %> for the `TabContainer` wrapper.
48
46
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
49
47
  def initialize(label:, body_arguments: {}, wrapper_arguments: {}, **system_arguments)
50
48
  @align = EXTRA_ALIGN_DEFAULT
51
- @wrapper_arguments = wrapper_arguments
52
49
 
53
- @system_arguments = deny_tag_argument(**system_arguments)
54
- @system_arguments[:tag] = :div
50
+ @system_arguments = { **deny_tag_argument(**system_arguments), **deny_tag_argument(**wrapper_arguments) }
51
+ @system_arguments[:tag] = :"tab-container"
55
52
  @system_arguments[:classes] = tab_nav_classes(@system_arguments[:classes])
56
-
57
- @body_arguments = deny_tag_argument(**body_arguments)
58
- @body_arguments[:tag] = :ul
59
- @body_arguments[:classes] = tab_nav_body_classes(@body_arguments[:classes])
60
-
61
- @body_arguments[:role] = :tablist
62
- @body_arguments[:"aria-label"] = label
53
+ @system_arguments[:"aria-label"] = label
63
54
  end
64
55
 
65
56
  def before_render
@@ -26,6 +26,7 @@ module Primer
26
26
  # @param monospace [Boolean] If `true`, uses a monospace font for the input field.
27
27
  # @param auto_check_src [String] When provided, makes a request to the given URL whenever the contents of the text field changes. If the server responds with a non-2xx status code, the response body is used as the validation message.
28
28
  # @param leading_visual [Hash] Renders a leading visual icon before the text field's cursor. The hash will be passed to Primer's <%= link_to_component(Primer::Beta::Octicon) %> component.
29
+ # @param leading_spinner [Boolean] If `true`, a leading spinner will be included in the markup. The spinner can be shown via the `showLeadingSpinner()` JavaScript method, and hidden via `hideLeadingSpinner()`. If this argument is `true`, a leading visual must also be provided.
29
30
  # @param show_clear_button [Boolean] Whether or not to include a clear button inside the input that clears the input's contents when clicked.
30
31
  # @param clear_button_id [String] The HTML id attribute of the clear button.
31
32
  end
@@ -1 +1 @@
1
- .UnderlineNav{box-shadow:inset 0 -1px 0 var(--borderColor-muted);display:flex;min-height:var(--base-size-48);overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:auto;justify-content:space-between}.UnderlineNav .Counter{background-color:var(--bgColor-neutral-muted,var(--color-neutral-muted));color:var(--fgColor-default);margin-left:var(--control-medium-gap)}.UnderlineNav .Counter--primary{background-color:var(--bgColor-neutral-emphasis);color:var(--fgColor-onEmphasis)}.UnderlineNav-body{align-items:center;display:flex;gap:var(--control-medium-gap);list-style:none}.UnderlineNav-item{align-items:center;background-color:initial;border:0;border-radius:var(--borderRadius-medium);color:var(--fgColor-default);cursor:pointer;display:flex;font-size:var(--text-body-size-medium);line-height:30px;padding:0 var(--control-medium-paddingInline-condensed);position:relative;text-align:center;white-space:nowrap}.UnderlineNav-item:focus,.UnderlineNav-item:focus-visible,.UnderlineNav-item:hover{border-bottom-color:var(--borderColor-neutral-muted);color:var(--fgColor-default);outline-offset:-2px;-webkit-text-decoration:none;text-decoration:none;transition:border-bottom-color .12s ease-out}.UnderlineNav-item [data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold);height:0;visibility:hidden}.UnderlineNav-item:before{content:"";height:100%;left:50%;min-height:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}@media (pointer:fine){.UnderlineNav-item:hover{background:var(--control-transparent-bgColor-hover);color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition:background .12s ease-out}}.UnderlineNav-item.selected,.UnderlineNav-item[aria-current]:not([aria-current=false]),.UnderlineNav-item[role=tab][aria-selected=true]{border-bottom-color:var(--underlineNav-borderColor-active);color:var(--fgColor-default);font-weight:var(--base-text-weight-semibold)}.UnderlineNav-item.selected:after,.UnderlineNav-item[aria-current]:not([aria-current=false]):after,.UnderlineNav-item[role=tab][aria-selected=true]:after{background:var(--underlineNav-borderColor-active);border-radius:var(--borderRadius-medium);bottom:calc(50% - 25px);content:"";height:2px;position:absolute;right:50%;transform:translate(50%,-50%);width:100%;z-index:1}.UnderlineNav--right{justify-content:flex-end}.UnderlineNav--right .UnderlineNav-actions{flex:1 1 auto}.UnderlineNav-actions{align-self:center}.UnderlineNav--full{display:block}.UnderlineNav--full .UnderlineNav-body{min-height:var(--base-size-48)}.UnderlineNav-octicon{color:var(--fgColor-muted);display:inline!important;margin-right:var(--control-medium-gap);fill:var(--fgColor-muted)}.UnderlineNav-container{display:flex;justify-content:space-between}
1
+ .UnderlineNav{box-shadow:inset 0 -1px 0 var(--borderColor-muted);display:flex;min-height:var(--base-size-48);overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:auto;justify-content:space-between}.UnderlineNav .Counter{background-color:var(--bgColor-neutral-muted,var(--color-neutral-muted));color:var(--fgColor-default);margin-left:var(--control-medium-gap)}.UnderlineNav .Counter--primary{background-color:var(--bgColor-neutral-emphasis);color:var(--fgColor-onEmphasis)}.UnderlineNav::part(tablist-wrapper){box-shadow:inset 0 -1px 0 var(--borderColor-muted);padding:var(--control-medium-gap) 0;width:100%}.UnderlineNav-body,.UnderlineNav::part(tablist){align-items:center;display:flex;gap:var(--control-medium-gap);list-style:none}.UnderlineNav-item{align-items:center;background-color:initial;border:0;border-radius:var(--borderRadius-medium);color:var(--fgColor-default);cursor:pointer;display:flex;font-size:var(--text-body-size-medium);line-height:30px;padding:0 var(--control-medium-paddingInline-condensed);position:relative;text-align:center;white-space:nowrap}.UnderlineNav-item:focus,.UnderlineNav-item:focus-visible,.UnderlineNav-item:hover{border-bottom-color:var(--borderColor-neutral-muted);color:var(--fgColor-default);outline-offset:-2px;-webkit-text-decoration:none;text-decoration:none;transition:border-bottom-color .12s ease-out}.UnderlineNav-item [data-content]:before{content:attr(data-content);display:block;font-weight:var(--base-text-weight-semibold);height:0;visibility:hidden}.UnderlineNav-item:before{content:"";height:100%;left:50%;min-height:48px;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:100%}@media (pointer:fine){.UnderlineNav-item:hover{background:var(--control-transparent-bgColor-hover);color:var(--fgColor-default);-webkit-text-decoration:none;text-decoration:none;transition:background .12s ease-out}}.UnderlineNav-item.selected,.UnderlineNav-item[aria-current]:not([aria-current=false]),.UnderlineNav-item[role=tab][aria-selected=true]{border-bottom-color:var(--underlineNav-borderColor-active);color:var(--fgColor-default);font-weight:var(--base-text-weight-semibold)}.UnderlineNav-item.selected:after,.UnderlineNav-item[aria-current]:not([aria-current=false]):after,.UnderlineNav-item[role=tab][aria-selected=true]:after{background:var(--underlineNav-borderColor-active);border-radius:var(--borderRadius-medium);bottom:calc(50% - 25px);content:"";height:2px;position:absolute;right:50%;transform:translate(50%,-50%);width:100%;z-index:1}.UnderlineNav--right{justify-content:flex-end}.UnderlineNav--right .UnderlineNav-actions{flex:1 1 auto}.UnderlineNav-actions{align-self:center}.UnderlineNav--full{display:block}.UnderlineNav--full .UnderlineNav-body{min-height:var(--base-size-48)}.UnderlineNav-octicon{color:var(--fgColor-muted);display:inline!important;margin-right:var(--control-medium-gap);fill:var(--fgColor-muted)}.UnderlineNav-container{display:flex;justify-content:space-between}
@@ -4,7 +4,9 @@
4
4
  ".UnderlineNav",
5
5
  ".UnderlineNav .Counter",
6
6
  ".UnderlineNav .Counter--primary",
7
+ ".UnderlineNav::part(tablist-wrapper)",
7
8
  ".UnderlineNav-body",
9
+ ".UnderlineNav::part(tablist)",
8
10
  ".UnderlineNav-item",
9
11
  ".UnderlineNav-item:focus",
10
12
  ".UnderlineNav-item:focus-visible",
@@ -1 +1 @@
1
- {"version":3,"sources":["underline_nav.pcss","<no source>"],"names":[],"mappings":"AAEA,cAKE,kDAAmD,CAJnD,YAAa,CACb,8BAA+B,CAC/B,eAAgB,CAChB,iBAAkB,CAElB,+BAAgC,CAChC,6BAYF,CAVE,uBAGE,wEAA0E,CAD1E,4BAA6B,CAD7B,qCAGF,CAEA,gCAEE,gDAAiD,CADjD,+BAEF,CAGF,mBAEE,kBAAmB,CADnB,YAAa,CAEb,6BAA8B,CAC9B,eACF,CAEA,mBAaE,kBAAmB,CAHnB,wBAA6B,CAC7B,QAAS,CACT,wCAAyC,CANzC,4BAA6B,CAG7B,cAAe,CAPf,YAAa,CAEb,sCAAuC,CACvC,gBAAiB,CAFjB,uDAAwD,CAFxD,iBAAkB,CAMlB,iBAAkB,CAClB,kBA8DF,CAvDE,mFAKE,oDAAqD,CAFrD,4BAA6B,CAG7B,mBAAoB,CAFpB,4BAAqB,CAArB,oBAAqB,CAGrB,4CACF,CAGA,yCAKE,0BAA2B,CAJ3B,aAAc,CAEd,4CAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,0BClEJ,WAAA,YAAA,SAAA,gBAAA,kBAAA,QAAA,4CAAA,UDkE8B,CAI5B,sBACE,yBAGE,mDAAoD,CAFpD,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CAErB,mCACF,CACF,CAEA,wIAKE,0DAA2D,CAD3D,4BAA6B,CAD7B,4CAiBF,CAZE,0JAQE,iDAAkD,CAClD,wCAAyC,CALzC,uBAAwB,CAGxB,UAAW,CADX,UAAW,CALX,iBAAkB,CAElB,SAAU,CAOV,6BAA+B,CAL/B,UAAW,CAHX,SASF,CAIJ,qBACE,wBAKF,CAHE,2CACE,aACF,CAGF,sBACE,iBACF,CAEA,oBACE,aAMF,CAHE,uCACE,8BACF,CAGF,sBAGE,0BAA2B,CAF3B,wBAA0B,CAC1B,sCAAuC,CAEvC,yBACF,CAEA,wBACE,YAAa,CACb,6BACF","file":"underline_nav.css","sourcesContent":["/* UnderlineNav */\n\n.UnderlineNav {\n display: flex;\n min-height: var(--base-size-48);\n overflow-x: auto;\n overflow-y: hidden;\n box-shadow: inset 0 -1px 0 var(--borderColor-muted);\n -webkit-overflow-scrolling: auto;\n justify-content: space-between;\n\n & .Counter {\n margin-left: var(--control-medium-gap);\n color: var(--fgColor-default);\n background-color: var(--bgColor-neutral-muted, var(--color-neutral-muted));\n }\n\n & .Counter--primary {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-neutral-emphasis);\n }\n}\n\n.UnderlineNav-body {\n display: flex;\n align-items: center;\n gap: var(--control-medium-gap);\n list-style: none;\n}\n\n.UnderlineNav-item {\n position: relative;\n display: flex;\n padding: 0 var(--control-medium-paddingInline-condensed);\n font-size: var(--text-body-size-medium);\n line-height: 30px;\n color: var(--fgColor-default);\n text-align: center;\n white-space: nowrap;\n cursor: pointer;\n background-color: transparent;\n border: 0;\n border-radius: var(--borderRadius-medium);\n align-items: center;\n\n &:hover,\n &:focus,\n &:focus-visible {\n color: var(--fgColor-default);\n text-decoration: none;\n border-bottom-color: var(--borderColor-neutral-muted);\n outline-offset: -2px;\n transition: border-bottom-color 0.12s ease-out;\n }\n\n /* renders a visibly hidden \"copy\" of the label in bold, reserving box space for when label becomes bold on selected */\n & [data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* increase touch target area */\n &::before {\n @mixin minTouchTarget 48px;\n }\n\n /* hover state was \"sticking\" on mobile after click */\n @media (pointer: fine) {\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n background: var(--control-transparent-bgColor-hover);\n transition: background 0.12s ease-out;\n }\n }\n\n &.selected,\n &[role='tab'][aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n font-weight: var(--base-text-weight-semibold);\n color: var(--fgColor-default);\n border-bottom-color: var(--underlineNav-borderColor-active);\n\n /* current/selected underline */\n &::after {\n position: absolute;\n z-index: 1; /* raise above full-width flash banner */\n right: 50%;\n bottom: calc(50% - 25px); /* 48px total height / 2 (24px) + 1px */\n width: 100%;\n height: 2px;\n content: '';\n background: var(--underlineNav-borderColor-active);\n border-radius: var(--borderRadius-medium);\n transform: translate(50%, -50%);\n }\n }\n}\n\n.UnderlineNav--right {\n justify-content: flex-end;\n\n & .UnderlineNav-actions {\n flex: 1 1 auto;\n }\n}\n\n.UnderlineNav-actions {\n align-self: center;\n}\n\n.UnderlineNav--full {\n display: block;\n\n /* required for underline to align with additional wrapper element */\n & .UnderlineNav-body {\n min-height: var(--base-size-48);\n }\n}\n\n.UnderlineNav-octicon {\n display: inline !important;\n margin-right: var(--control-medium-gap);\n color: var(--fgColor-muted);\n fill: var(--fgColor-muted);\n}\n\n.UnderlineNav-container {\n display: flex;\n justify-content: space-between;\n}\n",null]}
1
+ {"version":3,"sources":["underline_nav.pcss","<no source>"],"names":[],"mappings":"AAEA,cAKE,kDAAmD,CAJnD,YAAa,CACb,8BAA+B,CAC/B,eAAgB,CAChB,iBAAkB,CAElB,+BAAgC,CAChC,6BAYF,CAVE,uBAGE,wEAA0E,CAD1E,4BAA6B,CAD7B,qCAGF,CAEA,gCAEE,gDAAiD,CADjD,+BAEF,CAGF,qCAEE,kDAAmD,CACnD,mCAAoC,CAFpC,UAGF,CAEA,gDAEE,kBAAmB,CADnB,YAAa,CAEb,6BAA8B,CAC9B,eACF,CAEA,mBAaE,kBAAmB,CAHnB,wBAA6B,CAC7B,QAAS,CACT,wCAAyC,CANzC,4BAA6B,CAG7B,cAAe,CAPf,YAAa,CAEb,sCAAuC,CACvC,gBAAiB,CAFjB,uDAAwD,CAFxD,iBAAkB,CAMlB,iBAAkB,CAClB,kBA8DF,CAvDE,mFAKE,oDAAqD,CAFrD,4BAA6B,CAG7B,mBAAoB,CAFpB,4BAAqB,CAArB,oBAAqB,CAGrB,4CACF,CAGA,yCAKE,0BAA2B,CAJ3B,aAAc,CAEd,4CAA6C,CAD7C,QAAS,CAET,iBAEF,CAIE,0BCxEJ,WAAA,YAAA,SAAA,gBAAA,kBAAA,QAAA,4CAAA,UDwE8B,CAI5B,sBACE,yBAGE,mDAAoD,CAFpD,4BAA6B,CAC7B,4BAAqB,CAArB,oBAAqB,CAErB,mCACF,CACF,CAEA,wIAKE,0DAA2D,CAD3D,4BAA6B,CAD7B,4CAiBF,CAZE,0JAQE,iDAAkD,CAClD,wCAAyC,CALzC,uBAAwB,CAGxB,UAAW,CADX,UAAW,CALX,iBAAkB,CAElB,SAAU,CAOV,6BAA+B,CAL/B,UAAW,CAHX,SASF,CAIJ,qBACE,wBAKF,CAHE,2CACE,aACF,CAGF,sBACE,iBACF,CAEA,oBACE,aAMF,CAHE,uCACE,8BACF,CAGF,sBAGE,0BAA2B,CAF3B,wBAA0B,CAC1B,sCAAuC,CAEvC,yBACF,CAEA,wBACE,YAAa,CACb,6BACF","file":"underline_nav.css","sourcesContent":["/* UnderlineNav */\n\n.UnderlineNav {\n display: flex;\n min-height: var(--base-size-48);\n overflow-x: auto;\n overflow-y: hidden;\n box-shadow: inset 0 -1px 0 var(--borderColor-muted);\n -webkit-overflow-scrolling: auto;\n justify-content: space-between;\n\n & .Counter {\n margin-left: var(--control-medium-gap);\n color: var(--fgColor-default);\n background-color: var(--bgColor-neutral-muted, var(--color-neutral-muted));\n }\n\n & .Counter--primary {\n color: var(--fgColor-onEmphasis);\n background-color: var(--bgColor-neutral-emphasis);\n }\n}\n\n.UnderlineNav::part(tablist-wrapper) {\n width: 100%;\n box-shadow: inset 0 -1px 0 var(--borderColor-muted);\n padding: var(--control-medium-gap) 0;\n}\n\n.UnderlineNav-body,.UnderlineNav::part(tablist) {\n display: flex;\n align-items: center;\n gap: var(--control-medium-gap);\n list-style: none;\n}\n\n.UnderlineNav-item {\n position: relative;\n display: flex;\n padding: 0 var(--control-medium-paddingInline-condensed);\n font-size: var(--text-body-size-medium);\n line-height: 30px;\n color: var(--fgColor-default);\n text-align: center;\n white-space: nowrap;\n cursor: pointer;\n background-color: transparent;\n border: 0;\n border-radius: var(--borderRadius-medium);\n align-items: center;\n\n &:hover,\n &:focus,\n &:focus-visible {\n color: var(--fgColor-default);\n text-decoration: none;\n border-bottom-color: var(--borderColor-neutral-muted);\n outline-offset: -2px;\n transition: border-bottom-color 0.12s ease-out;\n }\n\n /* renders a visibly hidden \"copy\" of the label in bold, reserving box space for when label becomes bold on selected */\n & [data-content]::before {\n display: block;\n height: 0;\n font-weight: var(--base-text-weight-semibold);\n visibility: hidden;\n content: attr(data-content);\n }\n\n /* increase touch target area */\n &::before {\n @mixin minTouchTarget 48px;\n }\n\n /* hover state was \"sticking\" on mobile after click */\n @media (pointer: fine) {\n &:hover {\n color: var(--fgColor-default);\n text-decoration: none;\n background: var(--control-transparent-bgColor-hover);\n transition: background 0.12s ease-out;\n }\n }\n\n &.selected,\n &[role='tab'][aria-selected='true'],\n &[aria-current]:not([aria-current='false']) {\n font-weight: var(--base-text-weight-semibold);\n color: var(--fgColor-default);\n border-bottom-color: var(--underlineNav-borderColor-active);\n\n /* current/selected underline */\n &::after {\n position: absolute;\n z-index: 1; /* raise above full-width flash banner */\n right: 50%;\n bottom: calc(50% - 25px); /* 48px total height / 2 (24px) + 1px */\n width: 100%;\n height: 2px;\n content: '';\n background: var(--underlineNav-borderColor-active);\n border-radius: var(--borderRadius-medium);\n transform: translate(50%, -50%);\n }\n }\n}\n\n.UnderlineNav--right {\n justify-content: flex-end;\n\n & .UnderlineNav-actions {\n flex: 1 1 auto;\n }\n}\n\n.UnderlineNav-actions {\n align-self: center;\n}\n\n.UnderlineNav--full {\n display: block;\n\n /* required for underline to align with additional wrapper element */\n & .UnderlineNav-body {\n min-height: var(--base-size-48);\n }\n}\n\n.UnderlineNav-octicon {\n display: inline !important;\n margin-right: var(--control-medium-gap);\n color: var(--fgColor-muted);\n fill: var(--fgColor-muted);\n}\n\n.UnderlineNav-container {\n display: flex;\n justify-content: space-between;\n}\n",null]}
@@ -21,7 +21,13 @@
21
21
  }
22
22
  }
23
23
 
24
- .UnderlineNav-body {
24
+ .UnderlineNav::part(tablist-wrapper) {
25
+ width: 100%;
26
+ box-shadow: inset 0 -1px 0 var(--borderColor-muted);
27
+ padding: var(--control-medium-gap) 0;
28
+ }
29
+
30
+ .UnderlineNav-body,.UnderlineNav::part(tablist) {
25
31
  display: flex;
26
32
  align-items: center;
27
33
  gap: var(--control-medium-gap);
@@ -0,0 +1 @@
1
+ tab-container.UnderlineNav{box-shadow:none;flex-direction:column}
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "alpha/underline_panels",
3
+ "selectors": [
4
+ "tab-container.UnderlineNav"
5
+ ]
6
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["underline_panels.pcss"],"names":[],"mappings":"AAAA,2BACE,eAAgB,CAChB,qBACF","file":"underline_panels.css","sourcesContent":["tab-container.UnderlineNav {\n box-shadow: none;\n flex-direction: column;\n}\n"]}
@@ -1,18 +1,16 @@
1
- <%= tab_container_wrapper(with_panel: true, **@wrapper_arguments) do %>
1
+ <%= render Primer::BaseComponent.new(**@wrapper_arguments) do %>
2
2
  <%= render Primer::BaseComponent.new(**@system_arguments) do %>
3
3
  <% if @align == :right %>
4
4
  <%= actions %>
5
5
  <% end %>
6
- <%= render body do %>
7
- <% tabs.each do |tab| %>
8
- <%= tab %>
9
- <% end %>
6
+ <% tabs.each do |tab| %>
7
+ <%= tab %>
10
8
  <% end %>
11
9
  <% if @align == :left %>
12
10
  <%= actions %>
13
11
  <% end %>
14
- <% end %>
15
- <% tabs.each do |tab| %>
16
- <%= tab.panel %>
12
+ <% tabs.each do |tab| %>
13
+ <%= tab.panel %>
14
+ <% end %>
17
15
  <% end %>
18
16
  <% end %>
@@ -0,0 +1,4 @@
1
+ tab-container.UnderlineNav {
2
+ box-shadow: none;
3
+ flex-direction: column;
4
+ }
@@ -18,7 +18,7 @@ module Primer
18
18
  Primer::Alpha::Navigation::Tab.new(
19
19
  selected: selected,
20
20
  with_panel: true,
21
- list: true,
21
+ list: false,
22
22
  icon_classes: "UnderlineNav-octicon",
23
23
  panel_id: "panel-#{id}",
24
24
  **system_arguments
@@ -43,24 +43,16 @@ module Primer
43
43
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
44
44
  def initialize(label:, align: ALIGN_DEFAULT, body_arguments: {}, wrapper_arguments: {}, **system_arguments)
45
45
  @align = fetch_or_fallback(ALIGN_OPTIONS, align, ALIGN_DEFAULT)
46
- @wrapper_arguments = wrapper_arguments
46
+ @wrapper_arguments = deny_tag_argument(**wrapper_arguments)
47
+ @wrapper_arguments[:tag] = :div
47
48
 
48
49
  @system_arguments = deny_tag_argument(**system_arguments)
49
- @system_arguments[:tag] = :div
50
+ @system_arguments[:tag] = :"tab-container"
50
51
  @system_arguments[:classes] = underline_nav_classes(@system_arguments[:classes], @align)
52
+ @system_arguments[:"aria-label"] = label
51
53
 
52
54
  @body_arguments = deny_tag_argument(**body_arguments)
53
- @body_arguments[:tag] = :ul
54
- @body_arguments[:classes] = underline_nav_body_classes(@body_arguments[:classes])
55
-
56
- @body_arguments[:role] = :tablist
57
- @body_arguments[:"aria-label"] = label
58
- end
59
-
60
- private
61
-
62
- def body
63
- Primer::BaseComponent.new(**@body_arguments)
55
+ @body_arguments[:tag] = :div
64
56
  end
65
57
  end
66
58
  end
@@ -14,6 +14,11 @@ module Primer
14
14
  # Either `aria-label` or `aria-description` will be used for the `Tooltip` text, depending on which one is present.
15
15
  # Either `aria-label` or `aria-description` will be used for the `Tooltip` text, depending on which one is present.
16
16
  # [Learn more about best functional image practices (WAI Images)](https://www.w3.org/WAI/tutorials/images/functional)
17
+ #
18
+ # Additional markup is required if setting the `tag` argument to either `:a` or `:summary`.
19
+ #
20
+ # * `:a` requires you to pass in an `href` attribute
21
+ # * `:summary` requires you to wrap the component in a `<details>` element
17
22
  class IconButton < Primer::Component
18
23
  status :beta
19
24
 
@@ -101,6 +101,7 @@ module Primer
101
101
  # @param format_style [Symbol] The format the display should take. <%= one_of(Primer::Beta::RelativeTime::FORMAT_STYLE_OPTIONS) %>
102
102
  # @param lang [string] The language to use.
103
103
  # @param title [string] Provide a custom title to the element.
104
+ # @param no_title [Boolean] Removes the `title` attribute provided on the element by default.
104
105
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
105
106
  def initialize(
106
107
  datetime:,
@@ -120,6 +121,7 @@ module Primer
120
121
  format_style: nil,
121
122
  lang: nil,
122
123
  title: nil,
124
+ no_title: false,
123
125
  **system_arguments
124
126
  )
125
127
  @system_arguments = deny_tag_argument(**system_arguments)
@@ -137,6 +139,7 @@ module Primer
137
139
  @system_arguments[:threshold] = threshold if threshold.present?
138
140
  @system_arguments[:precision] = precision if precision.present?
139
141
  @system_arguments[:title] = title if title.present?
142
+ @system_arguments[:"no-title"] = no_title if no_title
140
143
  @system_arguments[:lang] = lang if lang.present?
141
144
  @system_arguments[:format] = fetch_or_fallback(FORMAT_OPTIONS, format, FORMAT_DEFAULT) if format.present?
142
145
  @system_arguments[:"format-style"] = format_style if format_style.present?
@@ -2,3 +2,6 @@
2
2
  <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none" />
3
3
  <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke" />
4
4
  <% end %>
5
+ <% if no_aria_label? %>
6
+ <span class="sr-only"><%= @sr_text %></span>
7
+ <% end %>
@@ -12,6 +12,7 @@ module Primer
12
12
  DEFAULT_SIZE => 32,
13
13
  :large => 64
14
14
  }.freeze
15
+ DEFAULT_SR_TEXT = "Loading"
15
16
 
16
17
  SIZE_OPTIONS = SIZE_MAPPINGS.keys
17
18
 
@@ -22,7 +23,7 @@ module Primer
22
23
  # @param size [Symbol] <%= one_of(Primer::Beta::Spinner::SIZE_MAPPINGS) %>
23
24
  # @param style [String] Custom element styles.
24
25
  # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
25
- def initialize(size: DEFAULT_SIZE, style: DEFAULT_STYLE, **system_arguments)
26
+ def initialize(size: DEFAULT_SIZE, style: DEFAULT_STYLE, sr_text: DEFAULT_SR_TEXT, **system_arguments)
26
27
  @system_arguments = deny_tag_argument(**system_arguments)
27
28
  @system_arguments[:tag] = :svg
28
29
  @system_arguments[:style] ||= style
@@ -31,6 +32,19 @@ module Primer
31
32
  @system_arguments[:height] = SIZE_MAPPINGS[fetch_or_fallback(SIZE_OPTIONS, size, DEFAULT_SIZE)]
32
33
  @system_arguments[:viewBox] = "0 0 16 16"
33
34
  @system_arguments[:fill] = :none
35
+ @system_arguments[:aria] ||= {}
36
+ @sr_text = sr_text
37
+
38
+ if no_aria_label?
39
+ @system_arguments[:aria][:hidden] = true
40
+ else
41
+ @system_arguments[:role] = "img"
42
+ end
43
+ end
44
+
45
+ def no_aria_label?
46
+ !@system_arguments[:aria][:label] && !@system_arguments[:"aria-label"] &&
47
+ !@system_arguments[:aria][:labelledby] && !@system_arguments[:"aria-labelledby"]
34
48
  end
35
49
  end
36
50
  end
@@ -3,6 +3,9 @@
3
3
  <div class="PageHeader-contextBar">
4
4
  <%= @parent_link %>
5
5
  <%= breadcrumbs %>
6
+ <% if @mobile_segmented_control %>
7
+ <%= render(@mobile_segmented_control, &@mobile_segmented_control_block) %>
8
+ <% end %>
6
9
  <% if render_mobile_menu? %>
7
10
  <%= render(@mobile_action_menu) do |menu| %>
8
11
  <% menu.with_show_button(icon: :"kebab-horizontal", size: :small, "aria-label": @mobile_menu_label) %>
@@ -141,6 +141,21 @@ module Primer
141
141
  component.new(**system_arguments)
142
142
  },
143
143
  },
144
+ segmented_control: {
145
+ renders: lambda { |**system_arguments, &block|
146
+ deny_tag_argument(**system_arguments)
147
+
148
+ system_arguments = set_action_arguments(system_arguments, scheme: DEFAULT_ACTION_SCHEME)
149
+ mobile_args = system_arguments.delete(:mobile_system_arguments) || {}
150
+ @mobile_segmented_control = Primer::Alpha::SegmentedControl.new(**system_arguments,
151
+ **mobile_args,
152
+ mr: 2,
153
+ display: %i[flex none])
154
+ @mobile_segmented_control_block = block
155
+
156
+ Primer::Alpha::SegmentedControl.new(**system_arguments)
157
+ },
158
+ },
144
159
  }
145
160
 
146
161
  # Optional leading action prepend the title
@@ -252,7 +267,7 @@ module Primer
252
267
 
253
268
  def set_action_arguments(system_arguments, scheme: nil)
254
269
  system_arguments[:ml] ||= 2
255
- system_arguments[:display] = [:none, :flex]
270
+ system_arguments[:display] = %i[none flex]
256
271
  system_arguments[:size] = :medium
257
272
  system_arguments[:scheme] = scheme unless scheme.nil?
258
273
  system_arguments[:classes] = class_names(
@@ -12,6 +12,7 @@
12
12
  @import "./alpha/button_marketing.pcss";
13
13
  @import "./alpha/toggle_switch.pcss";
14
14
  @import "./alpha/underline_nav.pcss";
15
+ @import "./alpha/underline_panels.pcss";
15
16
  @import "./alpha/segmented_control.pcss";
16
17
  @import "./alpha/menu.pcss";
17
18