Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ logs
.env
.env.*
!.env.example

.vscode
65 changes: 65 additions & 0 deletions app/components/AppSidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ router.afterEach(() => {
});

const nav = useState<ContentNavigationItem[] | undefined>("navigation");

const auth = useUserSession();

const authOptions = [
{
route: "/auth/yandex",
label: "Yandex",
icon: "tabler:brand-yandex",
},
{
route: "/auth/google",
label: "Google",
icon: "tabler:brand-google",
},
{
route: "/auth/github",
label: "GitHub",
icon: "tabler:brand-github",
},
];
</script>

<template>
Expand Down Expand Up @@ -146,6 +166,51 @@ const nav = useState<ContentNavigationItem[] | undefined>("navigation");
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter v-if="auth.ready">
<div
v-if="!auth.loggedIn.value"
class="flex flex-col gap-3 items-center"
>
<hr class="w-full" />
<p class="text-muted-foreground">Авторизация</p>
<ButtonGroup class="w-full">
<Button
v-for="authOption in authOptions"
:key="authOption.route"
:title="authOption.label"
class="w-full shrink"
variant="outline"
@click="auth.openInPopup(authOption.route)"
>
<Icon :name="authOption.icon" />
</Button>
</ButtonGroup>
</div>
<Popover v-else-if="auth.user.value">
<PopoverTrigger as-child>
<Button variant="outline">
<Avatar class="size-6">
<AvatarImage
:src="auth.user.value.avatarUrl"
alt="User Avatar"
/>
<AvatarFallback>{{
auth.user.value.name
}}</AvatarFallback>
</Avatar>
<span>{{ auth.user.value.name }}</span>
</Button>
</PopoverTrigger>
<PopoverContent class="w-80">
<div class="grid gap-4">
<Button variant="outline" @click="auth.clear()">
<Icon name="mdi:logout" class="mr-2" />
Выйти
</Button>
</div>
</PopoverContent>
</Popover>
</SidebarFooter>
</Sidebar>
</template>

Expand Down
18 changes: 18 additions & 0 deletions app/components/ui/avatar/Avatar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { AvatarRoot } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<AvatarRoot
data-slot="avatar"
:class="cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', props.class)"
>
<slot />
</AvatarRoot>
</template>
21 changes: 21 additions & 0 deletions app/components/ui/avatar/AvatarFallback.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
import type { AvatarFallbackProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { AvatarFallback } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<AvatarFallbackProps & { class?: HTMLAttributes["class"] }>()

const delegatedProps = reactiveOmit(props, "class")
</script>

<template>
<AvatarFallback
data-slot="avatar-fallback"
v-bind="delegatedProps"
:class="cn('bg-muted flex size-full items-center justify-center rounded-full', props.class)"
>
<slot />
</AvatarFallback>
</template>
16 changes: 16 additions & 0 deletions app/components/ui/avatar/AvatarImage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup lang="ts">
import type { AvatarImageProps } from "reka-ui"
import { AvatarImage } from "reka-ui"

const props = defineProps<AvatarImageProps>()
</script>

<template>
<AvatarImage
data-slot="avatar-image"
v-bind="props"
class="aspect-square size-full"
>
<slot />
</AvatarImage>
</template>
3 changes: 3 additions & 0 deletions app/components/ui/avatar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as Avatar } from "./Avatar.vue"
export { default as AvatarFallback } from "./AvatarFallback.vue"
export { default as AvatarImage } from "./AvatarImage.vue"
22 changes: 22 additions & 0 deletions app/components/ui/button-group/ButtonGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { ButtonGroupVariants } from "."
import { cn } from "@/lib/utils"
import { buttonGroupVariants } from "."

const props = defineProps<{
class?: HTMLAttributes["class"]
orientation?: ButtonGroupVariants["orientation"]
}>()
</script>

