Skip to content

Commit 1f19441

Browse files
committed
feat(switch): added array support via useCheckbox composable
1 parent 671558b commit 1f19441

File tree

6 files changed

+173
-14
lines changed

6 files changed

+173
-14
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script lang="ts" setup>
2+
import { ref } from 'vue'
3+
4+
const enabledNotifications = ref([])
5+
</script>
6+
7+
<template>
8+
<div class="grid-row place-items-stretch">
9+
<ASwitch
10+
v-model="enabledNotifications"
11+
value="accountActivity"
12+
label="accountActivity"
13+
/>
14+
<ASwitch
15+
v-model="enabledNotifications"
16+
value="comment"
17+
label="comment"
18+
/>
19+
<ASwitch
20+
v-model="enabledNotifications"
21+
value="like"
22+
label="like"
23+
/>
24+
<ASwitch
25+
v-model="enabledNotifications"
26+
value="mention"
27+
label="mention"
28+
/>
29+
<ASwitch
30+
v-model="enabledNotifications"
31+
value="follow"
32+
label="follow"
33+
/>
34+
35+
<p>Enabled Notifications: {{ enabledNotifications }}</p>
36+
</div>
37+
</template>

docs/guide/components/switch.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,17 @@ Use `on-icon` & `off-icon` prop to render icons inside switch dot.
5656

5757
::::
5858

59+
<!-- 👉 Array -->
60+
::::card Array
61+
62+
Just like `ACheckbox`, `ASwitch` also supports array.
63+
64+
:::code DemoSwitchArray
65+
<<< @/components/demos/switch/DemoSwitchArray.vue
66+
:::
67+
68+
::::
69+
5970
<!-- Custom model values -->
6071
::::card Custom model values
6172

