global-search.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <script setup lang="ts">
  2. import type { MenuRecordRaw } from '@vben/types';
  3. import { ref } from 'vue';
  4. import { $t } from '@vben/locales';
  5. import {
  6. IcRoundArrowDownward,
  7. IcRoundArrowUpward,
  8. IcRoundSearch,
  9. IcRoundSubdirectoryArrowLeft,
  10. MdiKeyboardEsc,
  11. } from '@vben-core/iconify';
  12. import {
  13. Dialog,
  14. DialogContent,
  15. DialogDescription,
  16. DialogFooter,
  17. DialogHeader,
  18. DialogTitle,
  19. DialogTrigger,
  20. } from '@vben-core/shadcn-ui';
  21. import { isWindowsOs } from '@vben-core/toolkit';
  22. import { useMagicKeys, useToggle, whenever } from '@vueuse/core';
  23. import SearchPanel from './search-panel.vue';
  24. defineOptions({
  25. name: 'GlobalSearch',
  26. });
  27. const props = withDefaults(
  28. defineProps<{ enableShortcutKey?: boolean; menus: MenuRecordRaw[] }>(),
  29. {
  30. enableShortcutKey: true,
  31. menus: () => [],
  32. },
  33. );
  34. const [open, toggleOpen] = useToggle();
  35. const keyword = ref('');
  36. function handleClose() {
  37. open.value = false;
  38. keyword.value = '';
  39. }
  40. if (props.enableShortcutKey) {
  41. const keys = useMagicKeys();
  42. const cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k'];
  43. whenever(cmd, () => {
  44. if (props.enableShortcutKey) {
  45. open.value = true;
  46. }
  47. });
  48. }
  49. </script>
  50. <template>
  51. <div>
  52. <Dialog :open="open">
  53. <DialogTrigger as-child>
  54. <div
  55. class="md:bg-accent group flex h-8 cursor-pointer items-center gap-3 rounded-2xl border-none bg-none px-2 py-0.5 outline-none"
  56. @click="toggleOpen()"
  57. >
  58. <IcRoundSearch
  59. class="text-muted-foreground group-hover:text-foreground size-4 group-hover:opacity-100"
  60. />
  61. <span
  62. class="text-muted-foreground group-hover:text-foreground hidden text-sm duration-300 md:block"
  63. >
  64. {{ $t('widgets.search.title') }}
  65. </span>
  66. <span
  67. v-if="enableShortcutKey"
  68. class="bg-background border-foreground/60 text-muted-foreground group-hover:text-foreground relative hidden rounded-sm rounded-r-xl px-1.5 py-1 text-xs leading-none group-hover:opacity-100 md:block"
  69. >
  70. {{ isWindowsOs() ? 'Ctrl' : '⌘' }}
  71. <kbd>K</kbd>
  72. </span>
  73. <span v-else></span>
  74. </div>
  75. </DialogTrigger>
  76. <DialogContent
  77. class="top-0 h-full w-full -translate-y-0 border-none p-0 shadow-xl sm:top-[10%] sm:h-[unset] sm:w-[600px] sm:rounded-2xl"
  78. @close="handleClose"
  79. >
  80. <DialogHeader>
  81. <DialogTitle
  82. class="border-border flex h-12 items-center gap-5 border-b px-5 font-normal"
  83. >
  84. <IcRoundSearch class="mt-1 size-4" />
  85. <input
  86. v-model="keyword"
  87. :placeholder="$t('widgets.search.search-navigate')"
  88. class="ring-none placeholder:text-muted-foreground w-[80%] rounded-md border border-none bg-transparent p-2 text-sm outline-none ring-0 ring-offset-transparent focus-visible:ring-transparent"
  89. />
  90. </DialogTitle>
  91. <DialogDescription />
  92. </DialogHeader>
  93. <SearchPanel :keyword="keyword" :menus="menus" @close="handleClose" />
  94. <DialogFooter
  95. class="text-muted-foreground border-border hidden flex-row rounded-b-2xl border-t px-4 py-2 text-xs sm:flex sm:justify-start sm:gap-x-4"
  96. >
  97. <div class="flex items-center">
  98. <IcRoundSubdirectoryArrowLeft class="mr-1" />
  99. {{ $t('widgets.search.select') }}
  100. </div>
  101. <div class="flex items-center">
  102. <IcRoundArrowUpward class="mr-2" />
  103. <IcRoundArrowDownward class="mr-2" />
  104. {{ $t('widgets.search.navigate') }}
  105. </div>
  106. <div class="flex items-center">
  107. <MdiKeyboardEsc class="mr-1" />
  108. {{ $t('widgets.search.close') }}
  109. </div>
  110. </DialogFooter>
  111. </DialogContent>
  112. </Dialog>
  113. </div>
  114. </template>