shadcn-vue icon indicating copy to clipboard operation
shadcn-vue copied to clipboard

[Feature]: Make it possible to use CommandInput with our own filtering method

Open maelp opened this issue 2 years ago • 9 comments
trafficstars

Describe the feature

I want to do a "CommandDialog" with a CommandInput but use Algolia to search in my database collections.

image

The issue I have is that if I use CommandInput, it automatically does the filtering using each CommandItem "value" but I want to avoid this since I create the CommandItem dynamically with the algolia search results, and the algolia filters are more complex than just filtering text on "value"

also there is a weird issue if I change the filters by typing, deleting, starting again, the "CommandItem" seem to be accessed in a random order (and some are not accessible) when using up and down keys? (you will see the selected item "jump around" sometimes, although I only press either "down key" multiple times, or "up key" multiple times, but I don't mix them)

https://github.com/radix-vue/shadcn-vue/assets/15458/fee362d0-a38d-409d-81b6-c70db90d430e

Additional information

  • [ ] I intend to submit a PR for this feature.
  • [ ] I have already implemented and/or tested this feature.

maelp avatar Nov 10 '23 09:11 maelp

To make it clear: I'd like a way to do the CommandDialog where the CommandInput only lets me select options with up/down, but does not filter on its own the content, so that I can use something like this to dynamically replace the result, and still use the "up/down/enter" selection

  <CommandDialog :open="open" @update:open="(v) => (open = v)">
    <ais-instant-search
      :search-client="searchClient"
      :index-name="searchIndexes.batteryAssemblies"
      :stalled-search-delay="200"
    >
      <ais-configure :hits-per-page.camel="5" />
      <ais-search-box placeholder="Type to search...">
        <template v-slot="{ refine }">
          <CommandInput
            @input="refine($event.currentTarget.value)"
            placeholder="Type to search..."
          ></CommandInput>
        </template>
      </ais-search-box>
      <algolia-loading-indicator />
      <CommandList>
        <CommandEmpty>No results found.</CommandEmpty>
        <CommandGroup
          :heading="category.title"
          v-for="category in categories"
          :key="category.value"
        >
          <ais-index :index-name="category.searchIndex">
            <ais-hits>
              <template #item="{ item }">
                <CommandItem
                  :value="item.id"
                  :key="item.id"
                  @click="
                    (event) => openCategoryId(event, category.value, item.id)
                  "
                >
                  {{ item.id }}
                </CommandItem>
              </template>
            </ais-hits>
          </ais-index>
        </CommandGroup>
      </CommandList>
    </ais-instant-search>
  </CommandDialog>

maelp avatar Nov 10 '23 09:11 maelp

Ha it seems it is possible to do what I want by ading the "filter-function" prop of "Command" to the "CommandDialog" helper

maelp avatar Nov 10 '23 09:11 maelp

Hmmm @zernonia I'm trying to do this to put the "filterFunction" on the Command in CommandDialog, but it complains because it also appears in "filtered" and therefore on "Dialog", do you know how to solve this?

<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
import { useForwardPropsEmits, VisuallyHidden } from "radix-vue";
import type { DialogRootEmits, DialogRootProps } from "radix-vue";
import Command from "./Command.vue";
import {
  Dialog,
  DialogContent,
  DialogTitle,
  DialogDescription,
} from "../index";

const props = defineProps<
  DialogRootProps & {
    filterFunction?: (val: Array<string | any>, term: string) => Array<any>;
  }
>();
const emits = defineEmits<DialogRootEmits>();

const forwarded = useForwardPropsEmits(props, emits);
</script>

<template>
  <Dialog v-bind="forwarded">
    <DialogContent class="p-0 overflow-hidden shadow-lg">
      <VisuallyHidden>
        <!-- TODO: would be better if the title could be updated via a prop, so it could be changed for each case -->
        <DialogTitle>Type command or search</DialogTitle>
        <DialogDescription
          >Type command or search</DialogDescription
        ></VisuallyHidden
      >
      <Command
        :filterFunction="props.filterFunction"
        class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
      >
        <slot />
      </Command>
    </DialogContent>
  </Dialog>
</template>

image

maelp avatar Nov 10 '23 09:11 maelp

Doing this now, but it feels like a hack... perhaps there's a better way to allow adding the props of "Command" on the "CommandDialog" to make it more flexible?

image

maelp avatar Nov 10 '23 09:11 maelp

Speaking of CommandDialog, I also had warnings from vue saying it misses a DialogTitle and a DialogDescription for accessibility, so I had to do this, there's probably a better way?

image

maelp avatar Nov 10 '23 09:11 maelp

Started working on this but need some more tests to make a PR, have some issues with output CommandItem component with v-for directive after API call (mock data for test)

hrynevychroman avatar Jan 16 '24 22:01 hrynevychroman

Don't work with Algolia previously, but will try to make a new Demo for such testcase, think that it will be helpful because every second website uses this type of search engine 😅

hrynevychroman avatar Jan 16 '24 22:01 hrynevychroman

Any progress on this?

For me I just added this to CommandDialog:

<script setup lang="ts">
import { useForwardPropsEmits } from "radix-vue";
import type { DialogRootEmits, DialogRootProps } from "radix-vue";
import {
  Command,
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@ui";
import { computed } from "vue";

// We define our own filterFunction, so we are able to use this with Algolia for instance
const props = defineProps<
  DialogRootProps & {
    filterFunction?: (val: Array<string | any>, term: string) => Array<any>;
  }
>();
const emits = defineEmits<DialogRootEmits>();

const forwarded = useForwardPropsEmits(props, emits);
const forwardedExceptFilterFunction = computed(() => {
  const { filterFunction: _, ...rest } = forwarded.value;
  return rest;
});
</script>

<template>
  <Dialog v-bind="forwardedExceptFilterFunction">
    <!-- Hidden header because otherwise the framework complains that there is no description -->
    <DialogHeader class="hidden">
      <DialogTitle></DialogTitle>
      <DialogDescription></DialogDescription>
    </DialogHeader>
    <DialogContent class="overflow-hidden p-0 shadow-lg">
      <Command
        :filter-function="filterFunction"
        class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
      >
        <slot />
      </Command>
    </DialogContent>
  </Dialog>
</template>

maelp avatar Mar 01 '24 09:03 maelp

Added a PR here @sadeghbarati https://github.com/radix-vue/shadcn-vue/pull/368

maelp avatar Mar 01 '24 10:03 maelp