import {
booleanAttribute,
ChangeDetectionStrategy,
Component,
input,
model,
ViewEncapsulation,
} from "@angular/core";
import { setupRipple } from "../../core/ripple/setupRipple";
import { setupModelHook } from "../../core/setupModelHook";
import { NgIcon } from "@ng-icons/core";
import { tablerCheck } from "@ng-icons/tabler-icons";
@Component({
selector: "sd-checkbox",
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [NgIcon],
template: `
@if (!radio()) {
} @else {
}
`,
styles: [
/* language=SCSS */ `
@use "sass:map";
@use "../../../scss/commons/variables";
@use "../../../scss/commons/mixins";
sd-checkbox {
@include mixins.form-control-base();
color: inherit;
cursor: pointer;
height: calc(
var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-sm) * 2 + 2px
);
gap: var(--gap-sm);
@supports not (appearance: auto) {
gap: 0;
> * + * {
margin-left: var(--gap-sm);
}
}
> ._indicator_rect {
display: inline-block;
vertical-align: -0.125em;
user-select: none;
width: calc(var(--font-size-default) + 2px);
height: calc(var(--font-size-default) + 2px);
border: 1px solid var(--trans-light);
background: var(--theme-secondary-lightest);
> ._indicator {
margin: -1px -2px;
text-align: center;
opacity: 0;
color: var(--text-trans-rev-default);
> ng-icon {
> svg {
vertical-align: top;
stroke-width: 2.5px !important;
}
}
}
}
._contents {
display: inline-block;
vertical-align: top;
padding-left: var(--gap-sm);
}
> ._indicator_rect + ._contents:empty {
display: none;
}
&:focus > ._indicator_rect {
border-color: var(--theme-primary-dark);
}
&[data-sd-checked="true"] {
> ._indicator_rect {
background: var(--theme-primary-default);
> ._indicator {
opacity: 1;
}
}
}
@each $key, $val in map.get(variables.$vars, theme) {
&[data-sd-theme="#{$key}"] {
> ._indicator_rect {
background: var(--theme-#{$key}-lightest);
> ._indicator {
color: var(--theme-#{$key}-default);
}
}
&:focus {
> ._indicator_rect {
border-color: var(--theme-#{$key}-default);
}
}
&[data-sd-checked="true"] {
> ._indicator_rect {
background: var(--theme-#{$key}-default);
> ._indicator {
color: var(--text-trans-rev-default);
}
}
}
}
}
&[data-sd-theme="white"] {
> ._indicator_rect {
background: var(--control-color);
border-color: var(--text-trans-lightest);
}
&:focus {
> ._indicator_rect {
border-color: var(--text-trans-default);
}
}
&[data-sd-checked="true"] {
> ._indicator_rect {
background: var(--theme-primary-default);
}
}
}
&[data-sd-radio="true"] {
> ._indicator_rect {
border-radius: 100%;
padding: var(--gap-xs);
> ._indicator {
border-radius: 100%;
width: 100%;
height: 100%;
background: var(--theme-primary-default);
}
}
&[data-sd-checked="true"] {
> ._indicator_rect {
background: var(--theme-secondary-lightest);
border-color: var(--theme-primary-dark);
}
}
}
&[data-sd-size="sm"] {
height: calc(
var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-xs) * 2 + 2px
);
padding: var(--gap-xs) var(--gap-sm);
gap: var(--gap-xs);
@supports not (appearance: auto) {
gap: 0;
> * + * {
margin-left: var(--gap-xs);
}
}
}
&[data-sd-size="lg"] {
height: calc(
var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-default) * 2 + 2px
);
padding: var(--gap-default) var(--gap-lg);
gap: var(--gap-default);
@supports not (appearance: auto) {
gap: 0;
> * + * {
margin-left: var(--gap-default);
}
}
}
&[data-sd-inset="true"] {
height: calc(
var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-sm) * 2
);
border: none;
justify-content: center;
text-align: center;
&[data-sd-size="sm"] {
height: calc(
var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-xs) * 2
);
}
&[data-sd-size="lg"] {
height: calc(
var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-default) * 2
);
}
}
&[data-sd-inline="true"] {
display: inline-block;
vertical-align: top;
padding: 0;
border: none;
height: calc(var(--font-size-default) * var(--line-height-strip-unit));
width: auto;
}
&[data-sd-disabled="true"] {
opacity: 0.3;
pointer-events: none;
}
}
`,
],
host: {
"[attr.data-sd-checked]": "value()",
"[attr.data-sd-disabled]": "disabled()",
"[attr.data-sd-inline]": "inline()",
"[attr.data-sd-inset]": "inset()",
"[attr.data-sd-radio]": "radio()",
"[attr.data-sd-size]": "size()",
"[attr.data-sd-theme]": "theme()",
"[attr.tabindex]": "'0'",
"(click)": "onClick($event)",
"(keydown)": "onKeydown($event)",
},
})
export class SdCheckbox {
value = model(false);
canChangeFn = input<(item: boolean) => boolean | Promise>(() => true);
icon = input(tablerCheck);
radio = input(false, { transform: booleanAttribute });
disabled = input(false, { transform: booleanAttribute });
size = input<"sm" | "lg">();
inline = input(false, { transform: booleanAttribute });
inset = input(false, { transform: booleanAttribute });
theme = input<
| "primary"
| "secondary"
| "info"
| "success"
| "warning"
| "danger"
| "gray"
| "blue-gray"
| "white"
>();
contentStyle = input();
constructor() {
setupModelHook(this.value, this.canChangeFn);
setupRipple(() => !this.disabled());
}
onClick(event: Event) {
event.preventDefault();
event.stopPropagation();
if (this.disabled()) return;
if (this.radio()) {
this.value.set(true);
} else {
this.value.update((v) => !v);
}
}
onKeydown(event: KeyboardEvent): void {
if (event.key === " ") {
event.preventDefault();
event.stopPropagation();
if (this.disabled()) return;
if (this.radio()) {
this.value.set(true);
} else {
this.value.update((v) => !v);
}
}
}
}