openproject-primer_view_components 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/app/assets/javascripts/app/components/primer/primer.d.ts +0 -1
  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_bar_element.js +2 -0
  9. data/app/components/primer/alpha/action_bar_element.ts +2 -0
  10. data/app/components/primer/alpha/action_menu/action_menu_element.js +20 -3
  11. data/app/components/primer/alpha/action_menu/action_menu_element.ts +27 -1
  12. data/app/components/primer/alpha/overlay.css +1 -1
  13. data/app/components/primer/alpha/overlay.css.json +0 -1
  14. data/app/components/primer/alpha/overlay.css.map +1 -1
  15. data/app/components/primer/alpha/overlay.pcss +0 -12
  16. data/app/components/primer/alpha/tool_tip.js +76 -3
  17. data/app/components/primer/alpha/tool_tip.ts +76 -3
  18. data/app/components/primer/open_project/page_header.css +1 -1
  19. data/app/components/primer/open_project/page_header.css.json +4 -1
  20. data/app/components/primer/open_project/page_header.css.map +1 -1
  21. data/app/components/primer/open_project/page_header.html.erb +6 -2
  22. data/app/components/primer/open_project/page_header.pcss +19 -6
  23. data/app/components/primer/open_project/page_header.rb +70 -0
  24. data/app/components/primer/primer.d.ts +0 -1
  25. data/app/components/primer/primer.js +0 -1
  26. data/app/components/primer/primer.pcss +0 -2
  27. data/app/components/primer/primer.ts +0 -1
  28. data/lib/primer/accessibility.rb +1 -3
  29. data/lib/primer/static/generate_info_arch.rb +6 -1
  30. data/lib/primer/view_components/version.rb +1 -1
  31. data/previews/primer/alpha/check_box_preview.rb +0 -3
  32. data/previews/primer/alpha/dialog_preview/with_text_input.html.erb +2 -1
  33. data/previews/primer/alpha/radio_button_preview.rb +0 -3
  34. data/previews/primer/open_project/page_header_preview.rb +39 -1
  35. data/static/classes.json +9 -0
  36. data/static/constants.json +12 -0
  37. data/static/info_arch.json +243 -8
  38. data/static/previews.json +198 -0
  39. metadata +2 -2
@@ -101,6 +101,8 @@ let ActionBarElement = class ActionBarElement extends HTMLElement {
101
101
  };
102
102
  _ActionBarElement_initialBarWidth = new WeakMap(), _ActionBarElement_previousBarWidth = new WeakMap(), _ActionBarElement_focusZoneAbortController = new WeakMap(), _ActionBarElement_instances = new WeakSet(), _ActionBarElement_isVisible = function _ActionBarElement_isVisible(element) {
103
103
  // Safari doesn't support `checkVisibility` yet.
104
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
105
+ // @ts-ignore
104
106
  if (typeof element.checkVisibility === 'function')
105
107
  return element.checkVisibility();
106
108
  return Boolean(element.offsetParent || element.offsetWidth || element.offsetHeight);
@@ -93,6 +93,8 @@ class ActionBarElement extends HTMLElement {
93
93
 
94
94
  #isVisible(element: HTMLElement): boolean {
95
95
  // Safari doesn't support `checkVisibility` yet.
96
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
97
+ // @ts-ignore
96
98
  if (typeof element.checkVisibility === 'function') return element.checkVisibility()
97
99
 
98
100
  return Boolean(element.offsetParent || element.offsetWidth || element.offsetHeight)
@@ -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_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, _ActionMenuElement_items_get;
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, _ActionMenuElement_items_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"]'];
@@ -109,7 +109,7 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
109
109
  __classPrivateFieldGet(this, _ActionMenuElement_abortController, "f").abort();
110
110
  }
111
111
  handleEvent(event) {
112
- var _a;
112
+ var _a, _b;
113
113
  const targetIsInvoker = (_a = this.invokerElement) === null || _a === void 0 ? void 0 : _a.contains(event.target);
114
114
  const eventIsActivation = __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isActivation).call(this, event);
115
115
  if (targetIsInvoker && event.type === 'mousedown') {
@@ -151,6 +151,16 @@ let ActionMenuElement = class ActionMenuElement extends HTMLElement {
151
151
  }
152
152
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_activateItem).call(this, event, item);
153
153
  __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_handleItemActivated).call(this, event, item);
154
+ // Pressing the space key on a button or link will cause the page to scroll unless preventDefault()
155
+ // is called. While calling preventDefault() appears to have no effect on link navigation, it skips
156
+ // form submission. The code below therefore only calls preventDefault() if the button has been
157
+ // activated by the space key, and manually submits the form if the button is a submit button.
158
+ if (__classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivationViaSpace).call(this, event)) {
159
+ event.preventDefault();
160
+ if (item.getAttribute('type') === 'submit') {
161
+ (_b = item.closest('form')) === null || _b === void 0 ? void 0 : _b.submit();
162
+ }
163
+ }
154
164
  return;