<template>
<div
role="group"
data-slot="button-group"
:data-orientation="props.orientation"
:class="cn(buttonGroupVariants({ orientation: props.orientation }), props.class)"
>
<slot />
</div>
</template>
24 changes: 24 additions & 0 deletions app/components/ui/button-group/ButtonGroupSeparator.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import type { SeparatorProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import { reactiveOmit } from "@vueuse/core"
import { cn } from "@/lib/utils"
import { Separator } from '@/components/ui/separator'

const props = withDefaults(defineProps<SeparatorProps & { class?: HTMLAttributes["class"] }>(), {
orientation: "vertical",
})
const delegatedProps = reactiveOmit(props, "class")
</script>

<template>
<Separator
data-slot="button-group-separator"
v-bind="delegatedProps"
:orientation="props.orientation"
:class="cn(
'bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto',
props.class,
)"
/>
</template>
29 changes: 29 additions & 0 deletions app/components/ui/button-group/ButtonGroupText.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script setup lang="ts">
import type { PrimitiveProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import type { ButtonGroupVariants } from "."
import { Primitive } from "reka-ui"
import { cn } from "@/lib/utils"

interface Props extends PrimitiveProps {
class?: HTMLAttributes["class"]
orientation?: ButtonGroupVariants["orientation"]
}

const props = withDefaults(defineProps<Props>(), {
as: "div",
})
</script>

<template>
<Primitive
role="group"
data-slot="button-group"
:data-orientation="props.orientation"
:as="as"
:as-child="asChild"
:class="cn('bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4', props.class)"
>
<slot />
</Primitive>
</template>
25 changes: 25 additions & 0 deletions app/components/ui/button-group/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"

export { default as ButtonGroup } from "./ButtonGroup.vue"
export { default as ButtonGroupSeparator } from "./ButtonGroupSeparator.vue"
export { default as ButtonGroupText } from "./ButtonGroupText.vue"

export const buttonGroupVariants = cva(
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
{
variants: {
orientation: {
horizontal:
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
vertical:
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
},
},
defaultVariants: {
orientation: "horizontal",
},
},
)

export type ButtonGroupVariants = VariantProps<typeof buttonGroupVariants>
27 changes: 27 additions & 0 deletions app/components/ui/item/Item.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script setup lang="ts">
import type { PrimitiveProps } from "reka-ui"
import type { HTMLAttributes } from "vue"
import type { ItemVariants } from "."
import { Primitive } from "reka-ui"
import { cn } from "@/lib/utils"
import { itemVariants } from "."

const props = withDefaults(defineProps<PrimitiveProps & {
class?: HTMLAttributes["class"]
variant?: ItemVariants["variant"]
size?: ItemVariants["size"]
}>(), {
as: "div",
})
</script>

<template>
<Primitive
data-slot="item"
:as="as"
:as-child="asChild"
:class="cn(itemVariants({ variant, size }), props.class)"
>
<slot />
</Primitive>
</template>
17 changes: 17 additions & 0 deletions app/components/ui/item/ItemActions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
data-slot="item-actions"
:class="cn('flex items-center gap-2', props.class)"
>
<slot />
</div>
</template>
17 changes: 17 additions & 0 deletions app/components/ui/item/ItemContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
data-slot="item-content"
:class="cn('flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none', props.class)"
>
<slot />
</div>
</template>
21 changes: 21 additions & 0 deletions app/components/ui/item/ItemDescription.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<p
data-slot="item-description"
:class="cn(
'text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance',
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
props.class,
)"
>
<slot />
</p>
</template>
17 changes: 17 additions & 0 deletions app/components/ui/item/ItemFooter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
data-slot="item-footer"
:class="cn('flex basis-full items-center justify-between gap-2', props.class)"
>
<slot />
</div>
</template>
18 changes: 18 additions & 0 deletions app/components/ui/item/ItemGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
role="list"
data-slot="item-group"
:class="cn('group/item-group flex flex-col', props.class)"
>
<slot />
</div>
</template>
17 changes: 17 additions & 0 deletions app/components/ui/item/ItemHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>

<template>
<div
data-slot="item-header"
:class="cn('flex basis-full items-center justify-between gap-2', props.class)"
>
<slot />
</div>
</template>
Loading