radix-vue / shadcn-vue

Vue port of shadcn-ui
https://www.shadcn-vue.com/
MIT License
4.19k stars 242 forks source link

[Bug]: Checkbox can't work with array v-model #670

Closed kikky7 closed 1 month ago

kikky7 commented 1 month ago

Reproduction

I don't think it's required.

Describe the bug

This works because form.remember is boolean: <Checkbox id="remember" v-model:checked="form.remember" />

This is not working and I tried 10 different combinations (form.tags is an empty array), and tags is simple key:value object:

<template v-for="(value, key) in tags">
    <div class="flex items-center gap-x-2">
        <Checkbox :id="`tag-${key}`" v-model:checked="form.tags" :value="key" />
        <Label :for="`tag-${key}`" class="cursor-pointer select-none">{{ value }}</Label>
    </div>
</template>

I tried v-model="form.tags" and some other (desperate) solutions, none worked. I see there is an example using Form (vee-validate); I don't want to use this if I don't have too.

I am using Inertia.js useForm.

So I ended up creating my own checkbox component, just to see if it works, and it does (with the same code above), which works with booleans and arrays:

<script setup lang="ts">
import { computed, type HTMLAttributes } from "vue";
import { cn } from "@/lib/utils";

const emit = defineEmits(["update:checked"]);

const props = withDefaults(
  defineProps<{
    class?: HTMLAttributes["class"];
    checked?: boolean | Array<string>;
  }>(),
  {
    checked: false,
  }
);

const proxyChecked = computed({
  get() {
    return props.checked;
  },
  set(value) {
    emit("update:checked", value);
  },
});
</script>

<template>
  <input
    type="checkbox"
    v-model="proxyChecked"
    :class="
      cn(
        'peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
        props.class
      )
    "
  />
</template>

So it's bug somewhere with radix-vue probably, or if it's not, how to achieve this?

System Info

System:
    OS: macOS 14.5
    CPU: (12) arm64 Apple M2 Max
    Memory: 1.61 GB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 21.7.3 - ~/.nvm/versions/node/v21.7.3/bin/node
    npm: 10.5.0 - ~/.nvm/versions/node/v21.7.3/bin/npm
    pnpm: 8.9.0 - /opt/homebrew/bin/pnpm
  Browsers:
    Chrome: 126.0.6478.183
    Safari: 17.5
  npmPackages:
    @vueuse/core: ^10.11.0 => 10.11.0 
    radix-vue: ^1.9.1 => 1.9.1 
    vue: ^3.4.0 => 3.4.31

Contributes

kikky7 commented 1 month ago

I used this with the checkbox component, but I find it ugly solution, and would be great to support multicheck via v-model directly:

<Checkbox
    :id="`tag-${key}`"
    :value="key"
    :checked="form.tags.includes(key)"
    @update:checked="(isChecked) => isChecked ? form.tags.push(key) : form.tags.splice(form.tags.indexOf(key), 1)" />
sadeghbarati commented 1 month ago

The radix-vue checkbox already has the same props and emits, whether you want to use vee-validate or inertiajs useForm it should work with booleans and arrays

It may be better to create a minimal reproduction


I see there is an example using Form (vee-validate)

This example in shadcn-vue might help you, Checkbox with array, even if you don't want to use vee-validate

https://github.com/radix-vue/shadcn-vue/blob/dev/apps/www/src/examples/forms/components/DisplayForm.vue#L90C15-L97C17

https://www.shadcn-vue.com/examples/forms/display.html

sadeghbarati commented 1 month ago

Closing for now will reopen after reproduction

kikky7 commented 1 month ago

There is no need for reproduction, this <Checkbox id="tags" v-model:checked="form.tags" /> should work with booleans and arrays, but it's not as I represented with my custom component, but it works only with boolean.

Only way to make it work with array is this code, which is just dirty looking:

<Checkbox
    :id="`tag-${key}`"
    :value="key"
    :checked="form.tags.includes(key)"
    @update:checked="(isChecked) => isChecked ? form.tags.push(key) : form.tags.splice(form.tags.indexOf(key), 1)" />

If you want I can create PR to implement such behaviour into your current component, using radix-vue

sadeghbarati commented 1 month ago

Make a Vue Playground with Stackblitz please, see if we could help

With reproduction, we can check it ourselves to see if it is really a bug or not

https://stackblitz.com/edit/inertia-sails-vue-nkt3qm?file=package.json

Just fork this link and delete additional files and make it minimal and send it here