| 1 | --- |
| 2 | editLink: false |
| 3 | footer: false |
| 4 | aside: false |
| 5 | outline: false |
| 6 | sidebar: false |
| 7 | pageClass: all-features-page |
| 8 | --- |
| 9 | |
| 10 | <script setup lang="ts"> |
| 11 | import { useUrlSearchParams } from '@vueuse/core' |
| 12 | import { computed, toRef, ref } from 'vue' |
| 13 | import { withBase } from 'vitepress' |
| 14 | import { data as features } from './index.data' |
| 15 | |
| 16 | const query = useUrlSearchParams('hash-params', { removeFalsyValues: true }) |
| 17 | const search = toRef(query, 'search') as Ref<string | null> |
| 18 | const tags = toRef(query, 'tags') as Ref<string | null> |
| 19 | const tagsArr = computed({ |
| 20 | get: () => tags.value?.split(',').map(t => t.trim()).filter(Boolean) ?? [], |
| 21 | set: (val: string[]) => query.tags = val.join(','), |
| 22 | }) |
| 23 | |
| 24 | const filteredFeatures = computed(() => { |
| 25 | const s = search.value?.toLowerCase().trim() |
| 26 | const t = tagsArr.value |
| 27 | return Object.values(features).filter(feature => { |
| 28 | const matchSearch = !s || |
| 29 | feature.name.toLowerCase().includes(s) || |
| 30 | feature.title.toLowerCase().includes(s) || |
| 31 | feature.description.toLowerCase().includes(s) |
| 32 | |
| 33 | const matchTags = !t?.length || t.every(tag => |
| 34 | feature.tags?.some(featureTag => |
| 35 | featureTag.toLowerCase() === tag.toLowerCase() |
| 36 | ) |
| 37 | ) |
| 38 | return matchSearch && matchTags |
| 39 | }) |
| 40 | }) |
| 41 | |
| 42 | function resetFilters() { |
| 43 | query.search = null |
| 44 | query.tags = null |
| 45 | } |
| 46 | |
| 47 | function removeTag(tag: string) { |
| 48 | tagsArr.value = tagsArr.value.filter(t => t !== tag) |
| 49 | } |
| 50 | </script> |
| 51 | |
| 52 | # Features |
| 53 | |
| 54 | This is a list of all the individual features that Slidev provides. Each feature can be used independently and is optional. |
| 55 | |
| 56 | You can also read <LinkInline link="guide/" /> to learn the features by topic. |
| 57 | |
| 58 | <ClientOnly> |
| 59 | <div flex items-center mt-6 gap-6> |
| 60 | <div |
| 61 | flex items-center rounded-md |
| 62 | px3 py2 gap-2 border-2 border-solid border-transparent |
| 63 | class="bg-$vp-c-bg-alt focus-within:border-color-$vp-c-brand" |
| 64 | > |
| 65 | <div class="i-carbon:search" text-sm op-80 /> |
| 66 | <input |
| 67 | v-model="search" |
| 68 | type="search" text-base |
| 69 | placeholder="Search features..." |
| 70 | /> |
| 71 | </div> |
| 72 | <div |
| 73 | v-if="tagsArr.length" |
| 74 | flex items-center gap-1 |
| 75 | > |
| 76 | <div class="i-carbon:tag" text-sm mr-1 op-80 /> |
| 77 | <FeatureTag v-for="tag in tagsArr" :key="tag" :tag removable @remove="removeTag(tag)"/> |
| 78 | </div> |
| 79 | </div> |
| 80 | |
| 81 | <FeaturesOverview :features="filteredFeatures" /> |
| 82 | |
| 83 | <div v-if="filteredFeatures.length === 0" class="w-full mt-6 op-80 flex flex-col items-center"> |
| 84 | No results found |
| 85 | <button class="block select-button flex-inline gap-1 items-center px-2 py-1 hover:bg-gray-400/10 rounded" @click="resetFilters()"> |
| 86 | <div class="i-carbon:filter-remove" /> |
| 87 | Clear Filters |
| 88 | </button> |
| 89 | </div> |
| 90 | </ClientOnly> |
| 91 | |
| 92 | <style> |
| 93 | .all-features-page .VPDoc > .container > .content { |
| 94 | max-width: 72vw !important; |
| 95 | } |
| 96 | </style> |
| 97 | |
| 98 | <style> |
| 99 | :root { |
| 100 | overflow-y: scroll; |
| 101 | } |
| 102 | </style> |
| 103 |