155
165
  }
156
166
  if (event.type === 'include-fragment-replaced') {
@@ -176,10 +186,17 @@ _ActionMenuElement_abortController = new WeakMap(), _ActionMenuElement_originalL
176
186
  event.stopImmediatePropagation();
177
187
  }
178
188
  }, _ActionMenuElement_isKeyboardActivation = function _ActionMenuElement_isKeyboardActivation(event) {
189
+ return __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivationViaEnter).call(this, event) || __classPrivateFieldGet(this, _ActionMenuElement_instances, "m", _ActionMenuElement_isKeyboardActivationViaSpace).call(this, event);
190
+ }, _ActionMenuElement_isKeyboardActivationViaEnter = function _ActionMenuElement_isKeyboardActivationViaEnter(event) {
191
+ return (event instanceof KeyboardEvent &&
192
+ event.type === 'keydown' &&
193
+ !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
194
+ event.key === 'Enter');
195
+ }, _ActionMenuElement_isKeyboardActivationViaSpace = function _ActionMenuElement_isKeyboardActivationViaSpace(event) {
179
196
  return (event instanceof KeyboardEvent &&
180
197
  event.type === 'keydown' &&
181
198
  !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
182
- (event.key === 'Enter' || event.key === ' '));
199
+ event.key === ' ');
183
200
  }, _ActionMenuElement_isMouseActivation = function _ActionMenuElement_isMouseActivation(event) {
184
201
  return event instanceof MouseEvent && event.type === 'click';
185
202
  }, _ActionMenuElement_isActivation = function _ActionMenuElement_isActivation(event) {
@@ -134,11 +134,24 @@ export class ActionMenuElement extends HTMLElement {
134
134
  }
135
135
 
136
136
  #isKeyboardActivation(event: Event): boolean {
137
+ return this.#isKeyboardActivationViaEnter(event) || this.#isKeyboardActivationViaSpace(event)
138
+ }
139
+
140
+ #isKeyboardActivationViaEnter(event: Event): boolean {
141
+ return (
142
+ event instanceof KeyboardEvent &&
143
+ event.type === 'keydown' &&
144
+ !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
145
+ event.key === 'Enter'
146
+ )
147
+ }
148
+
149
+ #isKeyboardActivationViaSpace(event: Event): boolean {
137
150
  return (
138
151
  event instanceof KeyboardEvent &&
139
152
  event.type === 'keydown' &&
140
153
  !(event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) &&
141
- (event.key === 'Enter' || event.key === ' ')
154
+ event.key === ' '
142
155
  )
143
156
  }
144
157
 
@@ -202,6 +215,19 @@ export class ActionMenuElement extends HTMLElement {
202
215
 
203
216
  this.#activateItem(event, item)
204
217
  this.#handleItemActivated(event, item)
218
+
219
+ // Pressing the space key on a button or link will cause the page to scroll unless preventDefault()
220
+ // is called. While calling preventDefault() appears to have no effect on link navigation, it skips
221
+ // form submission. The code below therefore only calls preventDefault() if the button has been
222
+ // activated by the space key, and manually submits the form if the button is a submit button.
223
+ if (this.#isKeyboardActivationViaSpace(event)) {
224
+ event.preventDefault()
225
+
226
+ if (item.getAttribute('type') === 'submit') {
227
+ item.closest('form')?.submit()
228
+ }
229
+ }
230
+
205
231
  return
206
232
  }
207
233
 
