Skip to content

Commit 5c49219

Browse files
committed
Updates radio and checkbox components
1 parent ab7cc91 commit 5c49219

File tree

8 files changed

+179
-208
lines changed

8 files changed

+179
-208
lines changed

src/webviews/apps/shared/components/checkbox/checkbox.css.ts

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,9 @@ import { css } from 'lit';
33
export const checkboxStyles = css`
44
:host {
55
--design-unit: 4;
6-
--checkbox-corner-radius: 3;
7-
--border-width: 1;
8-
}
9-
label {
10-
display: flex;
11-
gap: calc(var(--design-unit) * 2px + 2px);
12-
align-items: center;
13-
color: var(--vscode-checkbox-foreground);
14-
margin-inline-end: calc(var(--design-unit) * 2px + 2px);
15-
user-select: none;
16-
white-space: nowrap;
17-
}
18-
19-
label {
20-
cursor: pointer;
21-
}
22-
label[aria-disabled] {
23-
cursor: default;
24-
opacity: 0.5;
25-
}
26-
27-
input[type='checkbox'] {
28-
position: absolute;
29-
z-index: -1;
30-
opacity: 0;
31-
}
32-
input[type='checkbox'] + .control {
33-
flex-shrink: 0;
34-
display: inline-flex;
35-
align-items: center;
36-
justify-content: center;
37-
position: relative;
38-
width: calc(var(--design-unit) * 4px + 2px);
39-
height: calc(var(--design-unit) * 4px + 2px);
40-
box-sizing: border-box;
41-
border-radius: calc(var(--checkbox-corner-radius) * 1px);
42-
border: calc(var(--border-width) * 1px) solid var(--vscode-checkbox-border);
43-
background: var(--vscode-checkbox-background);
44-
}
45-
46-
label:not([aria-disabled]) input[type='checkbox']:hover + .control {
47-
background: var(--vscode-checkbox-background);
48-
border-color: var(--vscode-checkbox-border);
49-
}
50-
51-
label:not([aria-disabled]) input[type='checkbox']:focus-visible + .control,
52-
label:not([aria-disabled]) input[type='checkbox']:focus + .control {
53-
outline: 1px solid var(--vscode-focusBorder);
54-
}
55-
56-
label:not([aria-disabled]):active input[type='checkbox'] + .control,
57-
label:not([aria-disabled]) input[type='checkbox']:active + .control {
58-
background: var(--vscode-checkbox-background);
59-
border-color: var(--vscode-focusBorder);
60-
}
61-
62-
code-icon {
63-
pointer-events: none;
6+
--control-corner-radius: 3px;
7+
--control-border-width: 1px;
8+
--control-size: calc(var(--design-unit) * 4px + 2px);
9+
--label-spacing: calc(var(--design-unit) * 2px + 2px);
6410
}
6511
`;

src/webviews/apps/shared/components/checkbox/checkbox.react.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { reactWrapper } from '../helpers/react-wrapper';
2-
import { Checkbox } from './checkbox';
2+
import { Checkbox, tagName } from './checkbox';
33