packages/anu-vue/src/components/switch/ASwitch.vue

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script lang="ts" setup>
22
import { defu } from 'defu'
3-
import type { ExtractPropTypes } from 'vue'
3+
import type { ExtractPropTypes, PropType } from 'vue'
4+
import { useCheckbox } from '@/composables'
45
import { color as colorProp, disabled as disabledProp } from '@/composables/useProps'
56
67
const props = defineProps({
@@ -20,23 +21,20 @@ const props = defineProps({
2021
* Bind v-model value
2122
*/
2223
modelValue: {
23-
type: [Boolean, Number, String],
24+
type: [Boolean, Number, String, Array, Set] as PropType<string | number | boolean | unknown[]>,
2425
default: true,
2526
},
2627
2728
/**
2829
* Switch value when in on state
2930
*/
30-
onValue: {
31-
type: [Boolean, Number, String],
32-
default: true,
33-
},
31+
onValue: [Boolean, Number, String, Array, Set] as PropType<string | number | boolean | unknown[]>,
3432
3533
/**
3634
* Switch value when in off state
3735
*/
3836
offValue: {
39-
type: [Boolean, Number, String],
37+
type: [Boolean, Number, String, Array, Set] as PropType<string | number | boolean | unknown[]>,
4038
default: false,
4139
},
4240
@@ -50,6 +48,11 @@ const props = defineProps({
5048
*/
5149
offIcon: String,
5250
51+
/**
52+
* Bind classes to input element
53+
*/
54+
inputClasses: { type: null },
55+
5356
/**
5457
* Disable switch
5558
*/
@@ -61,6 +64,7 @@ const emit = defineEmits<{
6164
6265
defineOptions({
6366
name: 'ASwitch',
67+
inheritAttrs: false,
6468
})
6569
6670
defineSlots<{
@@ -73,12 +77,8 @@ defineSlots<{
7377
7478
const attrs = useAttrs()
7579
76-
const isChecked = computed(() => props.modelValue === props.onValue)
77-
78-
const handleChange = () => {
79-
const val = isChecked.value ? props.offValue : props.onValue
80-
emit('update:modelValue', val)
81-
}
80+
const _trueValue = computed(() => props.onValue || attrs.value || true)
81+
const { isChecked, onChange } = useCheckbox(toRef(props, 'modelValue'), emit, _trueValue, toRef(props, 'offValue'))
8282
8383
const elementId = `a-switch-${attrs.id || attrs.value}-${Math.random().toString(36).slice(2, 7)}`
8484
@@ -94,6 +94,7 @@ const dotPosition = computed(() => {
9494
:for="elementId"
9595
class="a-switch cursor-pointer rounded-full justify-between items-center"
9696
:class="[
97+
$attrs.class,
9798
props.label || $slots.default
9899
? 'flex'
99100
: 'inline-flex',
@@ -102,11 +103,13 @@ const dotPosition = computed(() => {
102103
>
103104

104105
<input
106+
v-bind="{ ...$attrs, class: props.inputClasses }"
105107
:id="elementId"
108+
:checked="isChecked"
106109
class="hidden"
107110
role="switch"
108111
type="checkbox"
109-
@change="handleChange"
112+
@change="onChange"
110113
>
111114

112115
<!-- 👉 Label -->

packages/anu-vue/src/composables/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './useAnu'
2+
export * from './useCheckbox'
23
export * from './useColor'
34
export * from './useConfigurable'
45
export * from './useDOMScrollLock'
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* `useCheckbox` vue composable using VueUse library that accepts v-model for checkbox state
3+
* It accepts modelValue of type boolean, string, number, array of unknown
4+
* Moreover, it also accepts trueValue and falseValue for customizing the values for true and false states
5+
*/
6+
7+
import type { MaybeRef } from '@vueuse/core'
8+
9+
export function useCheckbox(
10+
modelValue: MaybeRef<string | number | boolean | unknown[]>,
11+
emit: (event: string, ...args: any[]) => void,
12+
trueValue: MaybeRef<string | number | boolean> = true,
13+
falseValue: MaybeRef<string | number | boolean> = false,
14+
) {
15+
const handleModelValueChange = (val: typeof modelValue) => {
16+
const _modelValue = resolveUnref(modelValue)
17+
const _trueValue = resolveUnref(trueValue)
18+
const _falseValue = resolveUnref(falseValue)
19+
20+
if (Array.isArray(_modelValue)) {
21+
if (val)
22+
emit('update:modelValue', [..._modelValue, _trueValue])
23+
24+
else
25+
emit('update:modelValue', _modelValue.filter((item: any) => item !== _trueValue))
26+
}
27+
else {
28+
emit('update:modelValue', val ? _trueValue : _falseValue)
29+
}
30+
}
31+
32+
const onChange = (e: Event) => {
33+
handleModelValueChange((e.target as HTMLInputElement).checked)
34+
}
35+
36+
const isChecked = computed({
37+
get: () => {
38+
const _modelValue = resolveUnref(modelValue)
39+
const _trueValue = resolveUnref(trueValue)
40+
41+
if (Array.isArray(_modelValue))
42+
return _modelValue.includes(_trueValue)
43+
44+
return _modelValue === _trueValue
45+
},
46+
set: handleModelValueChange,
47+
})
48+
49+
return {
50+
isChecked,
51+
onChange,
52+
}
53+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { describe, expect, it, vitest } from 'vitest'
2+
import { useCheckbox } from '../../src/composables/useCheckbox'
3+
4+
describe('useCheckbox', () => {
5+
it('should have `isChecked` as `false` when `modelValue` is `false`', () => {
6+
const modelValue = ref(false)
7+
const emitMock = vitest.fn()
8+
const trueValue = true
9+
const falseValue = false
10+
11+
const { isChecked } = useCheckbox(modelValue, emitMock, trueValue, falseValue)
12+
13+
expect(isChecked.value).toBe(false)
14+
})
15+
16+
it('should have `isChecked` as `true` when `modelValue` is changed to `true`', () => {
17+
const modelValue = ref(false)
18+
const emitMock = vitest.fn()
19+
const trueValue = true
20+
const falseValue = false
21+
22+
const { isChecked } = useCheckbox(modelValue, emitMock, trueValue, falseValue)
23+
24+
modelValue.value = true
25+
26+
expect(isChecked.value).toBe(true)
27+
28+
// TODO: Why it isn't working?
29+
// Check if `emit` was called with `update:modelValue` and `true` as arguments
30+
// expect(emitMock).toBeCalledTimes(1)
31+
// expect(emitMock).toHaveBeenCalledWith('update:modelValue', true)
32+
})
33+
34+
// Test passing trueValue and falseValue
35+
it('should have `isChecked` as `true` when `modelValue` is changed to `true`', () => {
36+
const trueValue = 'on'
37+
const falseValue = 'off'
38+
39+
const modelValue = ref(falseValue)
40+
const emitMock = vitest.fn()
41+
42+
const { isChecked, onChange } = useCheckbox(modelValue, emitMock, trueValue, falseValue)
43+
44+
expect(isChecked.value).toBe(false)
45+
46+
modelValue.value = trueValue
47+
expect(isChecked.value).toBe(true)
48+
49+
onChange({ target: { checked: false } } as unknown as Event)
50+
51+
// TODO: Why it isn't working?
52+
// expect(modelValue.value).toBe(falseValue)
53+
})
54+
})

0 commit comments

Comments
 (0)