60 lines
1.9 KiB
Vue
60 lines
1.9 KiB
Vue
<script setup lang="ts">
|
|
import { CheckboxIndicator, CheckboxRoot } from 'reka-ui';
|
|
import { computed } from 'vue';
|
|
|
|
const props = defineProps<{
|
|
/**
|
|
* When set, the checkbox toggles this value inside an array model, which is
|
|
* how the permission matrix binds a column of boxes to one role.
|
|
* Without it the model is a plain boolean.
|
|
*/
|
|
value?: string | number;
|
|
invalid?: boolean;
|
|
disabled?: boolean;
|
|
id?: string;
|
|
}>();
|
|
|
|
const model = defineModel<boolean | Array<string | number> | null>();
|
|
|
|
const checked = computed(() => {
|
|
if (props.value === undefined) {
|
|
return Boolean(model.value);
|
|
}
|
|
|
|
return Array.isArray(model.value) && model.value.includes(props.value);
|
|
});
|
|
|
|
function toggle(state: boolean | 'indeterminate'): void {
|
|
if (props.value === undefined) {
|
|
model.value = state === true;
|
|
|
|
return;
|
|
}
|
|
|
|
const current = Array.isArray(model.value) ? model.value : [];
|
|
|
|
model.value = state === true
|
|
? [...current, props.value]
|
|
: current.filter((entry) => entry !== props.value);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<CheckboxRoot
|
|
:id="id"
|
|
:model-value="checked"
|
|
:disabled="disabled"
|
|
:aria-invalid="invalid || undefined"
|
|
class="flex size-5 shrink-0 cursor-pointer items-center justify-center rounded border transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-60"
|
|
:class="[
|
|
checked ? 'border-primary-600 bg-primary-600 text-white' : 'bg-white',
|
|
invalid ? 'border-red-500 focus-visible:outline-red-500' : 'border-slate-300 focus-visible:outline-primary-600',
|
|
]"
|
|
@update:model-value="toggle"
|
|
>
|
|
<CheckboxIndicator>
|
|
<i class="fas fa-check text-[0.65rem]" aria-hidden="true" />
|
|
</CheckboxIndicator>
|
|
</CheckboxRoot>
|
|
</template>
|