Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add sd-radio #68

Merged
merged 9 commits into from
Nov 5, 2024
15 changes: 8 additions & 7 deletions src/ui/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import "./field";
import "./label";
import "./option";
import "./radio-group";
import "./switch";
import "./text-area";
import "./text-field";
export * from "./field";
export * from "./label";
export * from "./option";
export * from "./radio";
export * from "./radio-group";
export * from "./switch";
export * from "./text-area";
export * from "./text-field";
50 changes: 36 additions & 14 deletions src/ui/components/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,22 @@ export class SDOptionElement extends LitElement {
/**
* Private backing field for {@link SDOptionElement.value}.
*/
#value: boolean | number | string | undefined;
#value: boolean | number | string | null | undefined = null;

/**
* Determines whether the option is disabled; default `false`.
*/
@property({ type: Boolean })
@property({
reflect: true,
type: Boolean,
})
public accessor disabled: boolean = false;

/**
* Label that represents the option; read from the `innerText` of the element.
* @returns The label.
* Label that represents the option.
*/
public get label(): string {
return this.innerText;
}
@property()
public accessor label: string | undefined;

/**
* Type of the value; allows for the value to be converted to a boolean or number.
Expand All @@ -44,23 +45,44 @@ export class SDOptionElement extends LitElement {
* @returns The value.
*/
public get value(): boolean | number | string | undefined {
if (this.#value === null) {
if (this.type === "boolean") {
this.#value = parseBoolean(this.htmlValue);
} else if (this.type === "number") {
this.#value = parseNumber(this.htmlValue);
} else {
this.#value = this.htmlValue;
}
}

return this.#value;
}

/**
* Sets the value of the option, and associated type.
* @param value New value.
*/
public set value(value: boolean | number | string | undefined) {
this.type = typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : "string";
this.htmlValue = value?.toString();
}

/**
* @inheritdoc
*/
protected override update(changedProperties: Map<PropertyKey, unknown>): void {
super.update(changedProperties);
this.dispatchEvent(new Event("update"));
}

/**
* @inheritdoc
*/
protected override willUpdate(_changedProperties: Map<PropertyKey, unknown>): void {
super.willUpdate(_changedProperties);

if (_changedProperties.has("type") || _changedProperties.has("value")) {
if (this.type === "boolean") {
this.#value = parseBoolean(this.htmlValue);
} else if (this.type === "number") {
this.#value = parseNumber(this.htmlValue);
} else {
this.#value = this.htmlValue;
}
this.#value = null;
}
}
}
Expand Down
121 changes: 16 additions & 105 deletions src/ui/components/radio-group.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { css, html, LitElement, type TemplateResult } from "lit";
import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js";
import { repeat } from "lit/directives/repeat.js";

import { Input } from "../mixins/input";
import { List } from "../mixins/list";
import { preventDoubleClickSelection } from "../utils";
import { SDRadioElement } from "./radio";

/**
* Element that offers persisting a value via a list of radio options.
Expand All @@ -17,92 +16,10 @@ export class SDRadioGroupElement extends List(Input<boolean | number | string>(L
*/
public static styles = [
super.styles ?? [],
...SDRadioElement.styles,
css`
label {
sd-radio {
display: flex;
align-items: center;
}

input {
/* Hide the input, whilst still allowing focus */
height: 0;
opacity: 0;
position: absolute;
width: 0;
}

/**
* Radio button replacement.
*/

.indicator {
--size: calc(var(--size-m) - calc(var(--border-width-thin) * 2));
align-items: center;
border: var(--border-width-thin) solid var(--color-content-disabled);
border-radius: var(--rounding-full);
display: inline-flex;
height: var(--size);
justify-content: center;
margin: var(--space-xs) var(--space-xs) var(--space-xs) 0;
user-select: none;
width: var(--size);
}

/**
* Checked.
*/

input:checked {
& + .indicator {
background: var(--color-surface-accent);
border-color: var(--color-content-disabled);
border-radius: var(--rounding-full);
}

& + .indicator::before {
content: "";
background: var(--color-surface-ondark);
border-radius: var(--rounding-full);
display: block;
height: var(--size-xs);
width: var(--size-xs);
}
}

/**
* Disabled.
*/

label:has(input:disabled) {
color: var(--color-content-disabled);
}

input:disabled + .indicator {
border-color: var(--color-border-subtle-disabled);
}

/**
* Checked + disabled.
*/

input:checked:disabled {
& + .indicator {
background-color: var(--color-surface-disabled);
}

& + .indicator::before {
background-color: var(--color-content-disabled);
}
}

/**
* Focus
*/

input:focus-visible + .indicator {
box-shadow: var(--highlight-box-shadow);
outline: var(--highlight-outline--focus);
outline-offset: var(--highlight-outline-offset);
}
`,
];
Expand All @@ -114,25 +31,19 @@ export class SDRadioGroupElement extends List(Input<boolean | number | string>(L
return html`
${repeat(
this.items,
({ key }) => key,
({ disabled, label, value }) => {
return html`
<label role="radio" @mousedown=${preventDoubleClickSelection}>
<input
name="radio"
type="radio"
value=${ifDefined(value)}
tabindex=${ifDefined(disabled ? undefined : 0)}
.checked=${this.value === value}
.disabled=${disabled}
@change=${(): void => {
this.value = value;
}}
/>
<span class="indicator"></span>
${label}
</label>
`;
(opt) => opt,
(opt) => {
return html`<sd-radio
name="__radio"
.checked=${this.value === opt.value}
.disabled=${opt.disabled}
.label=${opt.label}
.value=${opt.value}
@change=${(): void => {
this.value = opt.value;
}}
>${opt.innerText}</sd-radio
>`;
},
)}
`;
Expand Down
Loading