primer_view_components 0.0.91 → 0.0.92
Sign up to get free protection for your applications and to get access to all the features.
- 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;
|