openproject-primer_view_components 0.13.0 → 0.14.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 (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(".")