Fix colors, replace Dropdown with Select, add clear layout
This commit is contained in:
@ -1,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { SidebarProvider } from '@/components/ui/sidebar';
|
import { SidebarProvider } from '@/components/ui/sidebar';
|
||||||
import AppSidebar from '@/components/ui/sidebar/AppSidebar.vue';
|
|
||||||
|
|
||||||
|
const defaultOpen = useCookie<boolean>("sidebar_state");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SidebarProvider>
|
<SidebarProvider :defaultOpen="defaultOpen">
|
||||||
<AppSidebar />
|
|
||||||
<NuxtLayout>
|
<NuxtLayout>
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sidebar-muted: oklch(0.97 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
@ -19,6 +23,7 @@
|
|||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
--color-muted: var(--muted);
|
--color-muted: var(--muted);
|
||||||
|
--color-sidebar-muted: var(--sidebar-muted);
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
@ -55,8 +60,9 @@
|
|||||||
--secondary: oklch(0.97 0 0);
|
--secondary: oklch(0.97 0 0);
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
--muted: oklch(0.97 0 0);
|
--muted: oklch(0.97 0 0);
|
||||||
|
--sidebar-muted: oklch(0.87 0 0);
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
--accent: oklch(0.97 0 0);
|
--accent: oklch(0.9 0 0);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
--destructive-foreground: oklch(0.577 0.245 27.325);
|
||||||
@ -72,7 +78,7 @@
|
|||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
--sidebar-accent: oklch(0.9 0 0);
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
@ -89,7 +95,7 @@
|
|||||||
--primary-foreground: oklch(0.205 0 0);
|
--primary-foreground: oklch(0.205 0 0);
|
||||||
--secondary: oklch(0.269 0 0);
|
--secondary: oklch(0.269 0 0);
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--muted: oklch(0.269 0 0);
|
--muted: oklch(0.369 0 0);
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
--muted-foreground: oklch(0.708 0 0);
|
||||||
--accent: oklch(0.269 0 0);
|
--accent: oklch(0.269 0 0);
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
|
|||||||
5
app/components/icon/Draggable.vue
Normal file
5
app/components/icon/Draggable.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Icon name="ci:drag-vertical"></Icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
5
app/components/icon/EditAudio.vue
Normal file
5
app/components/icon/EditAudio.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Icon name="gridicons:audio"></Icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
26
app/components/ui/badge/Badge.vue
Normal file
26
app/components/ui/badge/Badge.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { BadgeVariants } from "."
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Primitive } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { badgeVariants } from "."
|
||||||
|
|
||||||
|
const props = defineProps<PrimitiveProps & {
|
||||||
|
variant?: BadgeVariants["variant"]
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Primitive
|
||||||
|
data-slot="badge"
|
||||||
|
:class="cn(badgeVariants({ variant }), props.class)"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</Primitive>
|
||||||
|
</template>
|
||||||
26
app/components/ui/badge/index.ts
Normal file
26
app/components/ui/badge/index.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
export { default as Badge } from "./Badge.vue"
|
||||||
|
|
||||||
|
export const badgeVariants = cva(
|
||||||
|
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
||||||
|
outline:
|
||||||
|
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||||
@ -1,29 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import type { PrimitiveProps } from "reka-ui"
|
|
||||||
import type { HTMLAttributes } from "vue"
|
|
||||||
import type { ContainerVariants } from "."
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { containerVariants } from "."
|
|
||||||
|
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
|
||||||
border?: ContainerVariants["border"]
|
|
||||||
borderRadius?: ContainerVariants["borderRadius"]
|
|
||||||
borderPlacement?: ContainerVariants["borderPlacement"]
|
|
||||||
background?: ContainerVariants["background"]
|
|
||||||
padding?: ContainerVariants["padding"]
|
|
||||||
margin?: ContainerVariants["margin"]
|
|
||||||
class?: HTMLAttributes["class"]
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
as: "container",
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :as="as" :as-child="asChild"
|
|
||||||
:class="cn(containerVariants({ border, borderRadius, borderPlacement, background, padding, margin }), props.class)">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
18
app/components/ui/dropdown-menu/DropdownMenu.vue
Normal file
18
app/components/ui/dropdown-menu/DropdownMenu.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuRootEmits, DropdownMenuRootProps } from "reka-ui"
|
||||||
|
import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuRootProps>()
|
||||||
|
const emits = defineEmits<DropdownMenuRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuRoot
|
||||||
|
data-slot="dropdown-menu"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuRoot>
|
||||||
|
</template>
|
||||||
38
app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue
Normal file
38
app/components/ui/dropdown-menu/DropdownMenuCheckboxItem.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Check } from "lucide-vue-next"
|
||||||
|
import {
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
|
||||||
|
DropdownMenuItemIndicator,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class=" cn(
|
||||||
|
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuItemIndicator>
|
||||||
|
<Check class="size-4" />
|
||||||
|
</DropdownMenuItemIndicator>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
</template>
|
||||||
36
app/components/ui/dropdown-menu/DropdownMenuContent.vue
Normal file
36
app/components/ui/dropdown-menu/DropdownMenuContent.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import {
|
||||||
|
DropdownMenuContent,
|
||||||
|
|
||||||
|
DropdownMenuPortal,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||||
|
{
|
||||||
|
sideOffset: 4,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const emits = defineEmits<DropdownMenuContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuPortal>
|
||||||
|
<DropdownMenuContent
|
||||||
|
data-slot="dropdown-menu-content"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn('bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenuPortal>
|
||||||
|
</template>
|
||||||
15
app/components/ui/dropdown-menu/DropdownMenuGroup.vue
Normal file
15
app/components/ui/dropdown-menu/DropdownMenuGroup.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuGroupProps } from "reka-ui"
|
||||||
|
import { DropdownMenuGroup } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuGroupProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuGroup
|
||||||
|
data-slot="dropdown-menu-group"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
</template>
|
||||||
31
app/components/ui/dropdown-menu/DropdownMenuItem.vue
Normal file
31
app/components/ui/dropdown-menu/DropdownMenuItem.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuItemProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DropdownMenuItem, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<DropdownMenuItemProps & {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
inset?: boolean
|
||||||
|
variant?: "default" | "destructive"
|
||||||
|
}>(), {
|
||||||
|
variant: "default",
|
||||||
|
})
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "inset", "variant", "class")
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuItem
|
||||||
|
data-slot="dropdown-menu-item"
|
||||||
|
:data-inset="inset ? '' : undefined"
|
||||||
|
:data-variant="variant"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn('focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</template>
|
||||||
23
app/components/ui/dropdown-menu/DropdownMenuLabel.vue
Normal file
23
app/components/ui/dropdown-menu/DropdownMenuLabel.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuLabelProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { DropdownMenuLabel, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class", "inset")
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuLabel
|
||||||
|
data-slot="dropdown-menu-label"
|
||||||
|
:data-inset="inset ? '' : undefined"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn('px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
</template>
|
||||||
22
app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue
Normal file
22
app/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from "reka-ui"
|
||||||
|
import {
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuRadioGroupProps>()
|
||||||
|
const emits = defineEmits<DropdownMenuRadioGroupEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
data-slot="dropdown-menu-radio-group"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</template>
|
||||||
39
app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue
Normal file
39
app/components/ui/dropdown-menu/DropdownMenuRadioItem.vue
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Circle } from "lucide-vue-next"
|
||||||
|
import {
|
||||||
|
DropdownMenuItemIndicator,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const emits = defineEmits<DropdownMenuRadioItemEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuRadioItem
|
||||||
|
data-slot="dropdown-menu-radio-item"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn(
|
||||||
|
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuItemIndicator>
|
||||||
|
<Circle class="size-2 fill-current" />
|
||||||
|
</DropdownMenuItemIndicator>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
</template>
|
||||||
24
app/components/ui/dropdown-menu/DropdownMenuSeparator.vue
Normal file
24
app/components/ui/dropdown-menu/DropdownMenuSeparator.vue
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuSeparatorProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import {
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuSeparatorProps & {
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuSeparator
|
||||||
|
data-slot="dropdown-menu-separator"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="cn('bg-border -mx-1 my-1 h-px', props.class)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
17
app/components/ui/dropdown-menu/DropdownMenuShortcut.vue
Normal file
17
app/components/ui/dropdown-menu/DropdownMenuShortcut.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>
|
||||||
|
<span
|
||||||
|
data-slot="dropdown-menu-shortcut"
|
||||||
|
:class="cn('text-muted-foreground ml-auto text-xs tracking-widest', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
19
app/components/ui/dropdown-menu/DropdownMenuSub.vue
Normal file
19
app/components/ui/dropdown-menu/DropdownMenuSub.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuSubEmits, DropdownMenuSubProps } from "reka-ui"
|
||||||
|
import {
|
||||||
|
DropdownMenuSub,
|
||||||
|
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuSubProps>()
|
||||||
|
const emits = defineEmits<DropdownMenuSubEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuSub data-slot="dropdown-menu-sub" v-bind="forwarded">
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuSub>
|
||||||
|
</template>
|
||||||
28
app/components/ui/dropdown-menu/DropdownMenuSubContent.vue
Normal file
28
app/components/ui/dropdown-menu/DropdownMenuSubContent.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import {
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuSubContent
|
||||||
|
data-slot="dropdown-menu-sub-content"
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="cn('bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</template>
|
||||||
31
app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue
Normal file
31
app/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuSubTriggerProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { ChevronRight } from "lucide-vue-next"
|
||||||
|
import {
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
|
||||||
|
useForwardProps,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class", "inset")
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuSubTrigger
|
||||||
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn(
|
||||||
|
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<ChevronRight class="ml-auto size-4" />
|
||||||
|
</DropdownMenuSubTrigger>
|
||||||
|
</template>
|
||||||
17
app/components/ui/dropdown-menu/DropdownMenuTrigger.vue
Normal file
17
app/components/ui/dropdown-menu/DropdownMenuTrigger.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { DropdownMenuTriggerProps } from "reka-ui"
|
||||||
|
import { DropdownMenuTrigger, useForwardProps } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<DropdownMenuTriggerProps>()
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(props)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DropdownMenuTrigger
|
||||||
|
data-slot="dropdown-menu-trigger"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
</template>
|
||||||
16
app/components/ui/dropdown-menu/index.ts
Normal file
16
app/components/ui/dropdown-menu/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export { default as DropdownMenu } from "./DropdownMenu.vue"
|
||||||
|
|
||||||
|
export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue"
|
||||||
|
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"
|
||||||
|
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
|
||||||
|
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue"
|
||||||
|
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue"
|
||||||
|
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue"
|
||||||
|
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue"
|
||||||
|
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue"
|
||||||
|
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue"
|
||||||
|
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue"
|
||||||
|
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue"
|
||||||
|
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue"
|
||||||
|
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
|
||||||
|
export { DropdownMenuPortal } from "reka-ui"
|
||||||
29
app/components/ui/frame/Frame.vue
Normal file
29
app/components/ui/frame/Frame.vue
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import type { FrameVariants } from "."
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { frameVariants } from "."
|
||||||
|
|
||||||
|
|
||||||
|
interface Props extends PrimitiveProps {
|
||||||
|
border?: FrameVariants["border"]
|
||||||
|
borderRadius?: FrameVariants["borderRadius"]
|
||||||
|
borderPlacement?: FrameVariants["borderPlacement"]
|
||||||
|
background?: FrameVariants["background"]
|
||||||
|
padding?: FrameVariants["padding"]
|
||||||
|
margin?: FrameVariants["margin"]
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
as: "frame",
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :as="as" :as-child="asChild"
|
||||||
|
:class="cn(frameVariants({ border, borderRadius, borderPlacement, background, padding, margin }), props.class)">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
export { default as Container } from "./Container.vue"
|
export { default as Frame } from "./Frame.vue"
|
||||||
|
|
||||||
export const containerVariants = cva(
|
export const frameVariants = cva(
|
||||||
"border-solid",
|
"border-solid",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
@ -52,7 +52,7 @@ export const containerVariants = cva(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
export type ContainerVariants = VariantProps<typeof containerVariants>
|
export type FrameVariants = VariantProps<typeof frameVariants>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
43
app/components/ui/input/InputWithIcon.vue
Normal file
43
app/components/ui/input/InputWithIcon.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { useVModel } from "@vueuse/core"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
defaultValue?: string | number
|
||||||
|
modelValue?: string | number
|
||||||
|
class?: HTMLAttributes["class"]
|
||||||
|
placeholder?: string
|
||||||
|
type?: string
|
||||||
|
icon?: any
|
||||||
|
iconSize?: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
placeholder: "Search...",
|
||||||
|
type: "text",
|
||||||
|
iconSize: "6",
|
||||||
|
id: "search"
|
||||||
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits<{
|
||||||
|
(e: "update:modelValue", payload: string | number): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const modelValue = useVModel(props, "modelValue", emits, {
|
||||||
|
passive: true,
|
||||||
|
defaultValue: props.defaultValue,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="relative w-full items-center">
|
||||||
|
<Input :id="id" :type="type" :placeholder="placeholder" :class="cn('pl-10', props.class)"
|
||||||
|
v-model="modelValue" />
|
||||||
|
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-2" v-if="icon">
|
||||||
|
<component :is="icon" :class="`size-${iconSize} text-muted-foreground`" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1 +1,2 @@
|
|||||||
export { default as Input } from "./Input.vue"
|
export { default as Input } from "./Input.vue"
|
||||||
|
export { default as InputWithIcon } from "./InputWithIcon.vue"
|
||||||
|
|||||||
55
app/components/ui/musiccard/MusicCard.vue
Normal file
55
app/components/ui/musiccard/MusicCard.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<Frame margin="none" class="px-2 py-3 flex h-fit gap-2 hover:bg-muted" :class="selected && 'bg-muted'">
|
||||||
|
<div class="text-[2rem] h-full">
|
||||||
|
<Draggable />
|
||||||
|
</div>
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<div class="w-fit flex-1 flex flex-col justify-between">
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="flex items-center justify-between w-full">
|
||||||
|
<h4 class="scroll-m-20 text-xl font-semibold tracking-tight truncate max-w-[32ch]">
|
||||||
|
{{ title }}
|
||||||
|
</h4>
|
||||||
|
<p class="leading-7" v-if="date">
|
||||||
|
{{ date }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-muted-foreground">
|
||||||
|
{{ author }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-1" v-if="badges && badges.length > 0">
|
||||||
|
<Badge v-for="(badge, index) in badges" :key="index">
|
||||||
|
{{ badge }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<div class="max-w-20 max-h-20 w-20 h-20" v-if="imageUrl">
|
||||||
|
<NuxtImg class="object-cover w-full h-full rounded-md" :src="imageUrl" :alt="title" />
|
||||||
|
</div>
|
||||||
|
</Frame>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Separator } from '@/components/ui/separator'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import Draggable from '@/components/icon/Draggable.vue'
|
||||||
|
import Frame from '@/components/ui/frame/Frame.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string
|
||||||
|
author: string
|
||||||
|
selected?: boolean
|
||||||
|
badges?: string[]
|
||||||
|
imageUrl?: string
|
||||||
|
date?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
withDefaults(defineProps<Props>(), {
|
||||||
|
authorLabel: 'Author',
|
||||||
|
badges: () => []
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@ -1,13 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { PrimitiveProps } from "reka-ui"
|
import type { PrimitiveProps } from "reka-ui"
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue"
|
||||||
import type { ContainerVariants } from "../container"
|
import type { FrameVariants } from "../frame"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import Container from "@/components/ui/container/Container.vue"
|
import Frame from "@/components/ui/frame/Frame.vue"
|
||||||
|
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
side?: ContainerVariants["borderPlacement"]
|
side?: FrameVariants["borderPlacement"]
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes["class"]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +17,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Container :as="as" :as-child="asChild" :class="cn(props.class)" margin="none" borderRadius="none"
|
<Frame :as="as" :as-child="asChild" :class="cn(props.class)" margin="none" borderRadius="none"
|
||||||
:borderPlacement="side">
|
:borderPlacement="side">
|
||||||
<slot />
|
<slot />
|
||||||
</Container>
|
</Frame>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
18
app/components/ui/select/Select.vue
Normal file
18
app/components/ui/select/Select.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectRootEmits, SelectRootProps } from "reka-ui"
|
||||||
|
import { SelectRoot, useForwardPropsEmits } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<SelectRootProps>()
|
||||||
|
const emits = defineEmits<SelectRootEmits>()
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(props, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectRoot
|
||||||
|
data-slot="select"
|
||||||
|
v-bind="forwarded"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectRoot>
|
||||||
|
</template>
|
||||||
52
app/components/ui/select/SelectContent.vue
Normal file
52
app/components/ui/select/SelectContent.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectContentEmits, SelectContentProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import {
|
||||||
|
SelectContent,
|
||||||
|
|
||||||
|
SelectPortal,
|
||||||
|
SelectViewport,
|
||||||
|
useForwardPropsEmits,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { SelectScrollDownButton, SelectScrollUpButton } from "."
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<SelectContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||||
|
{
|
||||||
|
position: "popper",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
const emits = defineEmits<SelectContentEmits>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectPortal>
|
||||||
|
<SelectContent
|
||||||
|
data-slot="select-content"
|
||||||
|
v-bind="{ ...forwarded, ...$attrs }"
|
||||||
|
:class="cn(
|
||||||
|
'bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--reka-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||||
|
position === 'popper'
|
||||||
|
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectViewport :class="cn('p-1', position === 'popper' && 'h-[var(--reka-select-trigger-height)] w-full min-w-[var(--reka-select-trigger-width)] scroll-my-1')">
|
||||||
|
<slot />
|
||||||
|
</SelectViewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectContent>
|
||||||
|
</SelectPortal>
|
||||||
|
</template>
|
||||||
23
app/components/ui/select/SelectCustomTrigger.vue
Normal file
23
app/components/ui/select/SelectCustomTrigger.vue
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectTriggerProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { SelectTrigger, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<SelectTriggerProps & { class?: HTMLAttributes["class"], size?: "sm" | "default" }>(),
|
||||||
|
{ size: "default" },
|
||||||
|
)
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class", "size")
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectTrigger data-slot="select-trigger" :data-size="size" v-bind="forwardedProps" :class="cn(
|
||||||
|
props.class,
|
||||||
|
)">
|
||||||
|
<slot />
|
||||||
|
</SelectTrigger>
|
||||||
|
</template>
|
||||||
15
app/components/ui/select/SelectGroup.vue
Normal file
15
app/components/ui/select/SelectGroup.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectGroupProps } from "reka-ui"
|
||||||
|
import { SelectGroup } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<SelectGroupProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectGroup
|
||||||
|
data-slot="select-group"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectGroup>
|
||||||
|
</template>
|
||||||
43
app/components/ui/select/SelectItem.vue
Normal file
43
app/components/ui/select/SelectItem.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectItemProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { Check } from "lucide-vue-next"
|
||||||
|
import {
|
||||||
|
SelectItem,
|
||||||
|
SelectItemIndicator,
|
||||||
|
|
||||||
|
SelectItemText,
|
||||||
|
useForwardProps,
|
||||||
|
} from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<SelectItemProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectItem
|
||||||
|
data-slot="select-item"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'focus:bg-accent focus:text-accent-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<span class="absolute right-2 flex size-3.5 items-center justify-center">
|
||||||
|
<SelectItemIndicator>
|
||||||
|
<Check class="size-4" />
|
||||||
|
</SelectItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectItemText>
|
||||||
|
<slot />
|
||||||
|
</SelectItemText>
|
||||||
|
</SelectItem>
|
||||||
|
</template>
|
||||||
15
app/components/ui/select/SelectItemText.vue
Normal file
15
app/components/ui/select/SelectItemText.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectItemTextProps } from "reka-ui"
|
||||||
|
import { SelectItemText } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<SelectItemTextProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectItemText
|
||||||
|
data-slot="select-item-text"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectItemText>
|
||||||
|
</template>
|
||||||
17
app/components/ui/select/SelectLabel.vue
Normal file
17
app/components/ui/select/SelectLabel.vue
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectLabelProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { SelectLabel } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<SelectLabelProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectLabel
|
||||||
|
data-slot="select-label"
|
||||||
|
:class="cn('px-2 py-1.5 text-sm font-medium', props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectLabel>
|
||||||
|
</template>
|
||||||
26
app/components/ui/select/SelectScrollDownButton.vue
Normal file
26
app/components/ui/select/SelectScrollDownButton.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectScrollDownButtonProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { ChevronDown } from "lucide-vue-next"
|
||||||
|
import { SelectScrollDownButton, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<SelectScrollDownButtonProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectScrollDownButton
|
||||||
|
data-slot="select-scroll-down-button"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn('flex cursor-default items-center justify-center py-1', props.class)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronDown class="size-4" />
|
||||||
|
</slot>
|
||||||
|
</SelectScrollDownButton>
|
||||||
|
</template>
|
||||||
26
app/components/ui/select/SelectScrollUpButton.vue
Normal file
26
app/components/ui/select/SelectScrollUpButton.vue
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectScrollUpButtonProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { ChevronUp } from "lucide-vue-next"
|
||||||
|
import { SelectScrollUpButton, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<SelectScrollUpButtonProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectScrollUpButton
|
||||||
|
data-slot="select-scroll-up-button"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn('flex cursor-default items-center justify-center py-1', props.class)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<ChevronUp class="size-4" />
|
||||||
|
</slot>
|
||||||
|
</SelectScrollUpButton>
|
||||||
|
</template>
|
||||||
19
app/components/ui/select/SelectSeparator.vue
Normal file
19
app/components/ui/select/SelectSeparator.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectSeparatorProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { SelectSeparator } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = defineProps<SelectSeparatorProps & { class?: HTMLAttributes["class"] }>()
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectSeparator
|
||||||
|
data-slot="select-separator"
|
||||||
|
v-bind="delegatedProps"
|
||||||
|
:class="cn('bg-border pointer-events-none -mx-1 my-1 h-px', props.class)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
33
app/components/ui/select/SelectTrigger.vue
Normal file
33
app/components/ui/select/SelectTrigger.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectTriggerProps } from "reka-ui"
|
||||||
|
import type { HTMLAttributes } from "vue"
|
||||||
|
import { reactiveOmit } from "@vueuse/core"
|
||||||
|
import { ChevronDown } from "lucide-vue-next"
|
||||||
|
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<SelectTriggerProps & { class?: HTMLAttributes["class"], size?: "sm" | "default" }>(),
|
||||||
|
{ size: "default" },
|
||||||
|
)
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class", "size")
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectTrigger
|
||||||
|
data-slot="select-trigger"
|
||||||
|
:data-size="size"
|
||||||
|
v-bind="forwardedProps"
|
||||||
|
:class="cn(
|
||||||
|
'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
|
props.class,
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<SelectIcon as-child>
|
||||||
|
<ChevronDown class="size-4 opacity-50" />
|
||||||
|
</SelectIcon>
|
||||||
|
</SelectTrigger>
|
||||||
|
</template>
|
||||||
15
app/components/ui/select/SelectValue.vue
Normal file
15
app/components/ui/select/SelectValue.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { SelectValueProps } from "reka-ui"
|
||||||
|
import { SelectValue } from "reka-ui"
|
||||||
|
|
||||||
|
const props = defineProps<SelectValueProps>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SelectValue
|
||||||
|
data-slot="select-value"
|
||||||
|
v-bind="props"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectValue>
|
||||||
|
</template>
|
||||||
12
app/components/ui/select/index.ts
Normal file
12
app/components/ui/select/index.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
export { default as Select } from "./Select.vue"
|
||||||
|
export { default as SelectContent } from "./SelectContent.vue"
|
||||||
|
export { default as SelectGroup } from "./SelectGroup.vue"
|
||||||
|
export { default as SelectItem } from "./SelectItem.vue"
|
||||||
|
export { default as SelectItemText } from "./SelectItemText.vue"
|
||||||
|
export { default as SelectLabel } from "./SelectLabel.vue"
|
||||||
|
export { default as SelectScrollDownButton } from "./SelectScrollDownButton.vue"
|
||||||
|
export { default as SelectScrollUpButton } from "./SelectScrollUpButton.vue"
|
||||||
|
export { default as SelectSeparator } from "./SelectSeparator.vue"
|
||||||
|
export { default as SelectTrigger } from "./SelectTrigger.vue"
|
||||||
|
export { default as SelectCustomTrigger } from "./SelectCustomTrigger.vue"
|
||||||
|
export { default as SelectValue } from "./SelectValue.vue"
|
||||||
@ -1,63 +1,71 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Calendar, ChevronsUpDown, Home, Inbox, Music2, Music4, Search, Settings } from "lucide-vue-next"
|
import { ChevronsUpDown, Download, Edit, Music4, Settings, Upload } from "lucide-vue-next"
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupContent,
|
SidebarGroupContent,
|
||||||
SidebarGroupLabel,
|
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarRail,
|
SidebarRail,
|
||||||
} from "@/components/ui/sidebar"
|
} from "@/components/ui/sidebar"
|
||||||
import Container from "@/components/ui/container/Container.vue";
|
|
||||||
import { useSidebar } from "@/components/ui/sidebar";
|
import { useSidebar } from "@/components/ui/sidebar";
|
||||||
|
import EditAudio from "@/components/icon/EditAudio.vue";
|
||||||
|
import Frame from "@/components/ui/frame/Frame.vue";
|
||||||
|
import Select from "@/components/ui/select/Select.vue";
|
||||||
|
import SelectTrigger from "@/components/ui/select/SelectTrigger.vue";
|
||||||
|
import SelectContent from "@/components/ui/select/SelectContent.vue";
|
||||||
|
import SelectLabel from "@/components/ui/select/SelectLabel.vue";
|
||||||
|
import SelectSeparator from "@/components/ui/select/SelectSeparator.vue";
|
||||||
|
import SelectGroup from "@/components/ui/select/SelectGroup.vue";
|
||||||
|
import SelectItem from "@/components/ui/select/SelectItem.vue";
|
||||||
|
import SelectCustomTrigger from "@/components/ui/select/SelectCustomTrigger.vue";
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{
|
{
|
||||||
title: "Home",
|
title: "Edit track",
|
||||||
url: "#",
|
url: "/edit",
|
||||||
icon: Home,
|
icon: Edit,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Inbox",
|
title: "Import",
|
||||||
url: "#",
|
url: "/import",
|
||||||
icon: Inbox,
|
icon: Upload,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Calendar",
|
title: "Export",
|
||||||
url: "#",
|
url: "/export",
|
||||||
icon: Calendar,
|
icon: Download,
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Search",
|
|
||||||
url: "#",
|
|
||||||
icon: Search,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
title: "Modify audio",
|
||||||
|
url: "/modify",
|
||||||
|
icon: EditAudio,
|
||||||
|
}, {
|
||||||
title: "Settings",
|
title: "Settings",
|
||||||
url: "#",
|
url: "/settings",
|
||||||
icon: Settings,
|
icon: Settings,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
openMobile,
|
|
||||||
} = useSidebar()
|
} = useSidebar()
|
||||||
|
const route = useRoute()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Sidebar collapsible="icon">
|
<Sidebar collapsible="icon">
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<div v-if="open">
|
<Select>
|
||||||
<div class="flex p-2 gap-2 items-center">
|
<div v-if="open" class="hover:bg-sidebar-muted cursor-pointer rounded-md select-none">
|
||||||
<Container borderRadius="round" background="primary" padding="dense" margin="none">
|
<SelectCustomTrigger class="w-full flex p-2 gap-2 items-center">
|
||||||
|
<Frame borderRadius="round" background="primary" padding="dense" margin="none">
|
||||||
<Music4 class="text-primary-foreground" :size="24" />
|
<Music4 class="text-primary-foreground" :size="24" />
|
||||||
</Container>
|
</Frame>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden text-start">
|
||||||
<h4 class="text-xl font-semibold tracking-tight truncate">
|
<h4 class="text-xl font-semibold tracking-tight truncate">
|
||||||
My playlist
|
My playlist
|
||||||
</h4>
|
</h4>
|
||||||
@ -68,26 +76,45 @@ const {
|
|||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<ChevronsUpDown />
|
<ChevronsUpDown />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SelectCustomTrigger>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!open">
|
<div v-if="!open">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<Container borderRadius="round" background="primary" padding="dense" margin="none">
|
<SelectCustomTrigger as-child>
|
||||||
|
<Frame borderRadius="round" background="primary" padding="dense" margin="none">
|
||||||
<Music4 class="text-primary-foreground" :size="24" />
|
<Music4 class="text-primary-foreground" :size="24" />
|
||||||
</Container>
|
</Frame>
|
||||||
|
</SelectCustomTrigger>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<SelectContent class="w-full">
|
||||||
|
<SelectLabel>Playlists</SelectLabel>
|
||||||
|
<SelectSeparator />
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="test">
|
||||||
|
<span>Test</span>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="second">
|
||||||
|
<span>Second</span>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="third">
|
||||||
|
<span>Third</span>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="fourth">
|
||||||
|
<span>Fourth</span>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<SidebarGroup>
|
<SidebarGroup>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent>
|
||||||
<SidebarMenu class="group-data-[collapsible=icon]:p-1">
|
<SidebarMenu class="group-data-[collapsible=icon]:p-2">
|
||||||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild :is-active="route.path === item.url">
|
||||||
<a :href="item.url">
|
<a :href="item.url">
|
||||||
<div class="text-[1.5rem]">
|
|
||||||
<component :is="item.icon" />
|
<component :is="item.icon" />
|
||||||
</div>
|
|
||||||
<span>{{ item.title }}</span>
|
<span>{{ item.title }}</span>
|
||||||
</a>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|||||||
18
app/layouts/clear.vue
Normal file
18
app/layouts/clear.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-1">
|
||||||
|
<div class="w-full">
|
||||||
|
<header v-if="slots.header">
|
||||||
|
<slot name="header" />
|
||||||
|
</header>
|
||||||
|
<main class="h-screen">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useSlots } from 'vue'
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
</script>
|
||||||
@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<AppSidebar />
|
||||||
|
<SidebarInset>
|
||||||
|
<div class="flex flex-1">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<header v-if="slots.header">
|
<header v-if="slots.header">
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
@ -8,13 +10,16 @@
|
|||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<aside v-if="slots.sidebar" class="w-full">
|
<aside v-if="slots.sidebar" class="w-full hidden sm:block">
|
||||||
<slot name="sidebar" />
|
<slot name="sidebar" />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
</SidebarInset>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useSlots } from 'vue'
|
import { useSlots } from 'vue'
|
||||||
|
import AppSidebar from '@/components/ui/sidebar/AppSidebar.vue';
|
||||||
|
import SidebarInset from '@/components/ui/sidebar/SidebarInset.vue';
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
106
app/pages/edit.vue
Normal file
106
app/pages/edit.vue
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Search } from 'lucide-vue-next'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { InputWithIcon } from '@/components/ui/input'
|
||||||
|
import MusicCard from '@/components/ui/musiccard/MusicCard.vue'
|
||||||
|
import { Outline } from '@/components/ui/outline'
|
||||||
|
import { SidebarTrigger } from '@/components/ui/sidebar'
|
||||||
|
|
||||||
|
const searchValue = ref('')
|
||||||
|
|
||||||
|
// Mock data
|
||||||
|
const tracks = ref([
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Best of Chobits OST - Beyond",
|
||||||
|
author: "ビヨンド",
|
||||||
|
authorLabel: "Author",
|
||||||
|
badges: ["mp3", "jpop", "anime"],
|
||||||
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-1.jpg?raw=true",
|
||||||
|
date: "about 17 years ago",
|
||||||
|
selected: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
title: "Summer Vibessssssssssssssssssssssssssssssssssssfawefjioawefjeaiofjeaoifjeaofejiaofejaiojfoaiss",
|
||||||
|
author: "Various Artists",
|
||||||
|
authorLabel: "Artists",
|
||||||
|
badges: ["mp3", "summer", "mix"],
|
||||||
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-2.jpg?raw=true",
|
||||||
|
date: "about 1 hour ago",
|
||||||
|
selected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Unknown Track",
|
||||||
|
author: "Unknown Artist",
|
||||||
|
authorLabel: "Author",
|
||||||
|
badges: ["wav"],
|
||||||
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-3.jpg?raw=true",
|
||||||
|
selected: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "Single Track",
|
||||||
|
author: "Solo Artist",
|
||||||
|
authorLabel: "Author",
|
||||||
|
badges: [],
|
||||||
|
imageUrl: "https://github.com/yavuzceliker/sample-images/blob/main/docs/image-5.jpg?raw=true",
|
||||||
|
date: "recently added",
|
||||||
|
selected: false
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const filteredTracks = computed(() => {
|
||||||
|
if (!searchValue.value) return tracks.value
|
||||||
|
|
||||||
|
return tracks.value.filter(track =>
|
||||||
|
track.title.toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
||||||
|
track.author.toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
||||||
|
track.badges.some(badge => badge.toLowerCase().includes(searchValue.value.toLowerCase()))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectTrack = (trackId: number) => {
|
||||||
|
tracks.value.forEach(track => {
|
||||||
|
track.selected = track.id === trackId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-1">
|
||||||
|
<NuxtLayout name="default">
|
||||||
|
<template #header>
|
||||||
|
<Outline side="bottom" padding="dense" class="w-full flex">
|
||||||
|
<div class="flex gap-8 w-full items-center">
|
||||||
|
<SidebarTrigger :size="5" />
|
||||||
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
||||||
|
Tracks
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<Button>
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
<div class="flex flex-col w-full p-4 gap-4 h-full">
|
||||||
|
<InputWithIcon v-model="searchValue" :icon="Search" placeholder="Search..." type="search" icon-size="5"
|
||||||
|
id="user-search" class="w-full" />
|
||||||
|
|
||||||
|
<MusicCard v-for="track in filteredTracks" :key="track.id" :title="track.title" :author="track.author"
|
||||||
|
:authorLabel="track.authorLabel" :badges="track.badges" :imageUrl="track.imageUrl"
|
||||||
|
:date="track.date" :selected="track.selected" @click="selectTrack(track.id)" />
|
||||||
|
</div>
|
||||||
|
<template #sidebar>
|
||||||
|
<Outline padding="none" class="h-full" side="left">
|
||||||
|
<Outline padding="dense" side="bottom">
|
||||||
|
<p class="leading-7 not-first:mt-6 font-semibold">
|
||||||
|
Metadata editor
|
||||||
|
</p>
|
||||||
|
</Outline>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
27
app/pages/export.vue
Normal file
27
app/pages/export.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Outline } from '@/components/ui/outline';
|
||||||
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
|
import Frame from '@/components/ui/frame/Frame.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-1">
|
||||||
|
<NuxtLayout name="default">
|
||||||
|
<template #header>
|
||||||
|
<Outline side="bottom" padding="dense" class="w-full">
|
||||||
|
<div class="flex gap-8 w-full items-center">
|
||||||
|
<SidebarTrigger :size="5" />
|
||||||
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
||||||
|
Export
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<Frame>
|
||||||
|
Hello
|
||||||
|
</Frame>
|
||||||
|
</div>
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
36
app/pages/import.vue
Normal file
36
app/pages/import.vue
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Outline } from '@/components/ui/outline';
|
||||||
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
|
import Frame from '@/components/ui/frame/Frame.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-1">
|
||||||
|
<NuxtLayout name="default">
|
||||||
|
<template #header>
|
||||||
|
<Outline side="bottom" padding="dense" class="w-full">
|
||||||
|
<div class="flex gap-8 w-full items-center">
|
||||||
|
<SidebarTrigger :size="5" />
|
||||||
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
||||||
|
Import
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<Frame>
|
||||||
|
Hello
|
||||||
|
</Frame>
|
||||||
|
</div>
|
||||||
|
<template #sidebar>
|
||||||
|
<Outline padding="none" class="h-full" side="left">
|
||||||
|
<Outline padding="dense" side="bottom">
|
||||||
|
<p class="leading-7 not-first:mt-6 font-semibold">
|
||||||
|
Metadata editor
|
||||||
|
</p>
|
||||||
|
</Outline>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,41 +1,3 @@
|
|||||||
<script setup lang="ts">
|
<script lang="ts" setup>
|
||||||
import Container from '@/components/ui/container/Container.vue';
|
await navigateTo('/edit')
|
||||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
|
||||||
import { Outline } from '@/components/ui/outline';
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
layout: false,
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex-1">
|
|
||||||
<NuxtLayout name="default">
|
|
||||||
<template #header>
|
|
||||||
<Outline side="bottom" padding="dense" class="w-full">
|
|
||||||
<div class="flex gap-8 w-full items-center">
|
|
||||||
<SidebarTrigger :size="5" />
|
|
||||||
<h2
|
|
||||||
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
|
||||||
Tracks
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</Outline>
|
|
||||||
</template>
|
|
||||||
<div class="w-full">
|
|
||||||
<Container>
|
|
||||||
Hello
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
<template #sidebar>
|
|
||||||
<Outline padding="none" class="h-full" side="left">
|
|
||||||
<Outline padding="dense" side="bottom">
|
|
||||||
<p class="leading-7 not-first:mt-6 font-semibold">
|
|
||||||
Metadata editor
|
|
||||||
</p>
|
|
||||||
</Outline>
|
|
||||||
</Outline>
|
|
||||||
</template>
|
|
||||||
</NuxtLayout>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|||||||
27
app/pages/modify.vue
Normal file
27
app/pages/modify.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Outline } from '@/components/ui/outline';
|
||||||
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
|
import Frame from '@/components/ui/frame/Frame.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-1">
|
||||||
|
<NuxtLayout name="default">
|
||||||
|
<template #header>
|
||||||
|
<Outline side="bottom" padding="dense" class="w-full">
|
||||||
|
<div class="flex gap-8 w-full items-center">
|
||||||
|
<SidebarTrigger :size="5" />
|
||||||
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
||||||
|
Modify
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<Frame>
|
||||||
|
Hello
|
||||||
|
</Frame>
|
||||||
|
</div>
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
27
app/pages/settings.vue
Normal file
27
app/pages/settings.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Outline } from '@/components/ui/outline';
|
||||||
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
|
import Frame from '@/components/ui/frame/Frame.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex-1">
|
||||||
|
<NuxtLayout name="default">
|
||||||
|
<template #header>
|
||||||
|
<Outline side="bottom" padding="dense" class="w-full">
|
||||||
|
<div class="flex gap-8 w-full items-center">
|
||||||
|
<SidebarTrigger :size="5" />
|
||||||
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
||||||
|
Settings
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</Outline>
|
||||||
|
</template>
|
||||||
|
<div class="w-full">
|
||||||
|
<Frame>
|
||||||
|
Hello
|
||||||
|
</Frame>
|
||||||
|
</div>
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Container from '@/components/ui/container/Container.vue';
|
|
||||||
import { Outline } from '@/components/ui/outline';
|
import { Outline } from '@/components/ui/outline';
|
||||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
import Frame from '@/components/ui/frame/Frame.vue';
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: false,
|
layout: false,
|
||||||
@ -9,24 +8,20 @@ definePageMeta({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex-1">
|
<NuxtLayout name="custom">
|
||||||
<NuxtLayout name="default">
|
|
||||||
<template #header>
|
<template #header>
|
||||||
<Outline side="bottom" padding="dense" class="w-full">
|
<Outline side="bottom" padding="dense" class="w-full">
|
||||||
<div class="flex gap-8 w-full items-center">
|
<div class="flex gap-8 w-full items-center">
|
||||||
<SidebarTrigger :size="5" />
|
<h2 class="scroll-m-20 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
||||||
<h2
|
|
||||||
class="scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight transition-colors first:mt-0">
|
|
||||||
Tracks
|
Tracks
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</Outline>
|
</Outline>
|
||||||
</template>
|
</template>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Container>
|
<Frame>
|
||||||
Hello
|
Hello
|
||||||
</Container>
|
</Frame>
|
||||||
</div>
|
</div>
|
||||||
</NuxtLayout>
|
</NuxtLayout>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user