u
This commit is contained in:
1 parent
f572dce2f6
commit
e99a9fb274
356 files changed
+28877
-1055
No files matched your search
@@ -0,0 +1,158 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { Check, Minus } from '@lucide/vue'
|
||||
import { useNamespace } from '../_composables/useNamespace'
|
||||
import type { CheckboxProps } from './types'
|
||||
|
||||
/**
|
||||
* 复选框 —— 支持选中 / 未选中 / 半选三种状态。
|
||||
*
|
||||
* 半选(indeterminate)是原生 DOM property,无法通过 attribute 绑定,组件内部手动同步;
|
||||
* 勾选态优先,选中时会强制清除半选。
|
||||
*/
|
||||
defineOptions({ name: 'G3Checkbox' })
|
||||
|
||||
const props = withDefaults(defineProps<CheckboxProps>(), {
|
||||
modelValue: false,
|
||||
indeterminate: false,
|
||||
disabled: false,
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
change: [value: boolean, ev: Event]
|
||||
focus: [ev: FocusEvent]
|
||||
blur: [ev: FocusEvent]
|
||||
}>()
|
||||
|
||||
const { b, e, m, is } = useNamespace('checkbox')
|
||||
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
/** 选中态优先显示勾选;仅在未选中且半选时显示减号 */
|
||||
const icon = computed(() => {
|
||||
if (props.modelValue) return Check
|
||||
if (props.indeterminate) return Minus
|
||||
return null
|
||||
})
|
||||
|
||||
const classes = computed(() => [
|
||||
b(),
|
||||
{
|
||||
[is('checked')]: props.modelValue,
|
||||
[is('indeterminate')]: props.indeterminate,
|
||||
[is('disabled')]: props.disabled,
|
||||
},
|
||||
])
|
||||
|
||||
function onChange(event: Event) {
|
||||
const checked = (event.target as HTMLInputElement).checked
|
||||
emit('update:modelValue', checked)
|
||||
emit('change', checked, event)
|
||||
}
|
||||
|
||||
function syncIndeterminate() {
|
||||
if (inputRef.value) {
|
||||
inputRef.value.indeterminate = props.indeterminate && !props.modelValue
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(syncIndeterminate)
|
||||
watch(() => [props.indeterminate, props.modelValue], syncIndeterminate)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label :class="classes">
|
||||
<input
|
||||
ref="inputRef"
|
||||
:class="e('input')"
|
||||
type="checkbox"
|
||||
:checked="modelValue"
|
||||
:disabled="disabled"
|
||||
:name="name"
|
||||
:value="value"
|
||||
@change="onChange"
|
||||
@focus="emit('focus', $event)"
|
||||
@blur="emit('blur', $event)"
|
||||
/>
|
||||
<span :class="e('box')" aria-hidden="true">
|
||||
<component :is="icon" v-if="icon" />
|
||||
</span>
|
||||
<span v-if="$slots.default" :class="e('label')">
|
||||
<slot />
|
||||
</span>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@use '@g3soft/tokens/scss' as *;
|
||||
|
||||
.#{b('checkbox')} {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: v('space-2', 8px);
|
||||
color: v('text-1', rgba(0, 0, 0, 0.88));
|
||||
font-size: v('font-size', 14px);
|
||||
line-height: v('line-height', 1.5);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.#{e('checkbox', 'input')} {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.#{e('checkbox', 'box')} {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: v('checkbox-size', 16px);
|
||||
height: v('checkbox-size', 16px);
|
||||
border: 1px solid v('border-color', #d9d9d9);
|
||||
border-radius: v('radius-sm', 4px);
|
||||
background: v('bg-container', #fff);
|
||||
color: v('text-inverse', #fff);
|
||||
transition:
|
||||
background-color v('duration', 0.2s) v('ease', ease),
|
||||
border-color v('duration', 0.2s) v('ease', ease);
|
||||
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.#{b('checkbox')}:hover:not(.#{is('disabled')}) .#{e('checkbox', 'box')} {
|
||||
border-color: v('color-primary', #18181b);
|
||||
}
|
||||
|
||||
.#{b('checkbox')}.#{is('checked')} .#{e('checkbox', 'box')},
|
||||
.#{b('checkbox')}.#{is('indeterminate')} .#{e('checkbox', 'box')} {
|
||||
border-color: v('color-primary', #18181b);
|
||||
background: v('color-primary', #18181b);
|
||||
}
|
||||
|
||||
.#{e('checkbox', 'input')}:focus-visible + .#{e('checkbox', 'box')} {
|
||||
@include focus-ring();
|
||||
}
|
||||
|
||||
.#{b('checkbox')}.#{is('disabled')} {
|
||||
color: v('text-disabled', rgba(0, 0, 0, 0.25));
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.#{b('checkbox')}.#{is('disabled')} .#{e('checkbox', 'box')} {
|
||||
border-color: v('border-color', #d9d9d9);
|
||||
background: v('bg-disabled', rgba(0, 0, 0, 0.04));
|
||||
color: v('text-disabled', rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user