@@ -1 +1 @@
1
- anchored-position[popover]{background:none;border-width:0;inset:auto;min-width:192px;overflow:visible;padding:0;position:absolute}.Overlay{display:flex}anchored-position[popover]:not(.\:popover-open){display:none}anchored-position.not-anchored::-webkit-backdrop{background-color:var(--overlay-backdrop-bgColor,var(--color-neutral-muted))}anchored-position.not-anchored::backdrop{background-color:var(--overlay-backdrop-bgColor,var(--color-neutral-muted))}@supports selector(:popover-open){anchored-position[popover]:not(.\:popover-open){display:revert}}
1
+ anchored-position[popover]{background:none;border-width:0;min-width:192px;overflow:visible;padding:0;position:absolute}.Overlay{display:flex}anchored-position.not-anchored::-webkit-backdrop{background-color:var(--overlay-backdrop-bgColor,var(--color-neutral-muted))}anchored-position.not-anchored::backdrop{background-color:var(--overlay-backdrop-bgColor,var(--color-neutral-muted))}
@@ -3,7 +3,6 @@
3
3
  "selectors": [
4
4
  "anchored-position[popover]",
5
5
  ".Overlay",
6
- "anchored-position[popover]:not(.\\:popover-open)",
7
6
  "anchored-position.not-anchored::-webkit-backdrop",
8
7
  "anchored-position.not-anchored::backdrop"
9
8
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["overlay.pcss"],"names":[],"mappings":"AAAA,2BAOE,eAAgB,CANhB,cAAe,CAIf,UAAW,CADX,eAAgB,CAEhB,gBAAiB,CAJjB,SAAU,CACV,iBAKF,CAEA,SACE,YACF,CAEA,gDACE,YACF,CAEA,iDACE,2EACF,CAFA,yCACE,2EACF,CAGA,kCACE,gDACE,cACF,CACF","file":"overlay.css","sourcesContent":["anchored-position[popover] {\n border-width: 0;\n padding: 0;\n position: absolute;\n min-width: 192px;\n inset: auto;\n overflow: visible;\n background: none;\n}\n\n.Overlay {\n display: flex;\n}\n\nanchored-position[popover]:not(.\\:popover-open) {\n display: none;\n}\n\nanchored-position.not-anchored::backdrop {\n background-color: var(--overlay-backdrop-bgColor, var(--color-neutral-muted));\n}\n\n/* This reverts the declaration above for native popover, where `:popover-open` is supported */\n@supports selector(:popover-open) {\n anchored-position[popover]:not(.\\:popover-open) {\n display: revert;\n }\n}\n"]}
1
+ {"version":3,"sources":["overlay.pcss"],"names":[],"mappings":"AAAA,2BAME,eAAgB,CALhB,cAAe,CAGf,eAAgB,CAChB,gBAAiB,CAHjB,SAAU,CACV,iBAIF,CAEA,SACE,YACF,CAEA,iDACE,2EACF,CAFA,yCACE,2EACF","file":"overlay.css","sourcesContent":["anchored-position[popover] {\n border-width: 0;\n padding: 0;\n position: absolute;\n min-width: 192px;\n overflow: visible;\n background: none;\n}\n\n.Overlay {\n display: flex;\n}\n\nanchored-position.not-anchored::backdrop {\n background-color: var(--overlay-backdrop-bgColor, var(--color-neutral-muted));\n}\n"]}
@@ -3,7 +3,6 @@ anchored-position[popover] {
3
3
  padding: 0;
4
4
  position: absolute;
5
5
  min-width: 192px;
6
- inset: auto;
7
6
  overflow: visible;
8
7
  background: none;
9
8
  }
@@ -12,17 +11,6 @@ anchored-position[popover] {
12
11
  display: flex;
13
12
  }
14
13
 
15
- anchored-position[popover]:not(.\:popover-open) {
16
- display: none;
17
- }
18
-
19
14
  anchored-position.not-anchored::backdrop {
20
15
  background-color: var(--overlay-backdrop-bgColor, var(--color-neutral-muted));
21
16
  }
22
-
23
- /* This reverts the declaration above for native popover, where `:popover-open` is supported */
24
- @supports selector(:popover-open) {
25
- anchored-position[popover]:not(.\:popover-open) {
26
- display: revert;
27
- }
28
- }
@@ -32,8 +32,9 @@ const isPopoverOpen = (() => {
32
32
  }
33
33
  return (el) => (selector ? el.matches(selector) : setSelector(el));
34
34
  })();
35
+ const TOOLTIP_ARROW_EDGE_OFFSET = 6;
35
36
  const TOOLTIP_SR_ONLY_CLASS = 'sr-only';
