WIP Implement search and watch prototype
This commit is contained in:
17
components/ui/dialog/Dialog.vue
Normal file
17
components/ui/dialog/Dialog.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogRoot, type DialogRootEmits, type DialogRootProps, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogRootProps>()
|
||||
const emits = defineEmits<DialogRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogRoot
|
||||
data-slot="dialog"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot />
|
||||
</DialogRoot>
|
||||
</template>
|
14
components/ui/dialog/DialogClose.vue
Normal file
14
components/ui/dialog/DialogClose.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogClose, type DialogCloseProps } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogCloseProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogClose
|
||||
data-slot="dialog-close"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</template>
|
47
components/ui/dialog/DialogContent.vue
Normal file
47
components/ui/dialog/DialogContent.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
type DialogContentEmits,
|
||||
type DialogContentProps,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import DialogOverlay from './DialogOverlay.vue'
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'], close?: () => void }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class', 'close')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
|
||||
const handleClose = () => {
|
||||
if (props.close) {
|
||||
props.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogContent data-slot="dialog-content" @escape-key-down="handleClose" @interact-outside="handleClose"
|
||||
@pointer-down-outside="handleClose" v-bind="forwarded" :class="cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class,
|
||||
)">
|
||||
<slot />
|
||||
|
||||
<DialogClose @click="handleClose"
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4">
|
||||
<X />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
22
components/ui/dialog/DialogDescription.vue
Normal file
22
components/ui/dialog/DialogDescription.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogDescription, type DialogDescriptionProps, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogDescription
|
||||
data-slot="dialog-description"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
15
components/ui/dialog/DialogFooter.vue
Normal file
15
components/ui/dialog/DialogFooter.vue
Normal file
@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
:class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
17
components/ui/dialog/DialogHeader.vue
Normal file
17
components/ui/dialog/DialogHeader.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
:class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
20
components/ui/dialog/DialogOverlay.vue
Normal file
20
components/ui/dialog/DialogOverlay.vue
Normal file
@ -0,0 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogOverlay, type DialogOverlayProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="dialog-overlay"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
</template>
|
56
components/ui/dialog/DialogScrollContent.vue
Normal file
56
components/ui/dialog/DialogScrollContent.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import {
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
type DialogContentEmits,
|
||||
type DialogContentProps,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||
>
|
||||
<DialogContent
|
||||
:class="
|
||||
cn(
|
||||
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
v-bind="forwarded"
|
||||
@pointer-down-outside="(event) => {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target as HTMLElement;
|
||||
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
|
||||
>
|
||||
<X class="w-4 h-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogOverlay>
|
||||
</DialogPortal>
|
||||
</template>
|
22
components/ui/dialog/DialogTitle.vue
Normal file
22
components/ui/dialog/DialogTitle.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogTitle, type DialogTitleProps, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTitle
|
||||
data-slot="dialog-title"
|
||||
v-bind="forwardedProps"
|
||||
:class="cn('text-lg leading-none font-semibold', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
14
components/ui/dialog/DialogTrigger.vue
Normal file
14
components/ui/dialog/DialogTrigger.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { DialogTrigger, type DialogTriggerProps } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DialogTrigger
|
||||
data-slot="dialog-trigger"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
</template>
|
10
components/ui/dialog/index.ts
Normal file
10
components/ui/dialog/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export { default as Dialog } from './Dialog.vue'
|
||||
export { default as DialogClose } from './DialogClose.vue'
|
||||
export { default as DialogContent } from './DialogContent.vue'
|
||||
export { default as DialogDescription } from './DialogDescription.vue'
|
||||
export { default as DialogFooter } from './DialogFooter.vue'
|
||||
export { default as DialogHeader } from './DialogHeader.vue'
|
||||
export { default as DialogOverlay } from './DialogOverlay.vue'
|
||||
export { default as DialogScrollContent } from './DialogScrollContent.vue'
|
||||
export { default as DialogTitle } from './DialogTitle.vue'
|
||||
export { default as DialogTrigger } from './DialogTrigger.vue'
|
25
components/ui/label/Label.vue
Normal file
25
components/ui/label/Label.vue
Normal file
@ -0,0 +1,25 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Label, type LabelProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Label
|
||||
data-slot="label"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
1
components/ui/label/index.ts
Normal file
1
components/ui/label/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Label } from './Label.vue'
|
Reference in New Issue
Block a user