Fix colors, replace Dropdown with Select, add clear layout

This commit is contained in:
2025-11-06 00:31:47 +05:00
parent 73a3be7539
commit 86b41eb5e0
51 changed files with 1248 additions and 164 deletions

View File

@ -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>

View File

@ -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);

View File

@ -0,0 +1,5 @@
<template>
<div>
<Icon name="ci:drag-vertical"></Icon>
</div>
</template>

View File

@ -0,0 +1,5 @@
<template>
<div>
<Icon name="gridicons:audio"></Icon>
</div>
</template>

View 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>

View 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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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"

View 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>

View File

@ -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>

View 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>

View File

@ -1 +1,2 @@
export { default as Input } from "./Input.vue" export { default as Input } from "./Input.vue"
export { default as InputWithIcon } from "./InputWithIcon.vue"

View 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>

View File

@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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"

View File

@ -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
View 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>

View File

@ -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
View 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
View 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
View 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>

View File

@ -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
View 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
View 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>

View File

@ -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>