36
- const TOOLTIP_OFFSET = 4;
37
+ const TOOLTIP_OFFSET = 10;
37
38
  const DIRECTION_CLASSES = [
38
39
  'tooltip-n',
39
40
  'tooltip-s',
@@ -106,15 +107,33 @@ class ToolTipElement extends HTMLElement {
106
107
  text-wrap: balance;
107
108
  }
108
109
 
110
+ :host:before{
111
+ position: absolute;
112
+ z-index: 1000001;
113
+ color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
114
+ content: "";
115
+ border: 6px solid transparent;
116
+ opacity: 0;
117
+ }
118
+
109
119
  @keyframes tooltip-appear {
110
120
  from {
111
121
  opacity: 0;
112
122
  }
113
123
  to {
114
- opacity: 1
124
+ opacity: 1;
115
125
  }
116
126
  }
117
127
 
128
+ :host:after{
129
+ position: absolute;
130
+ display: block;
131
+ right: 0;
132
+ left: 0;
133
+ height: 12px;
134
+ content: "";
135
+ }
136
+
118
137
  :host(:popover-open),
119
138
  :host(:popover-open):before {
120
139
  animation-name: tooltip-appear;
@@ -123,11 +142,65 @@ class ToolTipElement extends HTMLElement {
123
142
  animation-timing-function: ease-in;
124
143
  }
125
144
 
126
- :host(.\\:popover-open) {
145
+ :host(.\\:popover-open),
146
+ :host(.\\:popover-open):before {
127
147
  animation-name: tooltip-appear;
128
148
  animation-duration: .1s;
129
149
  animation-fill-mode: forwards;
130
150
  animation-timing-function: ease-in;
151
+ animation-delay: .4s;
152
+ }
153
+
154
+ :host(.tooltip-s):before,
155
+ :host(.tooltip-n):before {
156
+ right: 50%;
157
+ margin-right: -${TOOLTIP_ARROW_EDGE_OFFSET}px;
158
+ }
159
+ :host(.tooltip-s):before,
160
+ :host(.tooltip-se):before,
161
+ :host(.tooltip-sw):before {
162
+ bottom: 100%;
163
+ border-bottom-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
164
+ }
165
+ :host(.tooltip-s):after,
166
+ :host(.tooltip-se):after,
167
+ :host(.tooltip-sw):after {
168
+ bottom: 100%
169
+ }
170
+ :host(.tooltip-n):before,
171
+ :host(.tooltip-ne):before,
172
+ :host(.tooltip-nw):before {
173
+ top: 100%;
174
+ border-top-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
175
+ }
176
+ :host(.tooltip-n):after,
177
+ :host(.tooltip-ne):after,
178
+ :host(.tooltip-nw):after {
179
+ top: 100%;
180
+ }
181
+ :host(.tooltip-se):before,
182
+ :host(.tooltip-ne):before {
183
+ left: 0;
184
+ margin-left: ${TOOLTIP_ARROW_EDGE_OFFSET}px;
185
+ }
186
+ :host(.tooltip-sw):before,
187
+ :host(.tooltip-nw):before {
188
+ right: 0;
189
+ margin-right: ${TOOLTIP_ARROW_EDGE_OFFSET}px;
190
+ }
191
+ :host(.tooltip-w):before {
192
+ top: 50%;
193
+ bottom: 50%;
194
+ left: 100%;
195
+ margin-top: -6px;
196
+ border-left-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
197
+ }
198
+ :host(.tooltip-e):before {
199
+ top: 50%;
200
+ right: 100%;
201
+ bottom: 50%;
202
+ margin-top: -6px;
203
+ border-right-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
131
204
  }
132
205
  `;
133
206
  }
@@ -21,8 +21,9 @@ const isPopoverOpen = (() => {
21
21
  return (el: Element) => (selector ? el.matches(selector) : setSelector(el))
22
22
  })()
23
23
 
24
+ const TOOLTIP_ARROW_EDGE_OFFSET = 6
24
25
  const TOOLTIP_SR_ONLY_CLASS = 'sr-only'
25
- const TOOLTIP_OFFSET = 4
26
+ const TOOLTIP_OFFSET = 10
26
27
 
27
28
  type Direction = 'n' | 's' | 'e' | 'w' | 'ne' | 'se' | 'nw' | 'sw'
28
29
 
@@ -91,15 +92,33 @@ class ToolTipElement extends HTMLElement {
91
92
  text-wrap: balance;
92
93
  }
93
94
 
95
+ :host:before{
96
+ position: absolute;
97
+ z-index: 1000001;
98
+ color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
99
+ content: "";
100
+ border: 6px solid transparent;
101
+ opacity: 0;
102
+ }
103
+
94
104
  @keyframes tooltip-appear {
95
105
  from {
96
106
  opacity: 0;
97
107
  }
98
108
  to {
99
- opacity: 1
109
+ opacity: 1;
100
110
  }
101
111
  }
102
112
 
113
+ :host:after{
114
+ position: absolute;
115
+ display: block;
116
+ right: 0;
117
+ left: 0;
118
+ height: 12px;
119
+ content: "";
120
+ }
121
+
103
122
  :host(:popover-open),
104
123
  :host(:popover-open):before {
105
124
  animation-name: tooltip-appear;
@@ -108,11 +127,65 @@ class ToolTipElement extends HTMLElement {
108
127
  animation-timing-function: ease-in;
109
128
  }
110
129
 
111
- :host(.\\:popover-open) {
130
+ :host(.\\:popover-open),
131
+ :host(.\\:popover-open):before {
112
132
  animation-name: tooltip-appear;
113
133
  animation-duration: .1s;
114
134
  animation-fill-mode: forwards;
115
135
  animation-timing-function: ease-in;
136
+ animation-delay: .4s;
137
+ }
138
+
139
+ :host(.tooltip-s):before,
140
+ :host(.tooltip-n):before {
141
+ right: 50%;
142
+ margin-right: -${TOOLTIP_ARROW_EDGE_OFFSET}px;
143
+ }
144
+ :host(.tooltip-s):before,
145
+ :host(.tooltip-se):before,
146
+ :host(.tooltip-sw):before {
147
+ bottom: 100%;
148
+ border-bottom-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
149
+ }
150
+ :host(.tooltip-s):after,
151
+ :host(.tooltip-se):after,
152
+ :host(.tooltip-sw):after {
153
+ bottom: 100%
154
+ }
155
+ :host(.tooltip-n):before,
156
+ :host(.tooltip-ne):before,
157
+ :host(.tooltip-nw):before {
158
+ top: 100%;
159
+ border-top-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
160
+ }
161
+ :host(.tooltip-n):after,
162
+ :host(.tooltip-ne):after,
163
+ :host(.tooltip-nw):after {
164
+ top: 100%;
165
+ }
166
+ :host(.tooltip-se):before,
167
+ :host(.tooltip-ne):before {
168
+ left: 0;
169
+ margin-left: ${TOOLTIP_ARROW_EDGE_OFFSET}px;
170
+ }
171
+ :host(.tooltip-sw):before,
172
+ :host(.tooltip-nw):before {
173
+ right: 0;
174
+ margin-right: ${TOOLTIP_ARROW_EDGE_OFFSET}px;
175
+ }
176
+ :host(.tooltip-w):before {
177
+ top: 50%;
178
+ bottom: 50%;
179
+ left: 100%;
180
+ margin-top: -6px;
181
+ border-left-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
182
+ }
183
+ :host(.tooltip-e):before {
184
+ top: 50%;
185
+ right: 100%;
186
+ bottom: 50%;
187
+ margin-top: -6px;
188
+ border-right-color: var(--bgColor-emphasis, var(--color-neutral-emphasis-plus));
116
189
  }
117
190
  `
118
191
  }
@@ -1 +1 @@
1
- .PageHeader{border-bottom:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-muted,var(--color-border-muted));display:flex;flex-flow:row wrap;justify-content:flex-end;margin-bottom:var(--stack-gap-normal,1rem);padding-bottom:var(--stack-padding-condensed,.5rem)}@media (max-width:767.98px){.PageHeader{border-bottom:0}}.PageHeader-title{flex:1 1 auto;font-size:24px;font-weight:var(--base-text-weight-normal,400);order:0}.PageHeader-title--large{font-size:var(--text-title-size-large,2rem)}.PageHeader-description{color:var(--fgColor-muted,var(--color-fg-muted));flex:1 100%;font-size:var(--text-body-size-medium,.875rem);order:2}.PageHeader-actions{align-self:center;justify-content:flex-end;margin:var(--base-size-4,.25rem) 0 var(--base-size-4,.25rem) var(--base-size-4,.25rem);order:1}.PageHeader-actions+.PageHeader-description{margin-top:var(--base-size-4,.25rem)}
1
+ .PageHeader{border-bottom:var(--borderWidth-thin,max(1px,.0625rem)) solid var(--borderColor-muted,var(--color-border-muted));display:flex;flex-flow:column;margin-bottom:var(--stack-gap-normal,1rem);padding-bottom:var(--stack-padding-condensed,.5rem)}@media (max-width:767.98px){.PageHeader{border-bottom:0}}.PageHeader-titleBar{align-items:center;display:flex;flex-flow:row;justify-content:flex-end}.PageHeader-title{flex:1 1 auto;font-size:24px;font-weight:var(--base-text-weight-normal,400)}.PageHeader-title--large{font-size:var(--text-title-size-large,2rem)}.PageHeader-description{color:var(--fgColor-muted,var(--color-fg-muted));flex:1 100%;font-size:var(--text-body-size-medium,.875rem)}.PageHeader-actions{justify-content:flex-end;margin:var(--base-size-4,.25rem) 0 var(--base-size-4,.25rem) var(--base-size-4,.25rem)}.PageHeader-actions+.PageHeader-description{margin-top:var(--base-size-4,.25rem)}.PageHeader-breadcrumbs{display:block;margin-bottom:var(--base-size-8,.5rem);width:100%}.PageHeader-backButton{margin-right:var(--base-size-4,.25rem);margin-top:2px}
@@ -2,10 +2,13 @@
2
2
  "name": "open_project/page_header",
3
3
  "selectors": [
4
4
  ".PageHeader",
5
+ ".PageHeader-titleBar",
5
6
  ".PageHeader-title",
6
7
  ".PageHeader-title--large",
7
8
  ".PageHeader-description",
8
9
  ".PageHeader-actions",
9
- ".PageHeader-actions+.PageHeader-description"
10
+ ".PageHeader-actions+.PageHeader-description",
11
+ ".PageHeader-breadcrumbs",
12
+ ".PageHeader-backButton"
10
13
  ]
11
14
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["page_header.pcss"],"names":[],"mappings":"AAEA,YAIE,gHAAqE,CAHrE,YAAa,CAIb,kBAAmB,CACnB,wBAAyB,CAHzB,0CAAsC,CADtC,mDASF,CAHE,4BARF,YASI,eAEJ,CADE,CAGF,kBAGE,aAAc,CAFd,cAAe,CACf,8CAA2C,CAE3C,OACF,CAEA,yBACE,2CACF,CAGA,wBAEE,gDAA2B,CAC3B,WAAY,CAFZ,8CAAuC,CAGvC,OACF,CAGA,oBAEE,iBAAkB,CAClB,wBAAyB,CAFzB,sFAAkE,CAGlE,OAKF,CAHE,4CACE,oCACF","file":"page_header.css","sourcesContent":["/* OP PageHeader */\n\n.PageHeader {\n display: flex;\n padding-bottom: var(--stack-padding-condensed);\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);\n flex-flow: row wrap;\n justify-content: flex-end; /* Keep actions right aligned. */\n\n @media (max-width: 767.98px) {\n border-bottom: 0;\n }\n}\n\n.PageHeader-title {\n font-size: 24px;\n font-weight: var(--base-text-weight-normal);\n flex: 1 1 auto;\n order: 0;\n}\n\n.PageHeader-title--large {\n font-size: var(--text-title-size-large);\n}\n\n/* One-liner of supporting text */\n.PageHeader-description {\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-muted);\n flex: 1 100%;\n order: 2;\n}\n\n/* Add 1 or 2 buttons to the right of the heading */\n.PageHeader-actions {\n margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);\n align-self: center;\n justify-content: flex-end;\n order: 1;\n\n & + .PageHeader-description {\n margin-top: var(--base-size-4);\n }\n}\n"]}
1
+ {"version":3,"sources":["page_header.pcss"],"names":[],"mappings":"AAEA,YAIE,gHAAqE,CAHrE,YAAa,CAIb,gBAAiB,CAFjB,0CAAsC,CADtC,mDAQF,CAHE,4BAPF,YAQI,eAEJ,CADE,CAGF,qBAIE,kBAAmB,CAHnB,YAAa,CACb,aAAc,CACd,wBAEF,CAEA,kBAGE,aAAc,CAFd,cAAe,CACf,8CAEF,CAEA,yBACE,2CACF,CAGA,wBAEE,gDAA2B,CAC3B,WAAY,CAFZ,8CAGF,CAGA,oBAEE,wBAAyB,CADzB,sFAMF,CAHE,4CACE,oCACF,CAGF,wBACE,aAAc,CAEd,sCAAiC,CADjC,UAEF,CAEA,uBAEE,sCAAgC,CADhC,cAEF","file":"page_header.css","sourcesContent":["/* OP PageHeader */\n\n.PageHeader {\n display: flex;\n padding-bottom: var(--stack-padding-condensed);\n margin-bottom: var(--stack-gap-normal);\n border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);\n flex-flow: column;\n\n @media (max-width: 767.98px) {\n border-bottom: 0;\n }\n}\n\n.PageHeader-titleBar {\n display: flex;\n flex-flow: row;\n justify-content: flex-end;\n align-items: center; /* Keep back button vertically aligned. */\n}\n\n.PageHeader-title {\n font-size: 24px;\n font-weight: var(--base-text-weight-normal);\n flex: 1 1 auto;\n}\n\n.PageHeader-title--large {\n font-size: var(--text-title-size-large);\n}\n\n/* One-liner of supporting text */\n.PageHeader-description {\n font-size: var(--text-body-size-medium);\n color: var(--fgColor-muted);\n flex: 1 100%;\n}\n\n/* Add 1 or 2 buttons to the right of the heading */\n.PageHeader-actions {\n margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);\n justify-content: flex-end;\n\n & + .PageHeader-description {\n margin-top: var(--base-size-4);\n }\n}\n\n.PageHeader-breadcrumbs {\n display: block;\n width: 100%;\n margin-bottom: var(--base-size-8);\n}\n\n.PageHeader-backButton {\n margin-top: 2px; /* to center align with label */\n margin-right: var(--base-size-4);\n}\n"]}
@@ -1,5 +1,9 @@
1
1
  <%= render Primer::BaseComponent.new(**@system_arguments) do %>
