primer_view_components 0.0.91 → 0.0.92
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/app/assets/javascripts/primer_view_components.js +1 -1
- data/app/assets/javascripts/primer_view_components.js.map +1 -1
- data/app/components/primer/alpha/modal-dialog-element.d.ts +18 -0
- data/app/components/primer/alpha/modal-dialog-element.js +143 -0
- data/app/components/primer/alpha/toggle-switch-element.d.ts +28 -0
- data/app/components/primer/alpha/toggle-switch-element.js +135 -0
- data/app/components/primer/alpha/toggle-switch-element.ts +147 -0
- data/app/components/primer/alpha/toggle_switch.html.erb +44 -0
- data/app/components/primer/alpha/toggle_switch.rb +89 -0
- data/app/components/primer/alpha/tool-tip-element.d.ts +2 -0
- data/app/components/primer/alpha/tool-tip-element.js +34 -20
- data/app/components/primer/alpha/tool-tip-element.ts +31 -15
- data/app/components/primer/alpha/tooltip.rb +4 -1
- data/app/components/primer/primer.d.ts +1 -1
- data/app/components/primer/primer.js +1 -1
- data/app/components/primer/primer.ts +1 -0
- data/lib/primer/forms/acts_as_component.rb +1 -1
- data/lib/primer/view_components/linters/helpers/deprecated_components_helpers.rb +0 -2
- data/lib/primer/view_components/version.rb +1 -1
- data/lib/rubocop/cop/primer/component_name_migration.rb +0 -1
- data/static/audited_at.json +1 -2
- data/static/constants.json +20 -4
- data/static/statuses.json +1 -2
- metadata +9 -4
- data/app/components/primer/border_box_component.rb +0 -7
- data/app/components/primer/button_group.rb +0 -7
@@ -0,0 +1,18 @@
|
|
1
|
+
export declare class ModalDialogElement extends HTMLElement {
|
2
|
+
#private;
|
3
|
+
get open(): boolean;
|
4
|
+
set open(value: boolean);
|
5
|
+
get showButtons(): NodeList;
|
6
|
+
connectedCallback(): void;
|
7
|
+
disconnectedCallback(): void;
|
8
|
+
show(): void;
|
9
|
+
close(closed?: boolean): void;
|
10
|
+
}
|
11
|
+
declare global {
|
12
|
+
interface Window {
|
13
|
+
ModalDialogElement: typeof ModalDialogElement;
|
14
|
+
}
|
15
|
+
interface HTMLElementTagNameMap {
|
16
|
+
'modal-dialog': ModalDialogElement;
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,143 @@
|
|
1
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
2
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
3
|
+
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");
|
4
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
5
|
+
};
|
6
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
7
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
10
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
11
|
+
};
|
12
|
+
var _ModalDialogElement_instances, _ModalDialogElement_focusAbortController, _ModalDialogElement_abortController, _ModalDialogElement_openButton, _ModalDialogElement_shouldTryLoadingFragment, _ModalDialogElement_overlayBackdrop_get, _ModalDialogElement_keydown;
|
13
|
+
import { focusTrap } from '@primer/behaviors';
|
14
|
+
import { getFocusableChild } from '@primer/behaviors/utils';
|
15
|
+
function focusIfNeeded(elem) {
|
16
|
+
if (document.activeElement !== elem) {
|
17
|
+
elem === null || elem === void 0 ? void 0 : elem.focus();
|
18
|
+
}
|
19
|
+
}
|
20
|
+
export class ModalDialogElement extends HTMLElement {
|
21
|
+
constructor() {
|
22
|
+
super(...arguments);
|
23
|
+
_ModalDialogElement_instances.add(this);
|
24
|
+
//TODO: Do we remove the abortController from focusTrap?
|
25
|
+
_ModalDialogElement_focusAbortController.set(this, new AbortController());
|
26
|
+
_ModalDialogElement_abortController.set(this, null);
|
27
|
+
_ModalDialogElement_openButton.set(this, void 0);
|
28
|
+
_ModalDialogElement_shouldTryLoadingFragment.set(this, true);
|
29
|
+
}
|
30
|
+
get open() {
|
31
|
+
return this.hasAttribute('open');
|
32
|
+
}
|
33
|
+
set open(value) {
|
34
|
+
var _a, _b, _c, _d;
|
35
|
+
if (value) {
|
36
|
+
if (this.open)
|
37
|
+
return;
|
38
|
+
this.setAttribute('open', '');
|
39
|
+
(_a = __classPrivateFieldGet(this, _ModalDialogElement_instances, "a", _ModalDialogElement_overlayBackdrop_get)) === null || _a === void 0 ? void 0 : _a.classList.remove('Overlay--hidden');
|
40
|
+
document.body.style.overflow = 'hidden';
|
41
|
+
if (__classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").signal.aborted) {
|
42
|
+
__classPrivateFieldSet(this, _ModalDialogElement_focusAbortController, new AbortController(), "f");
|
43
|
+
}
|
44
|
+
focusTrap(this, undefined, __classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").signal);
|
45
|
+
}
|
46
|
+
else {
|
47
|
+
if (!this.open)
|
48
|
+
return;
|
49
|
+
this.removeAttribute('open');
|
50
|
+
(_b = __classPrivateFieldGet(this, _ModalDialogElement_instances, "a", _ModalDialogElement_overlayBackdrop_get)) === null || _b === void 0 ? void 0 : _b.classList.add('Overlay--hidden');
|
51
|
+
document.body.style.overflow = 'initial';
|
52
|
+
__classPrivateFieldGet(this, _ModalDialogElement_focusAbortController, "f").abort();
|
53
|
+
// if #openButton is a child of a menu, we need to focus a suitable child of the menu
|
54
|
+
// element since it is expected for the menu to close on click
|
55
|
+
const menu = ((_c = __classPrivateFieldGet(this, _ModalDialogElement_openButton, "f")) === null || _c === void 0 ? void 0 : _c.closest('details')) || ((_d = __classPrivateFieldGet(this, _ModalDialogElement_openButton, "f")) === null || _d === void 0 ? void 0 : _d.closest('action-menu'));
|
56
|
+
if (menu) {
|
57
|
+
focusIfNeeded(getFocusableChild(menu));
|
58
|
+
}
|
59
|
+
else {
|
60
|
+
focusIfNeeded(__classPrivateFieldGet(this, _ModalDialogElement_openButton, "f"));
|
61
|
+
}
|
62
|
+
__classPrivateFieldSet(this, _ModalDialogElement_openButton, undefined, "f");
|
63
|
+
}
|
64
|
+
}
|
65
|
+
get showButtons() {
|
66
|
+
// Dialogs may also be opened from any arbitrary button with a matching show-dialog-id data attribute
|
67
|
+
return document.querySelectorAll(`button[data-show-dialog-id='${this.id}']`);
|
68
|
+
}
|
69
|
+
connectedCallback() {
|
70
|
+
if (!this.hasAttribute('role'))
|
71
|
+
this.setAttribute('role', 'dialog');
|
72
|
+
const { signal } = (__classPrivateFieldSet(this, _ModalDialogElement_abortController, new AbortController(), "f"));
|
73
|
+
this.ownerDocument.addEventListener('click', event => {
|
74
|
+
const target = event.target;
|
75
|
+
const clickOutsideDialog = target.closest(this.tagName) !== this;
|
76
|
+
const button = target === null || target === void 0 ? void 0 : target.closest('button');
|
77
|
+
// go over this logic:
|
78
|
+
if (!button) {
|
79
|
+
if (clickOutsideDialog) {
|
80
|
+
// This click is outside the dialog
|
81
|
+
this.close();
|
82
|
+
}
|
83
|
+
return;
|
84
|
+
}
|
85
|
+
let dialogId = button.getAttribute('data-close-dialog-id');
|
86
|
+
if (dialogId === this.id) {
|
87
|
+
this.close();
|
88
|
+
}
|
89
|
+
dialogId = button.getAttribute('data-submit-dialog-id');
|
90
|
+
if (dialogId === this.id) {
|
91
|
+
this.close(true);
|
92
|
+
}
|
93
|
+
dialogId = button.getAttribute('data-show-dialog-id');
|
94
|
+
if (dialogId === this.id) {
|
95
|
+
//TODO: see if I can remove this
|
96
|
+
event.stopPropagation();
|
97
|
+
__classPrivateFieldSet(this, _ModalDialogElement_openButton, button, "f");
|
98
|
+
this.show();
|
99
|
+
}
|
100
|
+
}, { signal });
|
101
|
+
this.addEventListener('keydown', e => __classPrivateFieldGet(this, _ModalDialogElement_instances, "m", _ModalDialogElement_keydown).call(this, e));
|
102
|
+
}
|
103
|
+
disconnectedCallback() {
|
104
|
+
var _a;
|
105
|
+
(_a = __classPrivateFieldGet(this, _ModalDialogElement_abortController, "f")) === null || _a === void 0 ? void 0 : _a.abort();
|
106
|
+
}
|
107
|
+
show() {
|
108
|
+
this.open = true;
|
109
|
+
}
|
110
|
+
close(closed = false) {
|
111
|
+
if (this.open === false)
|
112
|
+
return;
|
113
|
+
const eventType = closed ? 'close' : 'cancel';
|
114
|
+
const dialogEvent = new Event(eventType);
|
115
|
+
this.dispatchEvent(dialogEvent);
|
116
|
+
this.open = false;
|
117
|
+
}
|
118
|
+
}
|
119
|
+
_ModalDialogElement_focusAbortController = new WeakMap(), _ModalDialogElement_abortController = new WeakMap(), _ModalDialogElement_openButton = new WeakMap(), _ModalDialogElement_shouldTryLoadingFragment = new WeakMap(), _ModalDialogElement_instances = new WeakSet(), _ModalDialogElement_overlayBackdrop_get = function _ModalDialogElement_overlayBackdrop_get() {
|
120
|
+
var _a;
|
121
|
+
if ((_a = this.parentElement) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-modal-dialog-overlay')) {
|
122
|
+
return this.parentElement;
|
123
|
+
}
|
124
|
+
return null;
|
125
|
+
}, _ModalDialogElement_keydown = function _ModalDialogElement_keydown(event) {
|
126
|
+
if (!(event instanceof KeyboardEvent))
|
127
|
+
return;
|
128
|
+
if (event.isComposing)
|
129
|
+
return;
|
130
|
+
switch (event.key) {
|
131
|
+
case 'Escape':
|
132
|
+
if (this.open) {
|
133
|
+
this.close();
|
134
|
+
event.preventDefault();
|
135
|
+
event.stopPropagation();
|
136
|
+
}
|
137
|
+
break;
|
138
|
+
}
|
139
|
+
};
|
140
|
+
if (!window.customElements.get('modal-dialog')) {
|
141
|
+
window.ModalDialogElement = ModalDialogElement;
|
142
|
+
window.customElements.define('modal-dialog', ModalDialogElement);
|
143
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
export declare class ToggleSwitchElement extends HTMLElement {
|
2
|
+
switch: HTMLElement;
|
3
|
+
loadingSpinner: HTMLElement;
|
4
|
+
errorIcon: HTMLElement;
|
5
|
+
get src(): string | null;
|
6
|
+
get csrf(): string | null;
|
7
|
+
get csrfField(): string;
|
8
|
+
isRemote(): boolean;
|
9
|
+
toggle(): void;
|
10
|
+
turnOn(): void;
|
11
|
+
turnOff(): void;
|
12
|
+
isOn(): boolean;
|
13
|
+
isOff(): boolean;
|
14
|
+
isDisabled(): boolean;
|
15
|
+
disable(): void;
|
16
|
+
enable(): void;
|
17
|
+
private performToggle;
|
18
|
+
private setLoadingState;
|
19
|
+
private setSuccessState;
|
20
|
+
private setErrorState;
|
21
|
+
private setFinishedState;
|
22
|
+
private check;
|
23
|
+
}
|
24
|
+
declare global {
|
25
|
+
interface Window {
|
26
|
+
ToggleSwitchElement: typeof ToggleSwitchElement;
|
27
|
+
}
|
28
|
+
}
|
@@ -0,0 +1,135 @@
|
|
1
|
+
/* eslint-disable custom-elements/expose-class-on-global */
|
2
|
+
/* eslint-disable custom-elements/define-tag-after-class-definition */
|
3
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
4
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
5
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
6
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
7
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
8
|
+
};
|
9
|
+
import { controller, target } from '@github/catalyst';
|
10
|
+
import { debounce } from '@github/mini-throttle/decorators';
|
11
|
+
let ToggleSwitchElement = class ToggleSwitchElement extends HTMLElement {
|
12
|
+
get src() {
|
13
|
+
const src = this.getAttribute('src');
|
14
|
+
if (!src)
|
15
|
+
return null;
|
16
|
+
const link = this.ownerDocument.createElement('a');
|
17
|
+
link.href = src;
|
18
|
+
return link.href;
|
19
|
+
}
|
20
|
+
get csrf() {
|
21
|
+
const csrfElement = this.querySelector('[data-csrf]');
|
22
|
+
return this.getAttribute('csrf') || (csrfElement instanceof HTMLInputElement && csrfElement.value) || null;
|
23
|
+
}
|
24
|
+
get csrfField() {
|
25
|
+
// the authenticity token is passed into the element and is not generated in js land
|
26
|
+
return this.getAttribute('csrf-field') || 'authenticity_token';
|
27
|
+
}
|
28
|
+
isRemote() {
|
29
|
+
return this.src != null;
|
30
|
+
}
|
31
|
+
toggle() {
|
32
|
+
if (this.isRemote()) {
|
33
|
+
this.setLoadingState();
|
34
|
+
this.check();
|
35
|
+
}
|
36
|
+
else {
|
37
|
+
this.performToggle();
|
38
|
+
}
|
39
|
+
}
|
40
|
+
turnOn() {
|
41
|
+
if (this.isDisabled()) {
|
42
|
+
return;
|
43
|
+
}
|
44
|
+
this.switch.setAttribute('aria-checked', 'true');
|
45
|
+
this.classList.add('ToggleSwitch--checked');
|
46
|
+
}
|
47
|
+
turnOff() {
|
48
|
+
if (this.isDisabled()) {
|
49
|
+
return;
|
50
|
+
}
|
51
|
+
this.switch.setAttribute('aria-checked', 'false');
|
52
|
+
this.classList.remove('ToggleSwitch--checked');
|
53
|
+
}
|
54
|
+
isOn() {
|
55
|
+
return this.switch.getAttribute('aria-checked') === 'true';
|
56
|
+
}
|
57
|
+
isOff() {
|
58
|
+
return !this.isOn();
|
59
|
+
}
|
60
|
+
isDisabled() {
|
61
|
+
return this.switch.getAttribute('aria-disabled') === 'true';
|
62
|
+
}
|
63
|
+
disable() {
|
64
|
+
this.switch.setAttribute('aria-disabled', 'true');
|
65
|
+
}
|
66
|
+
enable() {
|
67
|
+
this.switch.setAttribute('aria-disabled', 'false');
|
68
|
+
}
|
69
|
+
performToggle() {
|
70
|
+
if (this.isOn()) {
|
71
|
+
this.turnOff();
|
72
|
+
}
|
73
|
+
else {
|
74
|
+
this.turnOn();
|
75
|
+
}
|
76
|
+
}
|
77
|
+
setLoadingState() {
|
78
|
+
this.disable();
|
79
|
+
this.errorIcon.setAttribute('hidden', 'hidden');
|
80
|
+
this.loadingSpinner.removeAttribute('hidden');
|
81
|
+
}
|
82
|
+
setSuccessState() {
|
83
|
+
this.setFinishedState(false);
|
84
|
+
}
|
85
|
+
setErrorState() {
|
86
|
+
this.setFinishedState(true);
|
87
|
+
}
|
88
|
+
setFinishedState(error) {
|
89
|
+
if (error) {
|
90
|
+
this.errorIcon.removeAttribute('hidden');
|
91
|
+
}
|
92
|
+
this.loadingSpinner.setAttribute('hidden', 'hidden');
|
93
|
+
this.enable();
|
94
|
+
}
|
95
|
+
async check() {
|
96
|
+
const body = new FormData();
|
97
|
+
if (this.csrf) {
|
98
|
+
body.append(this.csrfField, this.csrf);
|
99
|
+
}
|
100
|
+
body.append('value', this.isOn() ? '1' : '0');
|
101
|
+
try {
|
102
|
+
const response = await fetch(this.src, {
|
103
|
+
credentials: 'same-origin',
|
104
|
+
method: 'POST',
|
105
|
+
body
|
106
|
+
});
|
107
|
+
if (response.ok) {
|
108
|
+
this.setSuccessState();
|
109
|
+
this.performToggle();
|
110
|
+
}
|
111
|
+
else {
|
112
|
+
this.setErrorState();
|
113
|
+
}
|
114
|
+
}
|
115
|
+
catch (error) {
|
116
|
+
this.setErrorState();
|
117
|
+
}
|
118
|
+
}
|
119
|
+
};
|
120
|
+
__decorate([
|
121
|
+
target
|
122
|
+
], ToggleSwitchElement.prototype, "switch", void 0);
|
123
|
+
__decorate([
|
124
|
+
target
|
125
|
+
], ToggleSwitchElement.prototype, "loadingSpinner", void 0);
|
126
|
+
__decorate([
|
127
|
+
target
|
128
|
+
], ToggleSwitchElement.prototype, "errorIcon", void 0);
|
129
|
+
__decorate([
|
130
|
+
debounce(300)
|
131
|
+
], ToggleSwitchElement.prototype, "check", null);
|
132
|
+
ToggleSwitchElement = __decorate([
|
133
|
+
controller
|
134
|
+
], ToggleSwitchElement);
|
135
|
+
export { ToggleSwitchElement };
|
@@ -0,0 +1,147 @@
|
|
1
|
+
/* eslint-disable custom-elements/expose-class-on-global */
|
2
|
+
/* eslint-disable custom-elements/define-tag-after-class-definition */
|
3
|
+
|
4
|
+
import {controller, target} from '@github/catalyst'
|
5
|
+
import {debounce} from '@github/mini-throttle/decorators'
|
6
|
+
|
7
|
+
@controller
|
8
|
+
export class ToggleSwitchElement extends HTMLElement {
|
9
|
+
@target switch: HTMLElement
|
10
|
+
@target loadingSpinner: HTMLElement
|
11
|
+
@target errorIcon: HTMLElement
|
12
|
+
|
13
|
+
get src(): string | null {
|
14
|
+
const src = this.getAttribute('src')
|
15
|
+
if (!src) return null
|
16
|
+
|
17
|
+
const link = this.ownerDocument.createElement('a')
|
18
|
+
link.href = src
|
19
|
+
return link.href
|
20
|
+
}
|
21
|
+
|
22
|
+
get csrf(): string | null {
|
23
|
+
const csrfElement = this.querySelector('[data-csrf]')
|
24
|
+
return this.getAttribute('csrf') || (csrfElement instanceof HTMLInputElement && csrfElement.value) || null
|
25
|
+
}
|
26
|
+
|
27
|
+
get csrfField(): string {
|
28
|
+
// the authenticity token is passed into the element and is not generated in js land
|
29
|
+
|
30
|
+
return this.getAttribute('csrf-field') || 'authenticity_token'
|
31
|
+
}
|
32
|
+
|
33
|
+
isRemote(): boolean {
|
34
|
+
return this.src != null
|
35
|
+
}
|
36
|
+
|
37
|
+
toggle() {
|
38
|
+
if (this.isRemote()) {
|
39
|
+
this.setLoadingState()
|
40
|
+
this.check()
|
41
|
+
} else {
|
42
|
+
this.performToggle()
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
turnOn(): void {
|
47
|
+
if (this.isDisabled()) {
|
48
|
+
return
|
49
|
+
}
|
50
|
+
|
51
|
+
this.switch.setAttribute('aria-checked', 'true')
|
52
|
+
this.classList.add('ToggleSwitch--checked')
|
53
|
+
}
|
54
|
+
|
55
|
+
turnOff(): void {
|
56
|
+
if (this.isDisabled()) {
|
57
|
+
return
|
58
|
+
}
|
59
|
+
|
60
|
+
this.switch.setAttribute('aria-checked', 'false')
|
61
|
+
this.classList.remove('ToggleSwitch--checked')
|
62
|
+
}
|
63
|
+
|
64
|
+
isOn(): boolean {
|
65
|
+
return this.switch.getAttribute('aria-checked') === 'true'
|
66
|
+
}
|
67
|
+
|
68
|
+
isOff(): boolean {
|
69
|
+
return !this.isOn()
|
70
|
+
}
|
71
|
+
|
72
|
+
isDisabled(): boolean {
|
73
|
+
return this.switch.getAttribute('aria-disabled') === 'true'
|
74
|
+
}
|
75
|
+
|
76
|
+
disable(): void {
|
77
|
+
this.switch.setAttribute('aria-disabled', 'true')
|
78
|
+
}
|
79
|
+
|
80
|
+
enable(): void {
|
81
|
+
this.switch.setAttribute('aria-disabled', 'false')
|
82
|
+
}
|
83
|
+
|
84
|
+
private performToggle(): void {
|
85
|
+
if (this.isOn()) {
|
86
|
+
this.turnOff()
|
87
|
+
} else {
|
88
|
+
this.turnOn()
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
private setLoadingState(): void {
|
93
|
+
this.disable()
|
94
|
+
this.errorIcon.setAttribute('hidden', 'hidden')
|
95
|
+
this.loadingSpinner.removeAttribute('hidden')
|
96
|
+
}
|
97
|
+
|
98
|
+
private setSuccessState(): void {
|
99
|
+
this.setFinishedState(false)
|
100
|
+
}
|
101
|
+
|
102
|
+
private setErrorState(): void {
|
103
|
+
this.setFinishedState(true)
|
104
|
+
}
|
105
|
+
|
106
|
+
private setFinishedState(error: boolean): void {
|
107
|
+
if (error) {
|
108
|
+
this.errorIcon.removeAttribute('hidden')
|
109
|
+
}
|
110
|
+
|
111
|
+
this.loadingSpinner.setAttribute('hidden', 'hidden')
|
112
|
+
this.enable()
|
113
|
+
}
|
114
|
+
|
115
|
+
@debounce(300)
|
116
|
+
private async check() {
|
117
|
+
const body = new FormData()
|
118
|
+
|
119
|
+
if (this.csrf) {
|
120
|
+
body.append(this.csrfField, this.csrf)
|
121
|
+
}
|
122
|
+
|
123
|
+
body.append('value', this.isOn() ? '1' : '0')
|
124
|
+
|
125
|
+
try {
|
126
|
+
const response = await fetch(this.src!, {
|
127
|
+
credentials: 'same-origin',
|
128
|
+
method: 'POST',
|
129
|
+
body
|
130
|
+
})
|
131
|
+
if (response.ok) {
|
132
|
+
this.setSuccessState()
|
133
|
+
this.performToggle()
|
134
|
+
} else {
|
135
|
+
this.setErrorState()
|
136
|
+
}
|
137
|
+
} catch (error) {
|
138
|
+
this.setErrorState()
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
declare global {
|
144
|
+
interface Window {
|
145
|
+
ToggleSwitchElement: typeof ToggleSwitchElement
|
146
|
+
}
|
147
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<%= render(Primer::BaseComponent.new(tag: "toggle-switch", **@system_arguments)) do %>
|
2
|
+
<%= render(Primer::OcticonComponent.new(size: :small, color: :danger, icon: :alert, hidden: "true", data: { target: "toggle-switch.errorIcon" })) %>
|
3
|
+
<%= render(Primer::SpinnerComponent.new(size: :small, hidden: "true", data: { target: "toggle-switch.loadingSpinner" })) %>
|
4
|
+
<%= render(Primer::Beta::Text.new(aria: { hidden: true }, classes: "ToggleSwitch-status")) do %>
|
5
|
+
<%= render(Primer::Box.new(classes: "ToggleSwitch-statusOn").with_content("On")) %>
|
6
|
+
<%= render(Primer::Box.new(classes: "ToggleSwitch-statusOff").with_content("Off")) %>
|
7
|
+
<% end %>
|
8
|
+
|
9
|
+
<%= render(Primer::BaseComponent.new(tag: :button, classes: "ToggleSwitch-track", role: "switch", data: { target: "toggle-switch.switch", action: "click:toggle-switch#toggle" }, aria: { checked: on?, disabled: disabled?, label: "Switch" })) do %>
|
10
|
+
<%= render(Primer::Box.new(classes: "ToggleSwitch-icons", aria: { hidden: true })) do %>
|
11
|
+
<%= render(Primer::Box.new(classes: "ToggleSwitch-lineIcon")) do %>
|
12
|
+
<%= render(Primer::BaseComponent.new(
|
13
|
+
tag: :svg,
|
14
|
+
width: @size == :small ? 12 : 16,
|
15
|
+
height: @size == :small ? 12 : 16,
|
16
|
+
viewBox: "0 0 16 16",
|
17
|
+
fill: "currentColor",
|
18
|
+
xmlns: "http://www.w3.org/2000/svg",
|
19
|
+
aria: { hidden: true },
|
20
|
+
focusable: false
|
21
|
+
)) do %>
|
22
|
+
<path fill-rule="evenodd" d="M8 2a.75.75 0 0 1 .75.75v11.5a.75.75 0 0 1-1.5 0V2.75A.75.75 0 0 1 8 2Z" />
|
23
|
+
<% end %>
|
24
|
+
<% end %>
|
25
|
+
|
26
|
+
<%= render(Primer::Box.new(classes: "ToggleSwitch-circleIcon")) do %>
|
27
|
+
<%= render(Primer::BaseComponent.new(
|
28
|
+
tag: :svg,
|
29
|
+
width: @size == :small ? 12 : 16,
|
30
|
+
height: @size == :small ? 12 : 16,
|
31
|
+
viewBox: "0 0 16 16",
|
32
|
+
fill: "currentColor",
|
33
|
+
xmlns: "http://www.w3.org/2000/svg",
|
34
|
+
aria: { hidden: true },
|
35
|
+
focusable: false
|
36
|
+
)) do %>
|
37
|
+
<path fill-rule="evenodd" d="M8 12.5a4.5 4.5 0 1 0 0-9 4.5 4.5 0 0 0 0 9ZM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12Z" />
|
38
|
+
<% end %>
|
39
|
+
<% end %>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<%= render(Primer::Box.new(classes: "ToggleSwitch-knob")) %>
|
43
|
+
<% end %>
|
44
|
+
<% end %>
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Primer
|
4
|
+
module Alpha
|
5
|
+
# The ToggleSwitch component is a button that toggles between two boolean states. It is meant to be used for
|
6
|
+
# settings that should cause an immediate update. If configured with a "src" attribute, the component will
|
7
|
+
# make a POST request containing data of the form "value: 0 | 1".
|
8
|
+
class ToggleSwitch < Primer::Component
|
9
|
+
SIZE_DEFAULT = :medium
|
10
|
+
SIZE_MAPPINGS = {
|
11
|
+
SIZE_DEFAULT => nil,
|
12
|
+
:small => "ToggleSwitch--small"
|
13
|
+
}.freeze
|
14
|
+
SIZE_OPTIONS = SIZE_MAPPINGS.keys.freeze
|
15
|
+
|
16
|
+
STATUS_LABEL_POSITION_DEFAULT = :start
|
17
|
+
STATUS_LABEL_POSITION_MAPPINGS = {
|
18
|
+
STATUS_LABEL_POSITION_DEFAULT => nil,
|
19
|
+
:end => "ToggleSwitch--statusAtEnd"
|
20
|
+
}.freeze
|
21
|
+
STATUS_LABEL_POSITION_OPTIONS = STATUS_LABEL_POSITION_MAPPINGS.keys.freeze
|
22
|
+
|
23
|
+
# @example Default
|
24
|
+
# <%= render(Primer::Alpha::ToggleSwitch.new(src: "/foo")) %>
|
25
|
+
#
|
26
|
+
# @example Checked
|
27
|
+
# <%= render(Primer::Alpha::ToggleSwitch.new(src: "/foo", checked: true)) %>
|
28
|
+
#
|
29
|
+
# @example Disabled
|
30
|
+
# <%= render(Primer::Alpha::ToggleSwitch.new(src: "/foo", enabled: false)) %>
|
31
|
+
#
|
32
|
+
# @example Checked and Disabled
|
33
|
+
# <%= render(Primer::Alpha::ToggleSwitch.new(src: "/foo", checked: true, enabled: false)) %>
|
34
|
+
#
|
35
|
+
# @example Small
|
36
|
+
# <%= render(Primer::Alpha::ToggleSwitch.new(src: "/foo", size: :small)) %>
|
37
|
+
#
|
38
|
+
# @example With status label positioned at the end
|
39
|
+
# <%= render(Primer::Alpha::ToggleSwitch.new(src: "/foo", status_label_position: :end)) %>
|
40
|
+
#
|
41
|
+
# @param src [String] The URL to POST to when the toggle switch is toggled. If `nil`, the toggle switch will not make any requests.
|
42
|
+
# @param csrf_token [String] A CSRF token that will be sent to the server as "authenticity_token" when the toggle switch is toggled. Unused if `src` is `nil`.
|
43
|
+
# @param checked [Boolean] Whether the toggle switch is on or off.
|
44
|
+
# @param enabled [Boolean] Whether or not the toggle switch responds to user input.
|
45
|
+
# @param size [Symbol] What size toggle switch to render. <%= one_of(Primer::Alpha::ToggleSwitch::STATUS_LABEL_POSITION_OPTIONS) %>
|
46
|
+
# @param status_label_position [Symbol] Which side of the toggle switch to render the status label. <%= one_of(Primer::Alpha::ToggleSwitch::SIZE_OPTIONS) %>
|
47
|
+
# @param system_arguments [Hash] <%= link_to_system_arguments_docs %>
|
48
|
+
def initialize(src: nil, csrf_token: nil, checked: false, enabled: true, size: SIZE_DEFAULT, status_label_position: STATUS_LABEL_POSITION_DEFAULT, **system_arguments)
|
49
|
+
@src = src
|
50
|
+
@csrf_token = csrf_token
|
51
|
+
@checked = checked
|
52
|
+
@enabled = enabled
|
53
|
+
@system_arguments = system_arguments
|
54
|
+
|
55
|
+
@size = fetch_or_fallback(SIZE_OPTIONS, size, SIZE_DEFAULT)
|
56
|
+
@status_label_position = fetch_or_fallback(
|
57
|
+
STATUS_LABEL_POSITION_OPTIONS, status_label_position, STATUS_LABEL_POSITION_DEFAULT
|
58
|
+
)
|
59
|
+
|
60
|
+
@system_arguments[:classes] = class_names(
|
61
|
+
@system_arguments.delete(:classes),
|
62
|
+
"ToggleSwitch",
|
63
|
+
on? ? "ToggleSwitch--checked" : nil,
|
64
|
+
enabled? ? nil : "ToggleSwitch--disabled",
|
65
|
+
STATUS_LABEL_POSITION_MAPPINGS[@status_label_position],
|
66
|
+
SIZE_MAPPINGS[@size]
|
67
|
+
)
|
68
|
+
|
69
|
+
@system_arguments[:src] = @src if @src
|
70
|
+
|
71
|
+
return unless @src && @csrf_token
|
72
|
+
|
73
|
+
@system_arguments[:csrf] = @csrf_token
|
74
|
+
end
|
75
|
+
|
76
|
+
def on?
|
77
|
+
@checked
|
78
|
+
end
|
79
|
+
|
80
|
+
def enabled?
|
81
|
+
@enabled
|
82
|
+
end
|
83
|
+
|
84
|
+
def disabled?
|
85
|
+
!enabled?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -9,6 +9,8 @@ declare class ToolTipElement extends HTMLElement {
|
|
9
9
|
get direction(): Direction;
|
10
10
|
set direction(value: Direction);
|
11
11
|
get control(): HTMLElement | null;
|
12
|
+
set hiddenFromView(value: true | false);
|
13
|
+
get hiddenFromView(): true | false;
|
12
14
|
connectedCallback(): void;
|
13
15
|
disconnectedCallback(): void;
|
14
16
|
handleEvent(event: Event): void;
|