44
export const GlCheckbox = reactWrapper(Checkbox, {
5-
tagName: 'gl-checkbox',
5+
tagName: tagName,
66
events: {
77
onChange: 'gl-change-value',
88
},

src/webviews/apps/shared/components/checkbox/checkbox.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@ import { html } from 'lit';
22
import { customElement, property, state } from 'lit/decorators.js';
33
import { when } from 'lit/directives/when.js';
44
import { GlElement, observe } from '../element';
5+
import { checkboxBaseStyles } from '../forms/checkbox.css';
56
import { checkboxStyles } from './checkbox.css';
67

78
import '../code-icon';
89

9-
@customElement('gl-checkbox')
10+
export const tagName = 'gl-checkbox';
11+
12+
@customElement(tagName)
1013
export class Checkbox extends GlElement {
11-
static override readonly styles = [checkboxStyles];
14+
static override shadowRootOptions: ShadowRootInit = {
15+
...GlElement.shadowRootOptions,
16+
delegatesFocus: true,
17+
};
18+
19+
static override readonly styles = [checkboxBaseStyles, checkboxStyles];
1220

1321
@property({ type: Boolean })
1422
disabled: boolean;
@@ -35,19 +43,33 @@ export class Checkbox extends GlElement {
3543

3644
handleChange(e: Event) {
3745
this.checked = (e.target as HTMLInputElement).checked;
38-
const event = new Event('gl-change-value');
46+
const event = new CustomEvent('gl-change-value');
3947
this.dispatchEvent(event);
4048
}
4149

4250
renderCheck() {
51+
if (!this.checked) return undefined;
52+
4353
return html` <code-icon icon="check"></code-icon> `;
4454
}
4555

4656
override render() {
4757
return html`<label ?aria-disabled=${this.disabled}
48-
><input .disabled=${this.disabled} type="checkbox" .checked=${this.checked} @change=${this.handleChange} />
49-
<div class="control">${when(this.checked, this.renderCheck.bind(this))}</div>
50-
<span><slot></slot></span
51-
></label>`;
58+
><input
59+
class="input"
60+
.disabled=${this.disabled}
61+
type="checkbox"
62+
.checked=${this.checked}
63+
@change=${this.handleChange}
64+
/>
65+
<div class="control">${this.renderCheck()}</div>
66+
<slot class="label-text"></slot>
67+
</label>`;
68+
}
69+
}
70+
71+
declare global {
72+
interface HTMLElementTagNameMap {
73+
[tagName]: Checkbox;
5274
}
5375
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { css } from 'lit';
2+
3+
export const checkboxBaseStyles = css`
4+
:host {
5+
display: block;
6+
margin-block: 0.8rem;
7+
}
8+
9+
label {
10+
display: flex;
11+
gap: var(--label-spacing);
12+
align-items: center;
13+
color: var(--vscode-checkbox-foreground);
14+
user-select: none;
15+
white-space: nowrap;
16+
cursor: pointer;
17+
}
18+
19+
label[aria-disabled] {
20+
cursor: default;
21+
opacity: 0.5;
22+
}
23+
24+
.label-text {
25+
display: block;
26+
line-height: normal;
27+
margin-inline-end: var(--label-spacing);
28+
}
29+
30+
.input {
31+
position: absolute;
32+
z-index: -1;
33+
opacity: 0;
34+
}
35+
.control {
36+
flex-shrink: 0;
37+
display: inline-flex;
38+
align-items: center;
39+
justify-content: center;
40+
position: relative;
41+
width: var(--control-size);
42+
height: var(--control-size);
43+
box-sizing: border-box;
44+
border-radius: var(--control-corner-radius);
45+
border: var(--control-border-width) solid var(--vscode-checkbox-border);
46+
background: var(--vscode-checkbox-background);
47+
}
48+
49+
label:not([aria-disabled]) .input:hover + .control {
50+
background: var(--vscode-checkbox-background);
51+
border-color: var(--vscode-checkbox-border);
52+
}
53+
54+
label:not([aria-disabled]) .input:focus-visible + .control,
55+
label:not([aria-disabled]) .input:focus + .control {
56+
outline: 1px solid var(--vscode-focusBorder);
57+
}
58+
59+
label:not([aria-disabled]):active .input + .control,
60+
label:not([aria-disabled]) .input:active + .control {
61+
background: var(--vscode-checkbox-background);
62+
border-color: var(--vscode-focusBorder);
63+
}
64+
65+
code-icon {
66+
pointer-events: none;
67+
}
68+
`;
Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,58 @@
11
import { html } from 'lit';
2-
import { customElement, property } from 'lit/decorators.js';
2+
import { customElement, property, queryAssignedElements } from 'lit/decorators.js';
33
import { GlElement, observe } from '../element';
44
import type { Radio } from './radio';
55
import { radioStyles } from './radio.css';
66

77
import '../code-icon';
88

9-
@customElement('gl-radio-group')
9+
export const tagName = 'gl-radio-group';
10+
11+
@customElement(tagName)
1012
export class RadioGroup extends GlElement {
1113
static override readonly styles = [radioStyles];
1214

13-
@property({ type: Boolean })
14-
disabled: boolean;
15+
@property({ type: Boolean, reflect: true })
16+
disabled: boolean = false;
1517

1618
@property({ type: String })
17-
value: string | null = null;
18-
19-
constructor() {
20-
super();
21-
this.disabled = false;
22-
}
19+
value?: string;
2320

2421
@observe(['value', 'disabled'])
2522
private handleValueChange() {
26-
Object.values(this.radioElements).forEach(radio => {
27-
if (radio) {
28-
radio.checked = radio.value === this.value;
29-
radio.disabled = this.disabled;
30-
}
31-
});
23+
this.updateRadioElements();
3224
}
3325

34-
setValue(value: string) {
35-
this.value = value;
36-
const event = new Event('gl-change-value');
37-
this.dispatchEvent(event);
26+
@queryAssignedElements({ flatten: true })
27+
private radioEls!: Radio[];
28+
29+
override firstUpdated() {
30+
this.role = 'group';
3831
}
3932

40-
renderCheck() {
41-
return html` <code-icon icon="check"></code-icon> `;
33+
private updateRadioElements(updateParentGroup = false) {
34+
this.radioEls.forEach(radio => {
35+
if (updateParentGroup) {
36+
radio.parentGroup = this;
37+
}
38+
radio.checked = radio.value === this.value;
39+
radio.disabled = this.disabled;
40+
});
4241
}
43-
private radioElements: Partial<Record<string, Radio>> = {};
4442

45-
subscribeRadioElement(element: Radio) {
46-
if (this.radioElements[element.value]) {
47-
console.warn(
48-
'be sure if you do not have the same value of radio in one group',
49-
element,
50-
this.radioElements,
51-
);
52-
}
53-
this.radioElements[element.value] = element;
54-
element.checked = element.value === this.value;
55-
element.disabled = this.disabled;
43+
override render() {
44+
return html`<slot @slotchange=${() => this.updateRadioElements(true)}></slot>`;
5645
}
57-
unsubscribeRadioElement(element: Radio) {
58-
this.radioElements[element.value] = undefined;
46+
47+
setValue(value: string) {
48+
this.value = value;
49+
const event = new CustomEvent('gl-change-value');
50+
this.dispatchEvent(event);
5951
}
52+
}
6053

61-
override render() {
62-
return html`<slot></slot>`;
54+
declare global {
55+
interface HTMLElementTagNameMap {
56+
[tagName]: RadioGroup;
6357
}
6458
}

src/webviews/apps/shared/components/radio/radio.css.ts

Lines changed: 4 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,62 +3,9 @@ import { css } from 'lit';
33
export const radioStyles = css`
44
:host {
55
--design-unit: 4;
6-
--border-width: 1;
7-
}
8-
label {
9-
display: flex;
10-
gap: calc(var(--design-unit) * 2px + 2px);
11-
align-items: center;
12-
color: var(--vscode-checkbox-foreground);
13-
margin-inline-end: calc(var(--design-unit) * 2px + 2px);
14-
user-select: none;
15-
white-space: nowrap;
16-
}
17-
18-
label {
19-
cursor: pointer;
20-
}
21-
label[aria-disabled] {
22-
cursor: default;
23-
opacity: 0.5;
24-
}
25-
26-
button {
27-
position: absolute;
28-
z-index: -1;
29-
opacity: 0;
30-
}
31-
button + .control {
32-
flex-shrink: 0;
33-
display: inline-flex;
34-
align-items: center;
35-
justify-content: center;
36-
position: relative;
37-
width: calc(var(--design-unit) * 4px + 2px);
38-
height: calc(var(--design-unit) * 4px + 2px);
39-
box-sizing: border-box;
40-
border-radius: 50%;
41-
border: calc(var(--border-width) * 1px) solid var(--vscode-checkbox-border);
42-
background: var(--vscode-checkbox-background);
43-
}
44-
45-
label:not([aria-disabled]) button:hover + .control {
46-
background: var(--vscode-checkbox-background);
47-
border-color: var(--vscode-checkbox-border);
48-
}
49-
50-
label:not([aria-disabled]) button:focus-visible + .control,
51-
label:not([aria-disabled]) button:focus + .control {
52-
outline: 1px solid var(--vscode-focusBorder);
53-
}
54-
55-
label:not([aria-disabled]):active button + .control,
56-
label:not([aria-disabled]) button:active + .control {
57-
background: var(--vscode-checkbox-background);
58-
border-color: var(--vscode-focusBorder);
59-
}
60-
61-
code-icon {
62-
pointer-events: none;
6+
--control-corner-radius: 50%;
7+
--control-border-width: 1px;
8+
--control-size: calc(var(--design-unit) * 4px + 2px);
9+
--label-spacing: calc(var(--design-unit) * 2px + 2px);
6310
}
6411
`;

src/webviews/apps/shared/components/radio/radio.react.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { reactWrapper } from '../helpers/react-wrapper';
2-
import { Radio } from './radio';
3-
import { RadioGroup } from './radio-group';
2+
import { Radio, tagName as radioTagName } from './radio';
3+
import { RadioGroup, tagName as radioGroupTagName } from './radio-group';
44

55
export const GlRadio = reactWrapper(Radio, {
6-
tagName: 'gl-radio',
6+
tagName: radioTagName,
77
});
88

99
export const GlRadioGroup = reactWrapper(RadioGroup, {
10-
tagName: 'gl-radio-group',
10+
tagName: radioGroupTagName,
1111
events: {
1212
onChange: 'gl-change-value',
1313
},

0 commit comments

Comments
 (0)