2
- <%= title %>
2
+ <%= breadcrumbs %>
3
+ <div class="PageHeader-titleBar">
4
+ <%= back_button %>
5
+ <%= title %>
6
+ <%= actions %>
7
+ </div>
3
8
  <%= description %>
4
- <%= actions %>
5
9
  <% end %>
@@ -5,19 +5,24 @@
5
5
  padding-bottom: var(--stack-padding-condensed);
6
6
  margin-bottom: var(--stack-gap-normal);
7
7
  border-bottom: var(--borderWidth-thin) solid var(--borderColor-muted);
8
- flex-flow: row wrap;
9
- justify-content: flex-end; /* Keep actions right aligned. */
8
+ flex-flow: column;
10
9
 
11
10
  @media (max-width: 767.98px) {
12
11
  border-bottom: 0;
13
12
  }
14
13
  }
15
14
 
15
+ .PageHeader-titleBar {
16
+ display: flex;
17
+ flex-flow: row;
18
+ justify-content: flex-end;
19
+ align-items: center; /* Keep back button vertically aligned. */
20
+ }
21
+
16
22
  .PageHeader-title {
17
23
  font-size: 24px;
18
24
  font-weight: var(--base-text-weight-normal);
19
25
  flex: 1 1 auto;
20
- order: 0;
21
26
  }
