refactor(Command): adapt command component to use listbox

This commit is contained in:
zernonia 2024-12-10 12:33:34 +08:00
parent b5cf49e8ae
commit d7c4f34bab
23 changed files with 450 additions and 138 deletions

View File

@ -171,22 +171,22 @@
* Table
* -------------------------------------------------------------------------- */
.vp-doc table {
.vp-doc table:not(:where(.not-docs *)) {
@apply relative w-full overflow-hidden border-none text-sm;
}
.vp-doc tr {
.vp-doc tr:not(:where(.not-docs *)) {
@apply m-0 border-b;
}
.vp-doc tbody tr:last-child {
.vp-doc tbody:not(:where(.not-docs *)) tr:last-child {
@apply border-b-0;
}
.vp-doc th {
.vp-doc th:not(:where(.not-docs *)) {
@apply px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right;
}
.vp-doc td {
.vp-doc td:not(:where(.not-docs *)) {
@apply px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right;
}

View File

@ -660,7 +660,8 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"reka-ui"
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [
"utils",
@ -2783,7 +2784,8 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"reka-ui"
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [
"utils",

View File

@ -2,7 +2,8 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"reka-ui"
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [
"utils",
@ -11,7 +12,7 @@
"files": [
{
"path": "ui/command/Command.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n open: true,\n modelValue: '',\n})\n\nconst emits = defineEmits<ComboboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ComboboxRoot>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes, reactive, ref, watch } from 'vue'\nimport { provideCommandContext } from '.'\n\nconst props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n modelValue: '',\n})\n\nconst emits = defineEmits<ListboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst allItems = ref<Map<string, string>>(new Map())\nconst allGroups = ref<Map<string, Set<string>>>(new Map())\n\nconst { contains } = useFilter({ sensitivity: 'base' })\nconst filterState = reactive({\n search: '',\n filtered: {\n /** The count of all visible items. */\n count: 0,\n /** Map from visible item id to its search score. */\n items: new Map() as Map<string, number>,\n /** Set of groups with at least one visible item. */\n groups: new Set() as Set<string>,\n },\n})\n\nfunction filterItems() {\n if (!filterState.search) {\n filterState.filtered.count = allItems.value.size\n // Do nothing, each item will know to show itself because search is empty\n return\n }\n\n // Reset the groups\n filterState.filtered.groups = new Set()\n let itemCount = 0\n\n // Check which items should be included\n for (const [id, value] of allItems.value) {\n const score = contains(value, filterState.search)\n filterState.filtered.items.set(id, score ? 1 : 0)\n if (score)\n itemCount++\n }\n\n // Check which groups have at least 1 item shown\n for (const [groupId, group] of allGroups.value) {\n for (const itemId of group) {\n if (filterState.filtered.items.get(itemId)! > 0) {\n filterState.filtered.groups.add(groupId)\n break\n }\n }\n }\n\n filterState.filtered.count = itemCount\n}\n\nfunction handleSelect() {\n filterState.search = ''\n}\n\nwatch(() => filterState.search, () => {\n filterItems()\n})\n\nprovideCommandContext({\n allItems,\n allGroups,\n filterState,\n})\n</script>\n\n<template>\n <ListboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ListboxRoot>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -23,37 +24,37 @@
},
{
"path": "ui/command/CommandEmpty.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxEmptyProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxEmpty } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxEmpty v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </ComboboxEmpty>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Primitive } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\nconst props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { filterState } = useCommand()\nconst isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,\n)\n</script>\n\n<template>\n <Primitive v-if=\"isRender\" v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </Primitive>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxGroup, ComboboxLabel } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxGroup\n v-bind=\"delegatedProps\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n >\n <ComboboxLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ComboboxLabel>\n <slot />\n </ComboboxGroup>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted } from 'vue'\nimport { provideCommandGroupContext, useCommand } from '.'\n\nconst props = defineProps<ListboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { allGroups, filterState } = useCommand()\nconst id = useId()\n\nconst isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))\n\nprovideCommandGroupContext({ id })\nonMounted(() => {\n if (!allGroups.value.has(id))\n allGroups.value.set(id, new Set())\n})\nonUnmounted(() => {\n allGroups.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxGroup\n v-bind=\"delegatedProps\"\n :id=\"id\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n :hidden=\"isRender ? undefined : true\"\n >\n <ListboxGroupLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ListboxGroupLabel>\n <slot />\n </ListboxGroup>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandInput.vue",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ComboboxInputProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ComboboxInput\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n auto-focus\n :class=\"cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ListboxFilter, type ListboxFilterProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ListboxFilterProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n\nconst { filterState } = useCommand()\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ListboxFilter\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n v-model=\"filterState.search\"\n auto-focus\n :class=\"cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxItem, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ComboboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxItem\n v-bind=\"forwarded\"\n :class=\"cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', props.class)\"\n >\n <slot />\n </ComboboxItem>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { useCurrentElement } from '@vueuse/core'\nimport { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from 'vue'\nimport { useCommand, useCommandGroup } from '.'\n\nconst props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ListboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst id = useId()\nconst { filterState, allItems, allGroups } = useCommand()\nconst groupContext = useCommandGroup()\n\nconst isRender = computed(() => {\n if (!filterState.search) {\n return true\n }\n else {\n const filteredCurrentItem = filterState.filtered.items.get(id)\n // If the filtered items is undefined means not in the all times map yet\n // Do the first render to add into the map\n if (filteredCurrentItem === undefined) {\n return true\n }\n\n // Check with filter\n return filteredCurrentItem > 0\n }\n})\n\nconst itemRef = ref()\nconst currentElement = useCurrentElement(itemRef)\nonMounted(() => {\n if (!(currentElement.value instanceof HTMLElement))\n return\n\n // textValue to perform filter\n allItems.value.set(id, currentElement.value.textContent ?? props.value.toString())\n\n const groupId = groupContext?.id\n if (groupId) {\n if (!allGroups.value.has(groupId)) {\n allGroups.value.set(groupId, new Set([id]))\n }\n else {\n allGroups.value.get(groupId)?.add(id)\n }\n }\n})\nonUnmounted(() => {\n allItems.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxItem\n v-if=\"isRender\"\n v-bind=\"forwarded\"\n :id=\"id\"\n ref=\"itemRef\"\n :class=\"cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)\"\n @select=\"() => {\n filterState.search = ''\n }\"\n >\n <slot />\n </ListboxItem>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandList.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxContent, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {\n dismissable: false,\n})\nconst emits = defineEmits<ComboboxContentEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ComboboxContent>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxContent, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <ListboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ListboxContent>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandSeparator.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxSeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxSeparator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxSeparator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </ComboboxSeparator>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { SeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Separator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <Separator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </Separator>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -65,7 +66,7 @@
},
{
"path": "ui/command/index.ts",
"content": "export { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n",
"content": "import type { Ref } from 'vue'\nimport { createContext } from 'reka-ui'\n\nexport { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n\nexport const [useCommand, provideCommandContext] = createContext<{\n allItems: Ref<Map<string, string>>\n allGroups: Ref<Map<string, Set<string>>>\n filterState: {\n search: string\n filtered: { count: number, items: Map<string, number>, groups: Set<string> }\n }\n}>('Command')\n\nexport const [useCommandGroup, provideCommandGroupContext] = createContext<{\n id?: string\n}>('CommandGroup')\n",
"type": "registry:ui",
"target": ""
}

View File

@ -2,7 +2,8 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"reka-ui"
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [
"utils",
@ -11,7 +12,7 @@
"files": [
{
"path": "ui/command/Command.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n open: true,\n modelValue: '',\n})\n\nconst emits = defineEmits<ComboboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ComboboxRoot>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes, reactive, ref, watch } from 'vue'\nimport { provideCommandContext } from '.'\n\nconst props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n modelValue: '',\n})\n\nconst emits = defineEmits<ListboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst allItems = ref<Map<string, string>>(new Map())\nconst allGroups = ref<Map<string, Set<string>>>(new Map())\n\nconst { contains } = useFilter({ sensitivity: 'base' })\nconst filterState = reactive({\n search: '',\n filtered: {\n /** The count of all visible items. */\n count: 0,\n /** Map from visible item id to its search score. */\n items: new Map() as Map<string, number>,\n /** Set of groups with at least one visible item. */\n groups: new Set() as Set<string>,\n },\n})\n\nfunction filterItems() {\n if (!filterState.search) {\n filterState.filtered.count = allItems.value.size\n // Do nothing, each item will know to show itself because search is empty\n return\n }\n\n // Reset the groups\n filterState.filtered.groups = new Set()\n let itemCount = 0\n\n // Check which items should be included\n for (const [id, value] of allItems.value) {\n const score = contains(value, filterState.search)\n filterState.filtered.items.set(id, score ? 1 : 0)\n if (score)\n itemCount++\n }\n\n // Check which groups have at least 1 item shown\n for (const [groupId, group] of allGroups.value) {\n for (const itemId of group) {\n if (filterState.filtered.items.get(itemId)! > 0) {\n filterState.filtered.groups.add(groupId)\n break\n }\n }\n }\n\n filterState.filtered.count = itemCount\n}\n\nfunction handleSelect() {\n filterState.search = ''\n}\n\nwatch(() => filterState.search, () => {\n filterItems()\n})\n\nprovideCommandContext({\n allItems,\n allGroups,\n filterState,\n})\n</script>\n\n<template>\n <ListboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ListboxRoot>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -23,37 +24,37 @@
},
{
"path": "ui/command/CommandEmpty.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxEmptyProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxEmpty } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxEmpty v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </ComboboxEmpty>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Primitive } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\nconst props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { filterState } = useCommand()\nconst isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,\n)\n</script>\n\n<template>\n <Primitive v-if=\"isRender\" v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </Primitive>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxGroup, ComboboxLabel } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxGroup\n v-bind=\"delegatedProps\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n >\n <ComboboxLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ComboboxLabel>\n <slot />\n </ComboboxGroup>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted } from 'vue'\nimport { provideCommandGroupContext, useCommand } from '.'\n\nconst props = defineProps<ListboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { allGroups, filterState } = useCommand()\nconst id = useId()\n\nconst isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))\n\nprovideCommandGroupContext({ id })\nonMounted(() => {\n if (!allGroups.value.has(id))\n allGroups.value.set(id, new Set())\n})\nonUnmounted(() => {\n allGroups.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxGroup\n v-bind=\"delegatedProps\"\n :id=\"id\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n :hidden=\"isRender ? undefined : true\"\n >\n <ListboxGroupLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ListboxGroupLabel>\n <slot />\n </ListboxGroup>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandInput.vue",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ComboboxInputProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ComboboxInput\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n auto-focus\n :class=\"cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ListboxFilter, type ListboxFilterProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ListboxFilterProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n\nconst { filterState } = useCommand()\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ListboxFilter\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n v-model=\"filterState.search\"\n auto-focus\n :class=\"cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxItem, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ComboboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxItem\n v-bind=\"forwarded\"\n :class=\"cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)\"\n >\n <slot />\n </ComboboxItem>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { useCurrentElement } from '@vueuse/core'\nimport { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from 'vue'\nimport { useCommand, useCommandGroup } from '.'\n\nconst props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ListboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst id = useId()\nconst { filterState, allItems, allGroups } = useCommand()\nconst groupContext = useCommandGroup()\n\nconst isRender = computed(() => {\n if (!filterState.search) {\n return true\n }\n else {\n const filteredCurrentItem = filterState.filtered.items.get(id)\n // If the filtered items is undefined means not in the all times map yet\n // Do the first render to add into the map\n if (filteredCurrentItem === undefined) {\n return true\n }\n\n // Check with filter\n return filteredCurrentItem > 0\n }\n})\n\nconst itemRef = ref()\nconst currentElement = useCurrentElement(itemRef)\nonMounted(() => {\n if (!(currentElement.value instanceof HTMLElement))\n return\n\n // textValue to perform filter\n allItems.value.set(id, currentElement.value.textContent ?? props.value.toString())\n\n const groupId = groupContext?.id\n if (groupId) {\n if (!allGroups.value.has(groupId)) {\n allGroups.value.set(groupId, new Set([id]))\n }\n else {\n allGroups.value.get(groupId)?.add(id)\n }\n }\n})\nonUnmounted(() => {\n allItems.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxItem\n v-if=\"isRender\"\n v-bind=\"forwarded\"\n :id=\"id\"\n ref=\"itemRef\"\n :class=\"cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)\"\n @select=\"() => {\n filterState.search = ''\n }\"\n >\n <slot />\n </ListboxItem>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandList.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxContent, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {\n dismissable: false,\n})\nconst emits = defineEmits<ComboboxContentEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ComboboxContent>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxContent, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <ListboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ListboxContent>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandSeparator.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxSeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxSeparator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxSeparator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </ComboboxSeparator>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { SeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Separator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <Separator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </Separator>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -65,7 +66,7 @@
},
{
"path": "ui/command/index.ts",
"content": "export { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n",
"content": "import type { Ref } from 'vue'\nimport { createContext } from 'reka-ui'\n\nexport { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n\nexport const [useCommand, provideCommandContext] = createContext<{\n allItems: Ref<Map<string, string>>\n allGroups: Ref<Map<string, Set<string>>>\n filterState: {\n search: string\n filtered: { count: number, items: Map<string, number>, groups: Set<string> }\n }\n}>('Command')\n\nexport const [useCommandGroup, provideCommandGroupContext] = createContext<{\n id?: string\n}>('CommandGroup')\n",
"type": "registry:ui",
"target": ""
}

View File

@ -16,7 +16,7 @@
},
{
"path": "ui/radio-group/RadioGroupItem.vue",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Check } from 'lucide-vue-next'\nimport {\n RadioGroupIndicator,\n RadioGroupItem,\n type RadioGroupItemProps,\n useForwardProps,\n} from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<RadioGroupItemProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <RadioGroupItem\n v-bind=\"forwardedProps\"\n :class=\"\n cn(\n 'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <RadioGroupIndicator class=\"flex items-center justify-center\">\n <Check class=\"h-3.5 w-3.5 fill-primary\" />\n </RadioGroupIndicator>\n </RadioGroupItem>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Check } from 'lucide-vue-next'\nimport {\n RadioGroupIndicator,\n RadioGroupItem,\n type RadioGroupItemProps,\n useForwardProps,\n} from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<RadioGroupItemProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <RadioGroupItem\n v-bind=\"forwardedProps\"\n :class=\"\n cn(\n 'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <RadioGroupIndicator class=\"flex items-center justify-center\">\n <Check class=\"h-3.5 w-3.5 text-primary\" />\n </RadioGroupIndicator>\n </RadioGroupItem>\n</template>\n",
"type": "registry:ui",
"target": ""
},

View File

@ -1050,7 +1050,8 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"reka-ui"
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [
"utils",
@ -1059,7 +1060,7 @@
"files": [
{
"path": "ui/command/Command.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n open: true,\n modelValue: '',\n})\n\nconst emits = defineEmits<ComboboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ComboboxRoot>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes, reactive, ref, watch } from 'vue'\nimport { provideCommandContext } from '.'\n\nconst props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n modelValue: '',\n})\n\nconst emits = defineEmits<ListboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst allItems = ref<Map<string, string>>(new Map())\nconst allGroups = ref<Map<string, Set<string>>>(new Map())\n\nconst { contains } = useFilter({ sensitivity: 'base' })\nconst filterState = reactive({\n search: '',\n filtered: {\n /** The count of all visible items. */\n count: 0,\n /** Map from visible item id to its search score. */\n items: new Map() as Map<string, number>,\n /** Set of groups with at least one visible item. */\n groups: new Set() as Set<string>,\n },\n})\n\nfunction filterItems() {\n if (!filterState.search) {\n filterState.filtered.count = allItems.value.size\n // Do nothing, each item will know to show itself because search is empty\n return\n }\n\n // Reset the groups\n filterState.filtered.groups = new Set()\n let itemCount = 0\n\n // Check which items should be included\n for (const [id, value] of allItems.value) {\n const score = contains(value, filterState.search)\n filterState.filtered.items.set(id, score ? 1 : 0)\n if (score)\n itemCount++\n }\n\n // Check which groups have at least 1 item shown\n for (const [groupId, group] of allGroups.value) {\n for (const itemId of group) {\n if (filterState.filtered.items.get(itemId)! > 0) {\n filterState.filtered.groups.add(groupId)\n break\n }\n }\n }\n\n filterState.filtered.count = itemCount\n}\n\nfunction handleSelect() {\n filterState.search = ''\n}\n\nwatch(() => filterState.search, () => {\n filterItems()\n})\n\nprovideCommandContext({\n allItems,\n allGroups,\n filterState,\n})\n</script>\n\n<template>\n <ListboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ListboxRoot>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -1071,37 +1072,37 @@
},
{
"path": "ui/command/CommandEmpty.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxEmptyProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxEmpty } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxEmpty v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </ComboboxEmpty>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Primitive } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\nconst props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { filterState } = useCommand()\nconst isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,\n)\n</script>\n\n<template>\n <Primitive v-if=\"isRender\" v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </Primitive>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxGroup, ComboboxLabel } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxGroup\n v-bind=\"delegatedProps\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n >\n <ComboboxLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ComboboxLabel>\n <slot />\n </ComboboxGroup>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted } from 'vue'\nimport { provideCommandGroupContext, useCommand } from '.'\n\nconst props = defineProps<ListboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { allGroups, filterState } = useCommand()\nconst id = useId()\n\nconst isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))\n\nprovideCommandGroupContext({ id })\nonMounted(() => {\n if (!allGroups.value.has(id))\n allGroups.value.set(id, new Set())\n})\nonUnmounted(() => {\n allGroups.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxGroup\n v-bind=\"delegatedProps\"\n :id=\"id\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n :hidden=\"isRender ? undefined : true\"\n >\n <ListboxGroupLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ListboxGroupLabel>\n <slot />\n </ListboxGroup>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandInput.vue",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ComboboxInputProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ComboboxInput\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n auto-focus\n :class=\"cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ListboxFilter, type ListboxFilterProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ListboxFilterProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n\nconst { filterState } = useCommand()\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ListboxFilter\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n v-model=\"filterState.search\"\n auto-focus\n :class=\"cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxItem, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ComboboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxItem\n v-bind=\"forwarded\"\n :class=\"cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)\"\n >\n <slot />\n </ComboboxItem>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { useCurrentElement } from '@vueuse/core'\nimport { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from 'vue'\nimport { useCommand, useCommandGroup } from '.'\n\nconst props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ListboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst id = useId()\nconst { filterState, allItems, allGroups } = useCommand()\nconst groupContext = useCommandGroup()\n\nconst isRender = computed(() => {\n if (!filterState.search) {\n return true\n }\n else {\n const filteredCurrentItem = filterState.filtered.items.get(id)\n // If the filtered items is undefined means not in the all times map yet\n // Do the first render to add into the map\n if (filteredCurrentItem === undefined) {\n return true\n }\n\n // Check with filter\n return filteredCurrentItem > 0\n }\n})\n\nconst itemRef = ref()\nconst currentElement = useCurrentElement(itemRef)\nonMounted(() => {\n if (!(currentElement.value instanceof HTMLElement))\n return\n\n // textValue to perform filter\n allItems.value.set(id, currentElement.value.textContent ?? props.value.toString())\n\n const groupId = groupContext?.id\n if (groupId) {\n if (!allGroups.value.has(groupId)) {\n allGroups.value.set(groupId, new Set([id]))\n }\n else {\n allGroups.value.get(groupId)?.add(id)\n }\n }\n})\nonUnmounted(() => {\n allItems.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxItem\n v-if=\"isRender\"\n v-bind=\"forwarded\"\n :id=\"id\"\n ref=\"itemRef\"\n :class=\"cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)\"\n @select=\"() => {\n filterState.search = ''\n }\"\n >\n <slot />\n </ListboxItem>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandList.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxContent, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {\n dismissable: false,\n})\nconst emits = defineEmits<ComboboxContentEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ComboboxContent>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxContent, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <ListboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ListboxContent>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandSeparator.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxSeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxSeparator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxSeparator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </ComboboxSeparator>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { SeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Separator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <Separator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </Separator>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -1113,7 +1114,7 @@
},
{
"path": "ui/command/index.ts",
"content": "export { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n",
"content": "import type { Ref } from 'vue'\nimport { createContext } from 'reka-ui'\n\nexport { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n\nexport const [useCommand, provideCommandContext] = createContext<{\n allItems: Ref<Map<string, string>>\n allGroups: Ref<Map<string, Set<string>>>\n filterState: {\n search: string\n filtered: { count: number, items: Map<string, number>, groups: Set<string> }\n }\n}>('Command')\n\nexport const [useCommandGroup, provideCommandGroupContext] = createContext<{\n id?: string\n}>('CommandGroup')\n",
"type": "registry:ui",
"target": ""
}
@ -2001,7 +2002,7 @@
},
{
"path": "ui/radio-group/RadioGroupItem.vue",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Check } from 'lucide-vue-next'\nimport {\n RadioGroupIndicator,\n RadioGroupItem,\n type RadioGroupItemProps,\n useForwardProps,\n} from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<RadioGroupItemProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <RadioGroupItem\n v-bind=\"forwardedProps\"\n :class=\"\n cn(\n 'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <RadioGroupIndicator class=\"flex items-center justify-center\">\n <Check class=\"h-3.5 w-3.5 fill-primary\" />\n </RadioGroupIndicator>\n </RadioGroupItem>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Check } from 'lucide-vue-next'\nimport {\n RadioGroupIndicator,\n RadioGroupItem,\n type RadioGroupItemProps,\n useForwardProps,\n} from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<RadioGroupItemProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <RadioGroupItem\n v-bind=\"forwardedProps\"\n :class=\"\n cn(\n 'aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',\n props.class,\n )\n \"\n >\n <RadioGroupIndicator class=\"flex items-center justify-center\">\n <Check class=\"h-3.5 w-3.5 text-primary\" />\n </RadioGroupIndicator>\n </RadioGroupItem>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -8342,7 +8343,8 @@
"name": "command",
"type": "registry:ui",
"dependencies": [
"reka-ui"
"reka-ui",
"@vueuse/core"
],
"registryDependencies": [
"utils",
@ -8351,7 +8353,7 @@
"files": [
{
"path": "ui/command/Command.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n open: true,\n modelValue: '',\n})\n\nconst emits = defineEmits<ComboboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ComboboxRoot>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes, reactive, ref, watch } from 'vue'\nimport { provideCommandContext } from '.'\n\nconst props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {\n modelValue: '',\n})\n\nconst emits = defineEmits<ListboxRootEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst allItems = ref<Map<string, string>>(new Map())\nconst allGroups = ref<Map<string, Set<string>>>(new Map())\n\nconst { contains } = useFilter({ sensitivity: 'base' })\nconst filterState = reactive({\n search: '',\n filtered: {\n /** The count of all visible items. */\n count: 0,\n /** Map from visible item id to its search score. */\n items: new Map() as Map<string, number>,\n /** Set of groups with at least one visible item. */\n groups: new Set() as Set<string>,\n },\n})\n\nfunction filterItems() {\n if (!filterState.search) {\n filterState.filtered.count = allItems.value.size\n // Do nothing, each item will know to show itself because search is empty\n return\n }\n\n // Reset the groups\n filterState.filtered.groups = new Set()\n let itemCount = 0\n\n // Check which items should be included\n for (const [id, value] of allItems.value) {\n const score = contains(value, filterState.search)\n filterState.filtered.items.set(id, score ? 1 : 0)\n if (score)\n itemCount++\n }\n\n // Check which groups have at least 1 item shown\n for (const [groupId, group] of allGroups.value) {\n for (const itemId of group) {\n if (filterState.filtered.items.get(itemId)! > 0) {\n filterState.filtered.groups.add(groupId)\n break\n }\n }\n }\n\n filterState.filtered.count = itemCount\n}\n\nfunction handleSelect() {\n filterState.search = ''\n}\n\nwatch(() => filterState.search, () => {\n filterItems()\n})\n\nprovideCommandContext({\n allItems,\n allGroups,\n filterState,\n})\n</script>\n\n<template>\n <ListboxRoot\n v-bind=\"forwarded\"\n :class=\"cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)\"\n >\n <slot />\n </ListboxRoot>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -8363,37 +8365,37 @@
},
{
"path": "ui/command/CommandEmpty.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxEmptyProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxEmpty } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxEmpty v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </ComboboxEmpty>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { PrimitiveProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Primitive } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\nconst props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { filterState } = useCommand()\nconst isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,\n)\n</script>\n\n<template>\n <Primitive v-if=\"isRender\" v-bind=\"delegatedProps\" :class=\"cn('py-6 text-center text-sm', props.class)\">\n <slot />\n </Primitive>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandGroup.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxGroup, ComboboxLabel } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxGroup\n v-bind=\"delegatedProps\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n >\n <ComboboxLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ComboboxLabel>\n <slot />\n </ComboboxGroup>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxGroupProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted } from 'vue'\nimport { provideCommandGroupContext, useCommand } from '.'\n\nconst props = defineProps<ListboxGroupProps & {\n class?: HTMLAttributes['class']\n heading?: string\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst { allGroups, filterState } = useCommand()\nconst id = useId()\n\nconst isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))\n\nprovideCommandGroupContext({ id })\nonMounted(() => {\n if (!allGroups.value.has(id))\n allGroups.value.set(id, new Set())\n})\nonUnmounted(() => {\n allGroups.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxGroup\n v-bind=\"delegatedProps\"\n :id=\"id\"\n :class=\"cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)\"\n :hidden=\"isRender ? undefined : true\"\n >\n <ListboxGroupLabel v-if=\"heading\" class=\"px-2 py-1.5 text-xs font-medium text-muted-foreground\">\n {{ heading }}\n </ListboxGroupLabel>\n <slot />\n </ListboxGroup>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandInput.vue",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ComboboxInputProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ComboboxInput\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n auto-focus\n :class=\"cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport { cn } from '@/lib/utils'\nimport { Search } from 'lucide-vue-next'\nimport { ListboxFilter, type ListboxFilterProps, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\nimport { useCommand } from '.'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst props = defineProps<ListboxFilterProps & {\n class?: HTMLAttributes['class']\n}>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwardedProps = useForwardProps(delegatedProps)\n\nconst { filterState } = useCommand()\n</script>\n\n<template>\n <div class=\"flex items-center border-b px-3\" cmdk-input-wrapper>\n <Search class=\"mr-2 h-4 w-4 shrink-0 opacity-50\" />\n <ListboxFilter\n v-bind=\"{ ...forwardedProps, ...$attrs }\"\n v-model=\"filterState.search\"\n auto-focus\n :class=\"cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)\"\n />\n </div>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandItem.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxItem, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ComboboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxItem\n v-bind=\"forwarded\"\n :class=\"cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', props.class)\"\n >\n <slot />\n </ComboboxItem>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { useCurrentElement } from '@vueuse/core'\nimport { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'\nimport { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from 'vue'\nimport { useCommand, useCommandGroup } from '.'\n\nconst props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()\nconst emits = defineEmits<ListboxItemEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n\nconst id = useId()\nconst { filterState, allItems, allGroups } = useCommand()\nconst groupContext = useCommandGroup()\n\nconst isRender = computed(() => {\n if (!filterState.search) {\n return true\n }\n else {\n const filteredCurrentItem = filterState.filtered.items.get(id)\n // If the filtered items is undefined means not in the all times map yet\n // Do the first render to add into the map\n if (filteredCurrentItem === undefined) {\n return true\n }\n\n // Check with filter\n return filteredCurrentItem > 0\n }\n})\n\nconst itemRef = ref()\nconst currentElement = useCurrentElement(itemRef)\nonMounted(() => {\n if (!(currentElement.value instanceof HTMLElement))\n return\n\n // textValue to perform filter\n allItems.value.set(id, currentElement.value.textContent ?? props.value.toString())\n\n const groupId = groupContext?.id\n if (groupId) {\n if (!allGroups.value.has(groupId)) {\n allGroups.value.set(groupId, new Set([id]))\n }\n else {\n allGroups.value.get(groupId)?.add(id)\n }\n }\n})\nonUnmounted(() => {\n allItems.value.delete(id)\n})\n</script>\n\n<template>\n <ListboxItem\n v-if=\"isRender\"\n v-bind=\"forwarded\"\n :id=\"id\"\n ref=\"itemRef\"\n :class=\"cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)\"\n @select=\"() => {\n filterState.search = ''\n }\"\n >\n <slot />\n </ListboxItem>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandList.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxContent, useForwardPropsEmits } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {\n dismissable: false,\n})\nconst emits = defineEmits<ComboboxContentEmits>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardPropsEmits(delegatedProps, emits)\n</script>\n\n<template>\n <ComboboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ComboboxContent>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { ListboxContentProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ListboxContent, useForwardProps } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n\nconst forwarded = useForwardProps(delegatedProps)\n</script>\n\n<template>\n <ListboxContent v-bind=\"forwarded\" :class=\"cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)\">\n <div role=\"presentation\">\n <slot />\n </div>\n </ListboxContent>\n</template>\n",
"type": "registry:ui",
"target": ""
},
{
"path": "ui/command/CommandSeparator.vue",
"content": "<script setup lang=\"ts\">\nimport type { ComboboxSeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { ComboboxSeparator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <ComboboxSeparator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </ComboboxSeparator>\n</template>\n",
"content": "<script setup lang=\"ts\">\nimport type { SeparatorProps } from 'reka-ui'\nimport { cn } from '@/lib/utils'\nimport { Separator } from 'reka-ui'\nimport { computed, type HTMLAttributes } from 'vue'\n\nconst props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()\n\nconst delegatedProps = computed(() => {\n const { class: _, ...delegated } = props\n\n return delegated\n})\n</script>\n\n<template>\n <Separator\n v-bind=\"delegatedProps\"\n :class=\"cn('-mx-1 h-px bg-border', props.class)\"\n >\n <slot />\n </Separator>\n</template>\n",
"type": "registry:ui",
"target": ""
},
@ -8405,7 +8407,7 @@
},
{
"path": "ui/command/index.ts",
"content": "export { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n",
"content": "import type { Ref } from 'vue'\nimport { createContext } from 'reka-ui'\n\nexport { default as Command } from './Command.vue'\nexport { default as CommandDialog } from './CommandDialog.vue'\nexport { default as CommandEmpty } from './CommandEmpty.vue'\nexport { default as CommandGroup } from './CommandGroup.vue'\nexport { default as CommandInput } from './CommandInput.vue'\nexport { default as CommandItem } from './CommandItem.vue'\nexport { default as CommandList } from './CommandList.vue'\nexport { default as CommandSeparator } from './CommandSeparator.vue'\nexport { default as CommandShortcut } from './CommandShortcut.vue'\n\nexport const [useCommand, provideCommandContext] = createContext<{\n allItems: Ref<Map<string, string>>\n allGroups: Ref<Map<string, Set<string>>>\n filterState: {\n search: string\n filtered: { count: number, items: Map<string, number>, groups: Set<string> }\n }\n}>('Command')\n\nexport const [useCommandGroup, provideCommandGroupContext] = createContext<{\n id?: string\n}>('CommandGroup')\n",
"type": "registry:ui",
"target": ""
}

View File

@ -1,15 +1,15 @@
<script setup lang="ts">
import type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'
import type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes, reactive, ref, watch } from 'vue'
import { provideCommandContext } from '.'
const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {
open: true,
const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {
modelValue: '',
})
const emits = defineEmits<ComboboxRootEmits>()
const emits = defineEmits<ListboxRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -18,13 +18,75 @@ const delegatedProps = computed(() => {
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const allItems = ref<Map<string, string>>(new Map())
const allGroups = ref<Map<string, Set<string>>>(new Map())
const { contains } = useFilter({ sensitivity: 'base' })
const filterState = reactive({
search: '',
filtered: {
/** The count of all visible items. */
count: 0,
/** Map from visible item id to its search score. */
items: new Map() as Map<string, number>,
/** Set of groups with at least one visible item. */
groups: new Set() as Set<string>,
},
})
function filterItems() {
if (!filterState.search) {
filterState.filtered.count = allItems.value.size
// Do nothing, each item will know to show itself because search is empty
return
}
// Reset the groups
filterState.filtered.groups = new Set()
let itemCount = 0
// Check which items should be included
for (const [id, value] of allItems.value) {
const score = contains(value, filterState.search)
filterState.filtered.items.set(id, score ? 1 : 0)
if (score)
itemCount++
}
// Check which groups have at least 1 item shown
for (const [groupId, group] of allGroups.value) {
for (const itemId of group) {
if (filterState.filtered.items.get(itemId)! > 0) {
filterState.filtered.groups.add(groupId)
break
}
}
}
filterState.filtered.count = itemCount
}
function handleSelect() {
filterState.search = ''
}
watch(() => filterState.search, () => {
filterItems()
})
provideCommandContext({
allItems,
allGroups,
filterState,
})
</script>
<template>
<ComboboxRoot
<ListboxRoot
v-bind="forwarded"
:class="cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)"
>
<slot />
</ComboboxRoot>
</ListboxRoot>
</template>

View File

@ -1,20 +1,25 @@
<script setup lang="ts">
import type { ComboboxEmptyProps } from 'reka-ui'
import type { PrimitiveProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxEmpty } from 'reka-ui'
import { Primitive } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { useCommand } from '.'
const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()
const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const { filterState } = useCommand()
const isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,
)
</script>
<template>
<ComboboxEmpty v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)">
<Primitive v-if="isRender" v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)">
<slot />
</ComboboxEmpty>
</Primitive>
</template>

View File

@ -1,10 +1,11 @@
<script setup lang="ts">
import type { ComboboxGroupProps } from 'reka-ui'
import type { ListboxGroupProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxGroup, ComboboxLabel } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'
import { computed, type HTMLAttributes, onMounted, onUnmounted } from 'vue'
import { provideCommandGroupContext, useCommand } from '.'
const props = defineProps<ComboboxGroupProps & {
const props = defineProps<ListboxGroupProps & {
class?: HTMLAttributes['class']
heading?: string
}>()
@ -14,16 +15,32 @@ const delegatedProps = computed(() => {
return delegated
})
const { allGroups, filterState } = useCommand()
const id = useId()
const isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))
provideCommandGroupContext({ id })
onMounted(() => {
if (!allGroups.value.has(id))
allGroups.value.set(id, new Set())
})
onUnmounted(() => {
allGroups.value.delete(id)
})
</script>
<template>
<ComboboxGroup
<ListboxGroup
v-bind="delegatedProps"
:id="id"
:class="cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)"
:hidden="isRender ? undefined : true"
>
<ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
<ListboxGroupLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
{{ heading }}
</ComboboxLabel>
</ListboxGroupLabel>
<slot />
</ComboboxGroup>
</ListboxGroup>
</template>

View File

@ -1,14 +1,15 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { Search } from 'lucide-vue-next'
import { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'reka-ui'
import { ListboxFilter, type ListboxFilterProps, useForwardProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { useCommand } from '.'
defineOptions({
inheritAttrs: false,
})
const props = defineProps<ComboboxInputProps & {
const props = defineProps<ListboxFilterProps & {
class?: HTMLAttributes['class']
}>()
@ -19,15 +20,18 @@ const delegatedProps = computed(() => {
})
const forwardedProps = useForwardProps(delegatedProps)
const { filterState } = useCommand()
</script>
<template>
<div class="flex items-center border-b px-3" cmdk-input-wrapper>
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<ComboboxInput
<ListboxFilter
v-bind="{ ...forwardedProps, ...$attrs }"
v-model="filterState.search"
auto-focus
:class="cn('flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
:class="cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
/>
</div>
</template>

View File

@ -1,11 +1,13 @@
<script setup lang="ts">
import type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui'
import type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxItem, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { useCurrentElement } from '@vueuse/core'
import { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'
import { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from 'vue'
import { useCommand, useCommandGroup } from '.'
const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ComboboxItemEmits>()
const props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ListboxItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -14,13 +16,63 @@ const delegatedProps = computed(() => {
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const id = useId()
const { filterState, allItems, allGroups } = useCommand()
const groupContext = useCommandGroup()
const isRender = computed(() => {
if (!filterState.search) {
return true
}
else {
const filteredCurrentItem = filterState.filtered.items.get(id)
// If the filtered items is undefined means not in the all times map yet
// Do the first render to add into the map
if (filteredCurrentItem === undefined) {
return true
}
// Check with filter
return filteredCurrentItem > 0
}
})
const itemRef = ref()
const currentElement = useCurrentElement(itemRef)
onMounted(() => {
if (!(currentElement.value instanceof HTMLElement))
return
// textValue to perform filter
allItems.value.set(id, currentElement.value.textContent ?? props.value.toString())
const groupId = groupContext?.id
if (groupId) {
if (!allGroups.value.has(groupId)) {
allGroups.value.set(groupId, new Set([id]))
}
else {
allGroups.value.get(groupId)?.add(id)
}
}
})
onUnmounted(() => {
allItems.value.delete(id)
})
</script>
<template>
<ComboboxItem
<ListboxItem
v-if="isRender"
v-bind="forwarded"
:class="cn('relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50', props.class)"
:id="id"
ref="itemRef"
:class="cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)"
@select="() => {
filterState.search = ''
}"
>
<slot />
</ComboboxItem>
</ListboxItem>
</template>

View File

@ -1,13 +1,10 @@
<script setup lang="ts">
import type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui'
import type { ListboxContentProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxContent, useForwardPropsEmits } from 'reka-ui'
import { ListboxContent, useForwardProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {
dismissable: false,
})
const emits = defineEmits<ComboboxContentEmits>()
const props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -15,13 +12,13 @@ const delegatedProps = computed(() => {
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
<ComboboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)">
<ListboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)">
<div role="presentation">
<slot />
</div>
</ComboboxContent>
</ListboxContent>
</template>

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
import type { ComboboxSeparatorProps } from 'reka-ui'
import type { SeparatorProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxSeparator } from 'reka-ui'
import { Separator } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()
const props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -14,10 +14,10 @@ const delegatedProps = computed(() => {
</script>
<template>
<ComboboxSeparator
<Separator
v-bind="delegatedProps"
:class="cn('-mx-1 h-px bg-border', props.class)"
>
<slot />
</ComboboxSeparator>
</Separator>
</template>

View File

@ -1,3 +1,6 @@
import type { Ref } from 'vue'
import { createContext } from 'reka-ui'
export { default as Command } from './Command.vue'
export { default as CommandDialog } from './CommandDialog.vue'
export { default as CommandEmpty } from './CommandEmpty.vue'
@ -7,3 +10,16 @@ export { default as CommandItem } from './CommandItem.vue'
export { default as CommandList } from './CommandList.vue'
export { default as CommandSeparator } from './CommandSeparator.vue'
export { default as CommandShortcut } from './CommandShortcut.vue'
export const [useCommand, provideCommandContext] = createContext<{
allItems: Ref<Map<string, string>>
allGroups: Ref<Map<string, Set<string>>>
filterState: {
search: string
filtered: { count: number, items: Map<string, number>, groups: Set<string> }
}
}>('Command')
export const [useCommandGroup, provideCommandGroupContext] = createContext<{
id?: string
}>('CommandGroup')

View File

@ -1,15 +1,15 @@
<script setup lang="ts">
import type { ComboboxRootEmits, ComboboxRootProps } from 'reka-ui'
import type { ListboxRootEmits, ListboxRootProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { ListboxRoot, useFilter, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes, reactive, ref, watch } from 'vue'
import { provideCommandContext } from '.'
const props = withDefaults(defineProps<ComboboxRootProps & { class?: HTMLAttributes['class'] }>(), {
open: true,
const props = withDefaults(defineProps<ListboxRootProps & { class?: HTMLAttributes['class'] }>(), {
modelValue: '',
})
const emits = defineEmits<ComboboxRootEmits>()
const emits = defineEmits<ListboxRootEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -18,13 +18,75 @@ const delegatedProps = computed(() => {
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const allItems = ref<Map<string, string>>(new Map())
const allGroups = ref<Map<string, Set<string>>>(new Map())
const { contains } = useFilter({ sensitivity: 'base' })
const filterState = reactive({
search: '',
filtered: {
/** The count of all visible items. */
count: 0,
/** Map from visible item id to its search score. */
items: new Map() as Map<string, number>,
/** Set of groups with at least one visible item. */
groups: new Set() as Set<string>,
},
})
function filterItems() {
if (!filterState.search) {
filterState.filtered.count = allItems.value.size
// Do nothing, each item will know to show itself because search is empty
return
}
// Reset the groups
filterState.filtered.groups = new Set()
let itemCount = 0
// Check which items should be included
for (const [id, value] of allItems.value) {
const score = contains(value, filterState.search)
filterState.filtered.items.set(id, score ? 1 : 0)
if (score)
itemCount++
}
// Check which groups have at least 1 item shown
for (const [groupId, group] of allGroups.value) {
for (const itemId of group) {
if (filterState.filtered.items.get(itemId)! > 0) {
filterState.filtered.groups.add(groupId)
break
}
}
}
filterState.filtered.count = itemCount
}
function handleSelect() {
filterState.search = ''
}
watch(() => filterState.search, () => {
filterItems()
})
provideCommandContext({
allItems,
allGroups,
filterState,
})
</script>
<template>
<ComboboxRoot
<ListboxRoot
v-bind="forwarded"
:class="cn('flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground', props.class)"
>
<slot />
</ComboboxRoot>
</ListboxRoot>
</template>

View File

@ -1,20 +1,25 @@
<script setup lang="ts">
import type { ComboboxEmptyProps } from 'reka-ui'
import type { PrimitiveProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxEmpty } from 'reka-ui'
import { Primitive } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { useCommand } from '.'
const props = defineProps<ComboboxEmptyProps & { class?: HTMLAttributes['class'] }>()
const props = defineProps<PrimitiveProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
return delegated
})
const { filterState } = useCommand()
const isRender = computed(() => !!filterState.search && filterState.filtered.count === 0,
)
</script>
<template>
<ComboboxEmpty v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)">
<Primitive v-if="isRender" v-bind="delegatedProps" :class="cn('py-6 text-center text-sm', props.class)">
<slot />
</ComboboxEmpty>
</Primitive>
</template>

View File

@ -1,10 +1,11 @@
<script setup lang="ts">
import type { ComboboxGroupProps } from 'reka-ui'
import type { ListboxGroupProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxGroup, ComboboxLabel } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { ListboxGroup, ListboxGroupLabel, useId } from 'reka-ui'
import { computed, type HTMLAttributes, onMounted, onUnmounted } from 'vue'
import { provideCommandGroupContext, useCommand } from '.'
const props = defineProps<ComboboxGroupProps & {
const props = defineProps<ListboxGroupProps & {
class?: HTMLAttributes['class']
heading?: string
}>()
@ -14,16 +15,32 @@ const delegatedProps = computed(() => {
return delegated
})
const { allGroups, filterState } = useCommand()
const id = useId()
const isRender = computed(() => !filterState.search ? true : filterState.filtered.groups.has(id))
provideCommandGroupContext({ id })
onMounted(() => {
if (!allGroups.value.has(id))
allGroups.value.set(id, new Set())
})
onUnmounted(() => {
allGroups.value.delete(id)
})
</script>
<template>
<ComboboxGroup
<ListboxGroup
v-bind="delegatedProps"
:id="id"
:class="cn('overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground', props.class)"
:hidden="isRender ? undefined : true"
>
<ComboboxLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
<ListboxGroupLabel v-if="heading" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
{{ heading }}
</ComboboxLabel>
</ListboxGroupLabel>
<slot />
</ComboboxGroup>
</ListboxGroup>
</template>

View File

@ -1,14 +1,15 @@
<script setup lang="ts">
import { cn } from '@/lib/utils'
import { Search } from 'lucide-vue-next'
import { ComboboxInput, type ComboboxInputProps, useForwardProps } from 'reka-ui'
import { ListboxFilter, type ListboxFilterProps, useForwardProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { useCommand } from '.'
defineOptions({
inheritAttrs: false,
})
const props = defineProps<ComboboxInputProps & {
const props = defineProps<ListboxFilterProps & {
class?: HTMLAttributes['class']
}>()
@ -19,13 +20,16 @@ const delegatedProps = computed(() => {
})
const forwardedProps = useForwardProps(delegatedProps)
const { filterState } = useCommand()
</script>
<template>
<div class="flex items-center border-b px-3" cmdk-input-wrapper>
<Search class="mr-2 h-4 w-4 shrink-0 opacity-50" />
<ComboboxInput
<ListboxFilter
v-bind="{ ...forwardedProps, ...$attrs }"
v-model="filterState.search"
auto-focus
:class="cn('flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50', props.class)"
/>

View File

@ -1,11 +1,13 @@
<script setup lang="ts">
import type { ComboboxItemEmits, ComboboxItemProps } from 'reka-ui'
import type { ListboxItemEmits, ListboxItemProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxItem, useForwardPropsEmits } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
import { useCurrentElement } from '@vueuse/core'
import { ListboxItem, useForwardPropsEmits, useId } from 'reka-ui'
import { computed, type HTMLAttributes, onMounted, onUnmounted, ref } from 'vue'
import { useCommand, useCommandGroup } from '.'
const props = defineProps<ComboboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ComboboxItemEmits>()
const props = defineProps<ListboxItemProps & { class?: HTMLAttributes['class'] }>()
const emits = defineEmits<ListboxItemEmits>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -14,13 +16,63 @@ const delegatedProps = computed(() => {
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const id = useId()
const { filterState, allItems, allGroups } = useCommand()
const groupContext = useCommandGroup()
const isRender = computed(() => {
if (!filterState.search) {
return true
}
else {
const filteredCurrentItem = filterState.filtered.items.get(id)
// If the filtered items is undefined means not in the all times map yet
// Do the first render to add into the map
if (filteredCurrentItem === undefined) {
return true
}
// Check with filter
return filteredCurrentItem > 0
}
})
const itemRef = ref()
const currentElement = useCurrentElement(itemRef)
onMounted(() => {
if (!(currentElement.value instanceof HTMLElement))
return
// textValue to perform filter
allItems.value.set(id, currentElement.value.textContent ?? props.value.toString())
const groupId = groupContext?.id
if (groupId) {
if (!allGroups.value.has(groupId)) {
allGroups.value.set(groupId, new Set([id]))
}
else {
allGroups.value.get(groupId)?.add(id)
}
}
})
onUnmounted(() => {
allItems.value.delete(id)
})
</script>
<template>
<ComboboxItem
<ListboxItem
v-if="isRender"
v-bind="forwarded"
:id="id"
ref="itemRef"
:class="cn('relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0', props.class)"
@select="() => {
filterState.search = ''
}"
>
<slot />
</ComboboxItem>
</ListboxItem>
</template>

View File

@ -1,13 +1,10 @@
<script setup lang="ts">
import type { ComboboxContentEmits, ComboboxContentProps } from 'reka-ui'
import type { ListboxContentProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxContent, useForwardPropsEmits } from 'reka-ui'
import { ListboxContent, useForwardProps } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = withDefaults(defineProps<ComboboxContentProps & { class?: HTMLAttributes['class'] }>(), {
dismissable: false,
})
const emits = defineEmits<ComboboxContentEmits>()
const props = defineProps<ListboxContentProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -15,13 +12,13 @@ const delegatedProps = computed(() => {
return delegated
})
const forwarded = useForwardPropsEmits(delegatedProps, emits)
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
<ComboboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)">
<ListboxContent v-bind="forwarded" :class="cn('max-h-[300px] overflow-y-auto overflow-x-hidden', props.class)">
<div role="presentation">
<slot />
</div>
</ComboboxContent>
</ListboxContent>
</template>

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
import type { ComboboxSeparatorProps } from 'reka-ui'
import type { SeparatorProps } from 'reka-ui'
import { cn } from '@/lib/utils'
import { ComboboxSeparator } from 'reka-ui'
import { Separator } from 'reka-ui'
import { computed, type HTMLAttributes } from 'vue'
const props = defineProps<ComboboxSeparatorProps & { class?: HTMLAttributes['class'] }>()
const props = defineProps<SeparatorProps & { class?: HTMLAttributes['class'] }>()
const delegatedProps = computed(() => {
const { class: _, ...delegated } = props
@ -14,10 +14,10 @@ const delegatedProps = computed(() => {
</script>
<template>
<ComboboxSeparator
<Separator
v-bind="delegatedProps"
:class="cn('-mx-1 h-px bg-border', props.class)"
>
<slot />
</ComboboxSeparator>
</Separator>
</template>

View File

@ -1,3 +1,6 @@
import type { Ref } from 'vue'
import { createContext } from 'reka-ui'
export { default as Command } from './Command.vue'
export { default as CommandDialog } from './CommandDialog.vue'
export { default as CommandEmpty } from './CommandEmpty.vue'
@ -7,3 +10,16 @@ export { default as CommandItem } from './CommandItem.vue'
export { default as CommandList } from './CommandList.vue'
export { default as CommandSeparator } from './CommandSeparator.vue'
export { default as CommandShortcut } from './CommandShortcut.vue'
export const [useCommand, provideCommandContext] = createContext<{
allItems: Ref<Map<string, string>>
allGroups: Ref<Map<string, Set<string>>>
filterState: {
search: string
filtered: { count: number, items: Map<string, number>, groups: Set<string> }
}
}>('Command')
export const [useCommandGroup, provideCommandGroupContext] = createContext<{
id?: string
}>('CommandGroup')

View File

@ -31,7 +31,7 @@ const forwardedProps = useForwardProps(delegatedProps)
"
>
<RadioGroupIndicator class="flex items-center justify-center">
<Check class="h-3.5 w-3.5 fill-primary" />
<Check class="h-3.5 w-3.5 text-primary" />
</RadioGroupIndicator>
</RadioGroupItem>
</template>