/
VCheckbox.vue
190 lines (180 loc) · 5.08 KB
/
VCheckbox.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<template>
<label
:for="id"
class="relative flex text-sm leading-5"
:class="labelClasses"
>
<span class="relative block">
<!--
The checkbox focus style is a slight variation on the `focus-slim-tx` style.
Because it becomes filled when checked, it also needs the
`checked:focus-visible:border-white` class.
-->
<input
:id="id"
type="checkbox"
class="me-3 block appearance-none border border-dark-charcoal bg-white transition-colors duration-100 checked:bg-dark-charcoal disabled:border-dark-charcoal-40 disabled:bg-dark-charcoal-10 checked:disabled:border-dark-charcoal-40 checked:disabled:bg-dark-charcoal-40"
:class="
isSwitch
? ['h-4.5', 'w-9', 'rounded-full', 'focus-slim-offset']
: [
'h-5',
'w-5',
'rounded-sm',
'focus-slim-tx',
'checked:focus-visible:border-white',
]
"
v-bind="inputAttrs"
@click="onChange"
/>
<!-- Knob, for when `ifSwitch` is `true` -->
<span
v-if="isSwitch"
class="absolute left-0.75 top-0.75 block h-3 w-3 rounded-full transition-transform duration-100"
:class="
localCheckedState
? ['bg-white', 'translate-x-[1.125rem]']
: disabled
? ['bg-dark-charcoal-40']
: ['bg-dark-charcoal']
"
aria-hidden="true"
/>
<!-- Checkmark, for when `ifSwitch` is `false` -->
<VIcon
v-else
v-show="localCheckedState"
class="absolute inset-0 transform text-white"
name="check"
:size="5"
/>
</span>
<!-- @slot The checkbox label --><slot />
</label>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from "vue"
import { defineEvent } from "~/types/emits"
import VIcon from "~/components/VIcon/VIcon.vue"
type CheckboxAttrs = {
name: string
value: string
disabled?: boolean
checked?: boolean
}
/**
* A checkbox input component that allows selection of multiple options from a list .
*
* Unlike the native checkbox, this component only has two states: checked / not checked.
*/
export default defineComponent({
name: "VCheckbox",
components: {
VIcon,
},
props: {
/**
* Checkbox `id` is used for the input id property, connecting the label to
* the checkbox. id is also used in the `change` event payload as the `name`
* and `value` parameters if they are not set.
*/
id: {
type: String,
required: true,
},
/**
* Whether the checkbox is checked or not. No indeterminate state is allowed.
*
* @default false
*/
checked: {
type: Boolean,
default: false,
},
/**
* Usually this is the category that this checkbox belongs to, e.g. 'license' for a
* 'by-nc-nd' checkbox, or 'licenseType' for 'commercial' checkbox.
* This parameter is used in the emitted object.
*
* If the form submission is done natively, the name and value parameters are used
* when sending the form data to the server on form submit: if a checkbox is checked,
* `name=value` pair is added to the POST request body.
*
* If not set, the value of `id` prop is used instead.
*/
name: {
type: String,
required: false,
},
/**
* The value parameter in `name=value` pair sent in the POST request body, here
* emitted in the event's payload. Usually the code as 'by-nc-nd'.
*
* If not set, the value of `id` prop is used.
*/
value: {
type: String,
required: false,
},
/**
* Sets disabled property of the input and changes label opacity if set to true.
*/
disabled: {
type: Boolean,
default: false,
},
/**
* whether to make the checkbox appear like a switch
*/
isSwitch: {
type: Boolean,
default: false,
},
},
emits: {
change: defineEvent<[Omit<CheckboxAttrs, "disabled">]>(),
},
setup(props, { emit }) {
const localCheckedState = ref(props.checked || false)
const labelClasses = computed(() =>
props.disabled ? "text-dark-charcoal-70" : "text-dark-charcoal"
)
const inputAttrs = computed<CheckboxAttrs>(() => {
const attrs: CheckboxAttrs = {
name: props.name || props.id,
value: props.value || props.id,
}
if (props.disabled) {
attrs.disabled = true
}
if (localCheckedState.value) {
attrs.checked = true
}
return attrs
})
watch(
() => props.checked,
(checked) => {
if (checked !== localCheckedState.value) {
localCheckedState.value = checked
}
}
)
const onChange = () => {
localCheckedState.value = !localCheckedState.value
emit("change", {
name: inputAttrs.value.name,
value: inputAttrs.value.value,
checked: localCheckedState.value,
})
}
return {
localCheckedState,
labelClasses,
inputAttrs,
onChange,
}
},
})
</script>