22
27
 
23
28
  .PageHeader-title--large {
@@ -29,17 +34,25 @@
29
34
  font-size: var(--text-body-size-medium);
30
35
  color: var(--fgColor-muted);
31
36
  flex: 1 100%;
32
- order: 2;
33
37
  }
34
38
 
35
39
  /* Add 1 or 2 buttons to the right of the heading */
36
40
  .PageHeader-actions {
37
41
  margin: var(--base-size-4) 0 var(--base-size-4) var(--base-size-4);
38
- align-self: center;
39
42
  justify-content: flex-end;
40
- order: 1;
41
43
 
42
44
  & + .PageHeader-description {
43
45
  margin-top: var(--base-size-4);
44
46
  }
45
47
  }
48
+
49
+ .PageHeader-breadcrumbs {
50
+ display: block;
51
+ width: 100%;
52
+ margin-bottom: var(--base-size-8);
53
+ }
54
+
55
+ .PageHeader-backButton {
56
+ margin-top: 2px; /* to center align with label */
57
+ margin-right: var(--base-size-4);
58
+ }
@@ -13,6 +13,20 @@ module Primer
13
13
  DEFAULT_HEADER_VARIANT
14
14
  ].freeze
15
15
 
16
+ DEFAULT_BACK_BUTTON_SIZE = :medium
17
+ BACK_BUTTON_SIZE_OPTIONS = [
18
+ :small,
19
+ DEFAULT_HEADER_VARIANT,
20
+ :large
21
+ ].freeze
22
+
23
+ DEFAULT_BACK_BUTTON_ICON = "arrow-left"
24
+ BACK_BUTTON_ICON_OPTIONS = [
25
+ DEFAULT_BACK_BUTTON_ICON,
26
+ "chevron-left",
27
+ "triangle-left"
28
+ ].freeze
29
+
16
30
  status :open_project
17
31
 
18
32
  # The title of the page header
@@ -50,6 +64,45 @@ module Primer
50
64
  Primer::BaseComponent.new(**system_arguments)
51
65
  }
52
66
 
67
+ # Optional back button prepend the title
68
+ #
69
+ # @param size [Symbol] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_SIZE_OPTIONS) %>
70
+ # @param icon [String] <%= one_of(Primer::OpenProject::PageHeader::BACK_BUTTON_ICON_OPTIONS) %>
71
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
72
+ renders_one :back_button, lambda { |
73
+ size: DEFAULT_BACK_BUTTON_SIZE,
74
+ icon: DEFAULT_BACK_BUTTON_ICON,
75
+ **system_arguments
76
+ |
77
+ deny_tag_argument(**system_arguments)
78
+ system_arguments[:tag] = :a
79
+ system_arguments[:scheme] = :invisible
80
+ system_arguments[:size] = fetch_or_fallback(BACK_BUTTON_SIZE_OPTIONS, size, DEFAULT_BACK_BUTTON_SIZE)
81
+ system_arguments[:icon] = fetch_or_fallback(BACK_BUTTON_ICON_OPTIONS, icon, DEFAULT_BACK_BUTTON_ICON)
82
+ system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-backButton")
83
+
84
+ Primer::Beta::IconButton.new(**system_arguments)
85
+ }
86
+
87
+ # Optional breadcrumbs above the title row
88
+ #
89
+ # @param items [Array<String, Hash>] Items is an array of strings, hash {href, text} or an anchor tag string
90
+ # @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
91
+ renders_one :breadcrumbs, lambda { |items, **system_arguments|
92
+ system_arguments[:classes] = class_names(system_arguments[:classes], "PageHeader-breadcrumbs")
93
+ render(Primer::Beta::Breadcrumbs.new(**system_arguments)) do |breadcrumbs|
94
+ items.each do |item|
95
+ item = anchor_string_to_object(item) if anchor_tag_string?(item)
96
+
97
+ if item.is_a?(String)
98
+ breadcrumbs.with_item(href: "#") { item }
99
+ else
100
+ breadcrumbs.with_item(href: item[:href]) { item[:text] }
101
+ end
102
+ end
103
+ end
104
+ }
105
+
53
106
  def initialize(**system_arguments)
54
107
  @system_arguments = deny_tag_argument(**system_arguments)
55
108
 
@@ -64,6 +117,23 @@ module Primer
64
117
  def render?
65
118
  title?
66
119
  end
120
+
121
+ private
122
+
123
+ # transform anchor tag strings to {href, text} objects
124
+ # e.g "\u003ca href=\"/admin\"\u003eAdministration\u003c/a\u003e"
125
+ def anchor_string_to_object(html_string)
126
+ # Parse the HTML
127
+ doc = Nokogiri::HTML.fragment(html_string)
128
+ # Extract href and text
129
+ anchor = doc.at("a")
130
+ { href: anchor["href"], text: anchor.text }
131
+ end
132
+
133
+ # Check if the item is an anchor tag string e.g "\u003ca href=\"/admin\"\u003eAdministration\u003c/a\u003e"
134
+ def anchor_tag_string?(item)
135
+ item.is_a?(String) && item.start_with?("\u003c")
136
+ end
67
137
  end
68
138
  end
69
139
  end
@@ -1,5 +1,4 @@
1
1
  import '@github/include-fragment-element';
2
- import '@oddbird/popover-polyfill';
3
2
  import './alpha/action_bar_element';
4
3
  import './alpha/dropdown';
5
4
  import './anchored_position';
@@ -1,5 +1,4 @@
1
1
  import '@github/include-fragment-element';
2
- import '@oddbird/popover-polyfill';
3
2
  import './alpha/action_bar_element';
4
3
  import './alpha/dropdown';
5
4
  import './anchored_position';
@@ -1,5 +1,3 @@
1
- @import "@oddbird/popover-polyfill/dist/popover.css";
2
-
3
1
  /* CSS component styles here */
4
2
 
5
3
  /* alpha */
@@ -1,5 +1,4 @@
1
1
  import '@github/include-fragment-element'
2
- import '@oddbird/popover-polyfill'
3
2
  import './alpha/action_bar_element'
4
3
  import './alpha/dropdown'
5
4
  import './anchored_position'
@@ -8,9 +8,7 @@ module Primer
8
8
  # Do not add to this list for any other reason!
9
9
  IGNORED_PREVIEWS = [
10
10
  Primer::Beta::MarkdownPreview,
11
- Primer::Beta::AutoCompleteItemPreview,
12
- Primer::Alpha::RadioButtonPreview,
13
- Primer::Alpha::CheckBoxPreview
11
+ Primer::Beta::AutoCompleteItemPreview
14
12
  ].freeze
15
13
 
16
14
  # Skip `:region` which relates to preview page structure rather than individual component.
@@ -70,10 +70,15 @@ module Primer
70
70
  render_erb_ignoring_markdown_code_fences(docs.base_docstring)
71
71
  end
72
72
 
73
+ accessibility_docs =
74
+ if (accessibility_tag_text = docs.tags(:accessibility)&.first&.text)
75
+ render_erb_ignoring_markdown_code_fences(accessibility_tag_text)
76
+ end
77
+
73
78
  memo[component] = {
74
79
  "fully_qualified_name" => component.name,
75
80
  "description" => description,
76
- "accessibility_docs" => docs.tags(:accessibility)&.first&.text,
81
+ "accessibility_docs" => accessibility_docs,
77
82
  "is_form_component" => docs.manifest_entry.form_component?,
78
83
  "is_published" => docs.manifest_entry.published?,
79
84
  "requires_js" => docs.manifest_entry.requires_js?,
@@ -5,7 +5,7 @@ module Primer
5
5
  module ViewComponents
6
6
  module VERSION
7
7
  MAJOR = 0
8
- MINOR = 13
8
+ MINOR = 14
9
9
  PATCH = 0
10
10
 
11
11
  STRING = [MAJOR, MINOR, PATCH].join(".")