какие-то приколы, не помню
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(python -c \"import sys,json; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\), **p.get\\('devDependencies',{}\\)}; [print\\(k,v\\) for k in sorted\\(deps\\) if any\\(x in k for x in ['mediasoup','socket','vueuse','vue']\\)]\")"
|
"Bash(python -c \"import sys,json; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\), **p.get\\('devDependencies',{}\\)}; [print\\(k,v\\) for k in sorted\\(deps\\) if any\\(x in k for x in ['mediasoup','socket','vueuse','vue']\\)]\")",
|
||||||
|
"Bash(yarn vue-tsc *)",
|
||||||
|
"Bash(yarn eslint *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,8 @@
|
|||||||
"@zag-js/file-upload": "^1.41.0",
|
"@zag-js/file-upload": "^1.41.0",
|
||||||
"@zag-js/file-utils": "^1.40.0",
|
"@zag-js/file-utils": "^1.40.0",
|
||||||
"@zag-js/password-input": "^1.40.0",
|
"@zag-js/password-input": "^1.40.0",
|
||||||
|
"@zag-js/slider": "^1.41.2",
|
||||||
|
"@zag-js/switch": "^1.41.1",
|
||||||
"@zag-js/toggle": "^1.40.0",
|
"@zag-js/toggle": "^1.40.0",
|
||||||
"@zag-js/vue": "^1.40.0",
|
"@zag-js/vue": "^1.40.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
import type { Channel, ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||||
import { useAuth } from '@shared/composables/use-auth.ts'
|
import { useAuth } from '@shared/composables/use-auth.ts'
|
||||||
import { useClients } from '@shared/composables/use-clients'
|
import { useClients } from '@shared/composables/use-clients'
|
||||||
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
||||||
@@ -12,12 +12,10 @@ export default function () {
|
|||||||
const { addClient, updateClient, removeClient, clear: clearClients } = useClients()
|
const { addClient, updateClient, removeClient, clear: clearClients } = useClients()
|
||||||
const eventBus = useEventBus()
|
const eventBus = useEventBus()
|
||||||
|
|
||||||
watch(signaling.connected, (connected) => {
|
watch(signaling.socket, (socket) => {
|
||||||
if (!connected)
|
if (!socket)
|
||||||
return
|
return
|
||||||
|
|
||||||
const socket = signaling.socket.value!
|
|
||||||
|
|
||||||
socket.on('initialized', async ({ clients }) => {
|
socket.on('initialized', async ({ clients }) => {
|
||||||
addClient(...clients)
|
addClient(...clients)
|
||||||
})
|
})
|
||||||
@@ -42,6 +40,22 @@ export default function () {
|
|||||||
socket.on('chat:new-message', (message: ChatMessage) => {
|
socket.on('chat:new-message', (message: ChatMessage) => {
|
||||||
eventBus.emit('chat:new-message', message)
|
eventBus.emit('chat:new-message', message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('channel-created', async (channel: Channel) => {
|
||||||
|
eventBus.emit('channel:created', channel)
|
||||||
|
|
||||||
|
await queryClient.setQueryData(['channel-list'], (channels: Channel[]) => {
|
||||||
|
return [...channels, channel]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('channel-removed', async (channelId: Channel['id']) => {
|
||||||
|
eventBus.emit('channel:removed', channelId)
|
||||||
|
|
||||||
|
await queryClient.setQueryData(['channel-list'], (channels: Channel[]) => {
|
||||||
|
return channels.filter(channel => channel.id !== channelId)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<ChadDialog :id="DIALOG_ID" title="Create channel">
|
<ChadDialog :id="DIALOG_ID" title="Create channel">
|
||||||
<ChadInput v-model="name" label="Name" />
|
<ChadInput v-model="name" label="Name" placeholder="Enter channel name..." />
|
||||||
|
<ChadSwitch v-model="persistent" label="Persistent" style="margin-top: var(--space-4);" />
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<ChadButton style="grid-column: 2" :disabled="!valid" :loading="submitting" @click="submit()">
|
<ChadButton style="grid-column: 2" :disabled="!valid" :loading="submitting" size="lg" @click="submit()">
|
||||||
Create
|
Create
|
||||||
</ChadButton>
|
</ChadButton>
|
||||||
</template>
|
</template>
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
import ChadButton from '@shared/components/ui/Button.vue'
|
import ChadButton from '@shared/components/ui/Button.vue'
|
||||||
import ChadDialog from '@shared/components/ui/Dialog.vue'
|
import ChadDialog from '@shared/components/ui/Dialog.vue'
|
||||||
import ChadInput from '@shared/components/ui/Input.vue'
|
import ChadInput from '@shared/components/ui/Input.vue'
|
||||||
|
import ChadSwitch from '@shared/components/ui/Switch.vue'
|
||||||
import { useDialogManager } from '@shared/composables/use-dialog.ts'
|
import { useDialogManager } from '@shared/composables/use-dialog.ts'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { mCreateChannel } from '@/entities/channel/api/mCreateChannel.ts'
|
import { mCreateChannel } from '@/entities/channel/api/mCreateChannel.ts'
|
||||||
@@ -25,6 +27,7 @@ const dialogManager = useDialogManager()
|
|||||||
const { mutateAsync: createChannel, isPending: submitting } = mCreateChannel()
|
const { mutateAsync: createChannel, isPending: submitting } = mCreateChannel()
|
||||||
|
|
||||||
const name = ref('')
|
const name = ref('')
|
||||||
|
const persistent = ref(false)
|
||||||
|
|
||||||
const valid = computed(() => {
|
const valid = computed(() => {
|
||||||
return name.value.trim().length > 0
|
return name.value.trim().length > 0
|
||||||
@@ -36,7 +39,7 @@ async function submit() {
|
|||||||
|
|
||||||
await createChannel({
|
await createChannel({
|
||||||
name: name.value,
|
name: name.value,
|
||||||
persistent: false,
|
persistent: persistent.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
dialogManager.close(DIALOG_ID)
|
dialogManager.close(DIALOG_ID)
|
||||||
|
|||||||
93
new-client/src/entities/client/ui/ClientRow.vue
Normal file
93
new-client/src/entities/client/ui/ClientRow.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<template>
|
||||||
|
<div class="client-row" v-bind="api.getRootProps()">
|
||||||
|
<div class="client-row__header" v-bind="api.getTriggerProps()">
|
||||||
|
<ChadAvatar class="client-row__avatar" src="" :fallback="initials" size="sm" :highlighted="isMe" />
|
||||||
|
|
||||||
|
<span class="client-row__name">{{ client.username }}</span>
|
||||||
|
|
||||||
|
<div class="client-row__badges" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="client-row__settings" v-bind="api.getContentProps()">
|
||||||
|
<ChadSlider label="Volume" />
|
||||||
|
|
||||||
|
<div class="client-row__actions">
|
||||||
|
<ChadButton :icon="VolumeOffIcon" type="secondary" size="sm">
|
||||||
|
Mute
|
||||||
|
</ChadButton>
|
||||||
|
|
||||||
|
<ChadButton :icon="HandFistIcon" type="secondary" size="sm">
|
||||||
|
Poke
|
||||||
|
</ChadButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { ChadClient } from '@shared/types.ts'
|
||||||
|
import type { Props as CollapsibleProps } from '@zag-js/collapsible'
|
||||||
|
import { HandFistIcon, VolumeOffIcon } from '@lucide/vue'
|
||||||
|
import ChadAvatar from '@shared/components/ui/Avatar.vue'
|
||||||
|
import ChadButton from '@shared/components/ui/Button.vue'
|
||||||
|
import ChadSlider from '@shared/components/ui/Slider.vue'
|
||||||
|
import { useClients } from '@shared/composables/use-clients.ts'
|
||||||
|
import { connect as collapsibleConnect, machine as collapsibleMachine } from '@zag-js/collapsible'
|
||||||
|
import { normalizeProps, useMachine } from '@zag-js/vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
client: ChadClient
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { self } = useClients()
|
||||||
|
|
||||||
|
const initials = computed(() => props.client.username.slice(props.client.username.length - 2))
|
||||||
|
|
||||||
|
const isMe = computed(() => self.value?.socketId === props.client.socketId)
|
||||||
|
|
||||||
|
const service = useMachine(collapsibleMachine, computed(() => {
|
||||||
|
return {
|
||||||
|
id: `client-${props.client.socketId}`,
|
||||||
|
} as CollapsibleProps
|
||||||
|
}))
|
||||||
|
|
||||||
|
const api = computed(() => collapsibleConnect(service, normalizeProps))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.client-row {
|
||||||
|
padding-inline: var(--space-4);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&[data-state='open'] {
|
||||||
|
background-color: var(--grey-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
overflow: hidden;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__settings {
|
||||||
|
padding-block: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
margin-top: var(--space-3);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<ChadButton full native-type="submit" :disabled="!valid" :loading="submitting">
|
<ChadButton full native-type="submit" :disabled="!valid" :loading="submitting" size="lg">
|
||||||
Let's go
|
Let's go
|
||||||
</ChadButton>
|
</ChadButton>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<ChadButton full native-type="submit" :disabled="!valid" :loading="submitting">
|
<ChadButton full native-type="submit" :disabled="!valid" :loading="submitting" size="lg">
|
||||||
Create an account
|
Create an account
|
||||||
</ChadButton>
|
</ChadButton>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chad-avatar" :class="{ highlighted }" v-bind="api.getRootProps()">
|
<div
|
||||||
<span class="chad-avatar__fallback" v-bind="api.getFallbackProps()">{{ fallback }}</span>
|
:class="[
|
||||||
<img class="chad-avatar__image" :alt="fallback" :src="src" v-bind="api.getImageProps()">
|
bem.b(),
|
||||||
|
bem.is('highlighted', highlighted),
|
||||||
|
bem.exp(!!size, bem.m(`size_${size!}`)),
|
||||||
|
]"
|
||||||
|
v-bind="api.getRootProps()"
|
||||||
|
>
|
||||||
|
<span :class="bem.e('fallback')" v-bind="api.getFallbackProps()">{{ fallback }}</span>
|
||||||
|
<img :class="bem.e('image')" :alt="fallback" :src="src" v-bind="api.getImageProps()">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
import * as avatar from '@zag-js/avatar'
|
import * as avatar from '@zag-js/avatar'
|
||||||
import { normalizeProps, useMachine } from '@zag-js/vue'
|
import { normalizeProps, useMachine } from '@zag-js/vue'
|
||||||
import { computed, useId } from 'vue'
|
import { computed, useId } from 'vue'
|
||||||
@@ -21,6 +29,8 @@ defineProps<{
|
|||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const bem = useBem('chad-avatar')
|
||||||
|
|
||||||
const service = useMachine(avatar.machine, { id: useId() })
|
const service = useMachine(avatar.machine, { id: useId() })
|
||||||
|
|
||||||
const api = computed(() => avatar.connect(service, normalizeProps))
|
const api = computed(() => avatar.connect(service, normalizeProps))
|
||||||
@@ -35,6 +45,12 @@ const api = computed(() => avatar.connect(service, normalizeProps))
|
|||||||
width: 32px;
|
width: 32px;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
&--size_sm {
|
||||||
|
width: 24px;
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
&__fallback {
|
&__fallback {
|
||||||
@include font-label;
|
@include font-label;
|
||||||
|
|
||||||
@@ -43,7 +59,13 @@ const api = computed(() => avatar.connect(service, normalizeProps))
|
|||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
background-color: var(--grey-2);
|
background-color: var(--grey-2);
|
||||||
|
|
||||||
#{$self}.highlighted & {
|
#{$self}--size_sm & {
|
||||||
|
@include font-micro;
|
||||||
|
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}.is-highlighted & {
|
||||||
background-color: var(--yellow);
|
background-color: var(--yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
bem.b(),
|
bem.b(),
|
||||||
bem.exp(!!type, bem.m(type!)),
|
bem.exp(!!type, bem.m(`type_${type!}`)),
|
||||||
|
bem.exp(!!size, bem.m(`size_${size!}`)),
|
||||||
bem.is('icon-only', iconOnly),
|
bem.is('icon-only', iconOnly),
|
||||||
bem.is('loading', loading),
|
bem.is('loading', loading),
|
||||||
bem.is('disabled', disabled),
|
bem.is('disabled', disabled),
|
||||||
@@ -11,21 +12,20 @@
|
|||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:type="nativeType"
|
:type="nativeType"
|
||||||
>
|
>
|
||||||
<ChadSpinner v-if="loading" class="chad-button__spinner" />
|
<!-- <ChadSpinner v-if="loading" class="chad-button__spinner" /> -->
|
||||||
|
|
||||||
<template v-else>
|
<!-- <template v-else> -->
|
||||||
<Component :is="icon" v-if="icon" class="chad-button__icon" />
|
<Component :is="icon" v-if="icon" :class="bem.e('icon')" />
|
||||||
|
|
||||||
<span v-if="$slots.default">
|
<span v-if="$slots.default">
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
</template>
|
<!-- </template> -->
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
import ChadSpinner from '@shared/components/ui/Spinner.vue'
|
|
||||||
import useBem from '@shared/composables/use-bem.ts'
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
import { computed, useSlots } from 'vue'
|
import { computed, useSlots } from 'vue'
|
||||||
|
|
||||||
@@ -43,10 +43,11 @@ const props = withDefaults(
|
|||||||
export interface ChadButtonProps {
|
export interface ChadButtonProps {
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
type?: 'secondary'
|
type?: 'secondary' | 'danger'
|
||||||
nativeType?: 'button' | 'submit'
|
nativeType?: 'button' | 'submit'
|
||||||
icon?: Component
|
icon?: Component
|
||||||
full?: boolean
|
full?: boolean
|
||||||
|
size?: 'sm' | 'lg'
|
||||||
}
|
}
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
@@ -59,13 +60,15 @@ const iconOnly = computed(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.chad-button {
|
.chad-button {
|
||||||
|
$self: &;
|
||||||
|
|
||||||
@include font-display-14;
|
@include font-display-14;
|
||||||
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
padding: 0 var(--space-3);
|
padding: 0 var(--space-3);
|
||||||
height: 44px;
|
height: 36px;
|
||||||
background-color: var(--yellow);
|
background-color: var(--yellow);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -82,45 +85,82 @@ const iconOnly = computed(() => {
|
|||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:not(:disabled) {
|
||||||
&:focus {
|
|
||||||
background-color: var(--yellow-deep);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background-color: var(--ink);
|
|
||||||
color: var(--yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--secondary {
|
|
||||||
background-color: var(--grey-1);
|
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
background-color: var(--grey-2);
|
background-color: var(--yellow-deep);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--grey-3);
|
background-color: var(--ink);
|
||||||
color: var(--ink);
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--size_sm {
|
||||||
|
@include font-body-bold;
|
||||||
|
|
||||||
|
height: 28px;
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--size_lg {
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--type_secondary {
|
||||||
|
background-color: var(--paper);
|
||||||
|
|
||||||
|
&:not(:disabled) {
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--yellow);
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--type_danger {
|
||||||
|
outline-color: var(--red);
|
||||||
|
background-color: var(--paper);
|
||||||
|
color: var(--red);
|
||||||
|
|
||||||
|
&:not(:disabled) {
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background-color: var(--red);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background-color: var(--grey-1);
|
opacity: 0.6;
|
||||||
color: var(--grey-3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-loading {
|
&.is-loading {
|
||||||
background-color: var(--grey-1);
|
--stripe-width: 44px;
|
||||||
cursor: wait;
|
|
||||||
|
@include diagonal-stripes;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
margin-right: var(--space-1);
|
margin-right: var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#{$self}--size_sm & {
|
||||||
|
width: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<Component :is="as" class="chad-card">
|
<Component :is="as" :class="bem.b()">
|
||||||
<div v-if="title || description" class="chad-card__header">
|
<div v-if="title || description" :class="bem.e('header')">
|
||||||
<p v-if="title" class="chad-card__title">
|
<p v-if="title" :class="bem.e('title')">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-if="description" class="chad-card__description">
|
<p v-if="description" :class="bem.e('description')">
|
||||||
{{ description }}
|
{{ description }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="chad-card__content">
|
<div :class="bem.e('content')">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!!slots.actions" class="chad-card__actions">
|
<div v-if="!!slots.actions" :class="bem.e('actions')">
|
||||||
<slot name="actions" />
|
<slot name="actions" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!!slots.bottom" class="chad-card__bottom">
|
<div v-if="!!slots.bottom" :class="bem.e('bottom')">
|
||||||
<slot name="bottom" />
|
<slot name="bottom" />
|
||||||
</div>
|
</div>
|
||||||
</Component>
|
</Component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ChadCard',
|
name: 'ChadCard',
|
||||||
})
|
})
|
||||||
@@ -42,6 +44,8 @@ const slots = defineSlots<{
|
|||||||
bottom: []
|
bottom: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const bem = useBem('chad-card')
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
as?: string
|
as?: string
|
||||||
title?: string
|
title?: string
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div :class="[bem.b(), bem.is('invalid', !!error)]">
|
||||||
class="chad-input"
|
<label v-if="label" :for="inputId" :class="bem.e('label')">
|
||||||
:class="{
|
|
||||||
'is-invalid': !!error,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<label v-if="label" :for="inputId" class="chad-input__label">
|
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<input :id="inputId" v-model="modelValue" class="chad-input__input" :placeholder="placeholder" type="text" autocomplete="off">
|
<input :id="inputId" v-model="modelValue" :class="bem.e('input')" :placeholder="placeholder" type="text" autocomplete="off">
|
||||||
|
|
||||||
<p v-if="helper" class="chad-input__helper">
|
<p v-if="helper" :class="bem.e('helper')">
|
||||||
{{ helper }}
|
{{ helper }}
|
||||||
</p>
|
</p>
|
||||||
<p v-if="error" class="chad-input__error">
|
<p v-if="error" :class="bem.e('error')">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
import { computed, useId } from 'vue'
|
import { computed, useId } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -37,6 +33,8 @@ defineOptions({
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const bem = useBem('chad-input')
|
||||||
|
|
||||||
const modelValue = defineModel<string>('modelValue')
|
const modelValue = defineModel<string>('modelValue')
|
||||||
|
|
||||||
const inputId = computed(() => {
|
const inputId = computed(() => {
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chad-notification-badge">
|
<div :class="bem.b()">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
|
|
||||||
|
const bem = useBem('chad-notification-badge')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chad-password-input" v-bind="api.getRootProps()">
|
<div :class="bem.b()" v-bind="api.getRootProps()">
|
||||||
<label v-if="label" class="chad-password-input__label" v-bind="api.getLabelProps()">
|
<label v-if="label" :class="bem.e('label')" v-bind="api.getLabelProps()">
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="chad-password-input__control" v-bind="api.getControlProps()">
|
<div :class="bem.e('control')" v-bind="api.getControlProps()">
|
||||||
<input v-bind="api.getInputProps()" v-model="modelValue" class="chad-password-input__input">
|
<input v-bind="api.getInputProps()" v-model="modelValue" :class="bem.e('input')">
|
||||||
|
|
||||||
<button class="chad-password-input__visibility-trigger" v-bind="api.getVisibilityTriggerProps()">
|
<button :class="bem.e('visibility-trigger')" v-bind="api.getVisibilityTriggerProps()">
|
||||||
<Component :is="api.visible ? EyeIcon : EyeOffIcon " v-bind="api.getIndicatorProps()" />
|
<Component :is="api.visible ? EyeIcon : EyeOffIcon " v-bind="api.getIndicatorProps()" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { EyeIcon, EyeOffIcon } from '@lucide/vue'
|
import { EyeIcon, EyeOffIcon } from '@lucide/vue'
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
import * as passwordInput from '@zag-js/password-input'
|
import * as passwordInput from '@zag-js/password-input'
|
||||||
import { normalizeProps, useMachine } from '@zag-js/vue'
|
import { normalizeProps, useMachine } from '@zag-js/vue'
|
||||||
import { computed, useId } from 'vue'
|
import { computed, useId } from 'vue'
|
||||||
@@ -34,6 +35,8 @@ defineOptions({
|
|||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
|
|
||||||
|
const bem = useBem('chad-password-input')
|
||||||
|
|
||||||
const modelValue = defineModel<string>('modelValue')
|
const modelValue = defineModel<string>('modelValue')
|
||||||
|
|
||||||
const inputId = computed(() => {
|
const inputId = computed(() => {
|
||||||
|
|||||||
86
new-client/src/shared/components/ui/Slider.vue
Normal file
86
new-client/src/shared/components/ui/Slider.vue
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div v-bind="api.getRootProps()" :class="[bem.b()]">
|
||||||
|
<div style="margin-bottom: var(--space-2);">
|
||||||
|
<label v-if="label" :class="bem.e('label')" v-bind="api.getLabelProps()">{{ label }}</label>
|
||||||
|
<output v-bind="api.getValueTextProps()">{{ api.value.at(0) }}</output>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="bem.e('control')" v-bind="api.getControlProps()">
|
||||||
|
<div :class="bem.e('track')" v-bind="api.getTrackProps()">
|
||||||
|
<div :class="bem.e('range')" v-bind="api.getRangeProps()" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="(_, index) in api.value"
|
||||||
|
:key="index"
|
||||||
|
v-bind="api.getThumbProps({ index })"
|
||||||
|
:class="bem.e('thumb')"
|
||||||
|
>
|
||||||
|
<input v-bind="api.getHiddenInputProps({ index })">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Props as SliderProps } from '@zag-js/slider'
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
|
import { connect, machine } from '@zag-js/slider'
|
||||||
|
import { normalizeProps, useMachine } from '@zag-js/vue'
|
||||||
|
import { computed, useId } from 'vue'
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ChadSlider',
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<ChadSliderProps>()
|
||||||
|
|
||||||
|
export interface ChadSliderProps {
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = useId()
|
||||||
|
const bem = useBem('chad-slider')
|
||||||
|
|
||||||
|
const service = useMachine(machine, computed(() => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
origin: 'center',
|
||||||
|
thumbAlignment: 'center',
|
||||||
|
} as SliderProps
|
||||||
|
}))
|
||||||
|
const api = computed(() => connect(service, normalizeProps))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.chad-slider {
|
||||||
|
&__label {
|
||||||
|
@include font-micro;
|
||||||
|
|
||||||
|
color: var(--grey-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__control {
|
||||||
|
background-color: var(--paper);
|
||||||
|
border: var(--border-w) solid var(--ink);
|
||||||
|
//outline-offset: calc(var(--border-w) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__track {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__range {
|
||||||
|
background-color: var(--yellow);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__thumb {
|
||||||
|
width: 10px;
|
||||||
|
height: 28px;
|
||||||
|
background-color: var(--ink);
|
||||||
|
top: -4px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chad-spinner" />
|
<div :class="bem.b()" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ChadSpinner',
|
name: 'ChadSpinner',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const bem = useBem('chad-spinner')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
90
new-client/src/shared/components/ui/Switch.vue
Normal file
90
new-client/src/shared/components/ui/Switch.vue
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<div :class="[bem.b()]">
|
||||||
|
<label :class="bem.e('root')" v-bind="api.getRootProps()">
|
||||||
|
<input v-bind="api.getHiddenInputProps()">
|
||||||
|
<span :class="bem.e('control')" v-bind="api.getControlProps()">
|
||||||
|
<span :class="bem.e('thumb')" v-bind="api.getThumbProps()" />
|
||||||
|
</span>
|
||||||
|
<span v-if="label" :class="bem.e('label')" v-bind="api.getLabelProps()">
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Props as SwitchProps } from '@zag-js/switch'
|
||||||
|
import useBem from '@shared/composables/use-bem'
|
||||||
|
import { connect, machine } from '@zag-js/switch'
|
||||||
|
import { normalizeProps, useMachine } from '@zag-js/vue'
|
||||||
|
import { computed, useId } from 'vue'
|
||||||
|
|
||||||
|
export interface ChadSwitchProps {
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ChadSwitch',
|
||||||
|
})
|
||||||
|
|
||||||
|
const props = defineProps<ChadSwitchProps>()
|
||||||
|
|
||||||
|
const modelValue = defineModel('modelValue')
|
||||||
|
|
||||||
|
const id = useId()
|
||||||
|
const bem = useBem('chad-switch')
|
||||||
|
|
||||||
|
const service = useMachine(machine, computed(() => {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
checked: modelValue.value,
|
||||||
|
onCheckedChange: (details) => {
|
||||||
|
modelValue.value = details.checked
|
||||||
|
},
|
||||||
|
} as SwitchProps
|
||||||
|
}))
|
||||||
|
|
||||||
|
const api = computed(() => connect(service, normalizeProps))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.chad-switch {
|
||||||
|
&__root {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__control {
|
||||||
|
border: var(--border-w) solid var(--ink);
|
||||||
|
height: var(--space-6);
|
||||||
|
aspect-ratio: 2;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: var(--border-w);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__thumb {
|
||||||
|
background-color: var(--ink);
|
||||||
|
height: 100%;
|
||||||
|
width: 50%;
|
||||||
|
|
||||||
|
&[data-state='checked'] {
|
||||||
|
background-color: var(--yellow);
|
||||||
|
translate: 100% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
@include font-label;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="chad-tag" :data-type="type">
|
<div :class="[bem.b(), bem.m(`type_${type}`)]">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type: 'success' | 'error'
|
type: 'success' | 'error'
|
||||||
}
|
}
|
||||||
@@ -14,6 +16,8 @@ defineOptions({
|
|||||||
})
|
})
|
||||||
|
|
||||||
defineProps<Props>()
|
defineProps<Props>()
|
||||||
|
|
||||||
|
const bem = useBem('chad-tag')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -32,13 +36,13 @@ defineProps<Props>()
|
|||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-type='success'] {
|
&--type_success {
|
||||||
&::before {
|
&::before {
|
||||||
background-color: var(--green);
|
background-color: var(--green);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-type='error'] {
|
&--type_error {
|
||||||
&::before {
|
&::before {
|
||||||
background-color: var(--red);
|
background-color: var(--red);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="chad-to-bottom">
|
<button :class="bem.b()">
|
||||||
<ArrowDown />
|
<ArrowDown />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowDown } from '@lucide/vue'
|
import { ArrowDown } from '@lucide/vue'
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ChadToBottom',
|
name: 'ChadToBottom',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const bem = useBem('chad-to-bottom')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<button class="chad-toggle" v-bind="api.getRootProps()">
|
<button :class="bem.b()" v-bind="api.getRootProps()">
|
||||||
<span class="chad-toggle__indicator" v-bind="api.getIndicatorProps()" />
|
<span :class="bem.e('indicator')" v-bind="api.getIndicatorProps()" />
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
|
import useBem from '@shared/composables/use-bem.ts'
|
||||||
import * as toggle from '@zag-js/toggle'
|
import * as toggle from '@zag-js/toggle'
|
||||||
import { normalizeProps, useMachine } from '@zag-js/vue'
|
import { normalizeProps, useMachine } from '@zag-js/vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -20,6 +21,8 @@ const props = defineProps<{
|
|||||||
icon?: Component
|
icon?: Component
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const bem = useBem('chad-toggle')
|
||||||
|
|
||||||
const modelValue = defineModel<boolean>('modelValue')
|
const modelValue = defineModel<boolean>('modelValue')
|
||||||
|
|
||||||
const service = useMachine(toggle.machine, computed(() => {
|
const service = useMachine(toggle.machine, computed(() => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
import type { Channel, ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||||
import type { EventType } from 'mitt'
|
import type { EventType } from 'mitt'
|
||||||
import mitt from 'mitt'
|
import mitt from 'mitt'
|
||||||
|
|
||||||
@@ -21,6 +21,10 @@ export interface AppEvents extends Record<EventType, unknown> {
|
|||||||
// 'producer:paused': Producer
|
// 'producer:paused': Producer
|
||||||
// 'producer:resumed': Producer
|
// 'producer:resumed': Producer
|
||||||
|
|
||||||
|
'channel:created': Channel
|
||||||
|
'channel:removed': Channel['id']
|
||||||
|
'channel:updated': Channel
|
||||||
|
|
||||||
'audio:muted': void
|
'audio:muted': void
|
||||||
'audio:unmuted': void
|
'audio:unmuted': void
|
||||||
'output:muted': void
|
'output:muted': void
|
||||||
|
|||||||
@@ -19,6 +19,12 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
|
<div class="sidebar__top">
|
||||||
|
<ChadButton type="secondary" full :icon="PlusIcon" @click="createChannelDialog.open()">
|
||||||
|
Create channel
|
||||||
|
</ChadButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ChannelList class="sidebar__channel-list" />
|
<ChannelList class="sidebar__channel-list" />
|
||||||
|
|
||||||
<!-- <dl> -->
|
<!-- <dl> -->
|
||||||
@@ -37,9 +43,6 @@
|
|||||||
<!-- Logout -->
|
<!-- Logout -->
|
||||||
<!-- </ChadButton> -->
|
<!-- </ChadButton> -->
|
||||||
|
|
||||||
<ChadButton style="margin: var(--space-2)" type="secondary" @click="createChannelDialog.open()">
|
|
||||||
Create channel
|
|
||||||
</ChadButton>
|
|
||||||
<ControlPanel />
|
<ControlPanel />
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -50,6 +53,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { PlusIcon } from '@lucide/vue'
|
||||||
import ChadButton from '@shared/components/ui/Button.vue'
|
import ChadButton from '@shared/components/ui/Button.vue'
|
||||||
import { useApp } from '@shared/composables/use-app.js'
|
import { useApp } from '@shared/composables/use-app.js'
|
||||||
import { useDialog } from '@shared/composables/use-dialog.ts'
|
import { useDialog } from '@shared/composables/use-dialog.ts'
|
||||||
@@ -140,6 +144,10 @@ watchEffect(() => {
|
|||||||
border-right: var(--border-w) solid var(--ink);
|
border-right: var(--border-w) solid var(--ink);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&__top {
|
||||||
|
padding: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
&__channel-list {
|
&__channel-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
8
new-client/src/shared/styles/animations.scss
Normal file
8
new-client/src/shared/styles/animations.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
@keyframes diagonal-stripes {
|
||||||
|
from {
|
||||||
|
background-position-x: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-position-x: var(--stripe-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@use 'tokens.scss';
|
@use 'tokens.scss';
|
||||||
|
@use 'animations.scss';
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
@forward 'mixins/typography';
|
|
||||||
11
new-client/src/shared/styles/mixins/animations.scss
Normal file
11
new-client/src/shared/styles/mixins/animations.scss
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
@mixin diagonal-stripes {
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
var(--grey-1) 25%,
|
||||||
|
var(--grey-1) 50%,
|
||||||
|
var(--grey-2) 50%,
|
||||||
|
var(--grey-2) 75%
|
||||||
|
);
|
||||||
|
background-size: var(--stripe-width) var(--stripe-width);
|
||||||
|
animation: diagonal-stripes 800ms infinite linear;
|
||||||
|
}
|
||||||
2
new-client/src/shared/styles/mixins/index.scss
Normal file
2
new-client/src/shared/styles/mixins/index.scss
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@forward 'typography';
|
||||||
|
@forward 'animations';
|
||||||
@@ -1,53 +1,119 @@
|
|||||||
<template>
|
<template>
|
||||||
<li v-bind="api.getRootProps()" class="channel">
|
<div
|
||||||
<div class="channel__header" v-bind="api.getTriggerProps()">
|
class="channel"
|
||||||
{{ channel.name }}
|
:class="{
|
||||||
|
'is-active': isActive,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="channel__header" @click="joinChannel">
|
||||||
|
<span class="channel__name">{{ channel.name }}</span>
|
||||||
|
|
||||||
|
<div v-if="false" class="channel__actions">
|
||||||
|
<ChadButton size="sm" type="danger" :icon="TrashIcon" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-bind="api.getContentProps()" class="channel__content">
|
<div v-if="clients.length > 0" class="channel__content">
|
||||||
<p v-for="client in clients" :key="client.socketId">
|
<ClientRow v-for="client in clients" :key="client.socketId" class="channel__client" :client="client" />
|
||||||
{{ client.username }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Channel } from '@shared/api/generated-chad-api.ts'
|
import type { Channel } from '@shared/api/generated-chad-api.ts'
|
||||||
|
import { TrashIcon } from '@lucide/vue'
|
||||||
|
import ChadButton from '@shared/components/ui/Button.vue'
|
||||||
import { useClients } from '@shared/composables/use-clients.ts'
|
import { useClients } from '@shared/composables/use-clients.ts'
|
||||||
import * as collapsible from '@zag-js/collapsible'
|
import { useSignaling } from '@shared/composables/use-signaling.ts'
|
||||||
import { normalizeProps, useMachine } from '@zag-js/vue'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import ClientRow from '@/entities/client/ui/ClientRow.vue'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
channel: Channel
|
channel: Channel
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { findByChannel } = useClients()
|
const { findByChannel, self } = useClients()
|
||||||
|
const { socket } = useSignaling()
|
||||||
const service = useMachine(collapsible.machine, computed(() => {
|
|
||||||
return {
|
|
||||||
id: props.channel.id,
|
|
||||||
defaultOpen: true,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
const api = computed(() => collapsible.connect(service, normalizeProps))
|
|
||||||
|
|
||||||
const clients = computed(() => {
|
const clients = computed(() => {
|
||||||
return findByChannel(props.channel.id)
|
return findByChannel(props.channel.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const isActive = computed(() => {
|
||||||
|
return self.value?.channelId === props.channel.id
|
||||||
|
})
|
||||||
|
|
||||||
|
function joinChannel() {
|
||||||
|
if (isActive.value || !socket.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
socket.value.emit('join-channel', { channelId: props.channel.id })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.channel {
|
.channel {
|
||||||
padding-inline: var(--space-3);
|
$self: &;
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
@include font-micro;
|
@include font-micro;
|
||||||
|
|
||||||
padding-block: var(--space-2);
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
padding-inline: var(--space-4);
|
||||||
color: var(--grey-3);
|
color: var(--grey-3);
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: var(--space-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--grey-1);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}__actions {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}.is-active &:after {
|
||||||
|
background-color: var(--yellow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-self: flex-end;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-1);
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
padding-block: var(--space-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__client {
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: var(--space-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ const bem = useBem('chat-attachment')
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.chat-attachment {
|
.chat-attachment {
|
||||||
--stripe-width: 20px;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@@ -39,24 +37,9 @@ const bem = useBem('chat-attachment')
|
|||||||
color: var(--grey-3);
|
color: var(--grey-3);
|
||||||
|
|
||||||
&.is-loading {
|
&.is-loading {
|
||||||
background-image: repeating-linear-gradient(
|
--stripe-width: 20px;
|
||||||
-45deg,
|
|
||||||
transparent 25%,
|
|
||||||
transparent 50%,
|
|
||||||
var(--grey-2) 50%,
|
|
||||||
var(--grey-2) 75%
|
|
||||||
);
|
|
||||||
background-size: var(--stripe-width) var(--stripe-width);
|
|
||||||
animation: loading 500ms infinite linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes loading {
|
@include diagonal-stripes;
|
||||||
from {
|
|
||||||
background-position-x: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
background-position-x: var(--stripe-width);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
class="chat-input__add-attachment"
|
class="chat-input__add-attachment"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
:icon="Plus"
|
:icon="Plus"
|
||||||
|
size="lg"
|
||||||
@click="fileUploadApi.openFilePicker()"
|
@click="fileUploadApi.openFilePicker()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
<ChadButton
|
<ChadButton
|
||||||
class="chat-input__send"
|
class="chat-input__send"
|
||||||
:loading="isUploading"
|
:loading="isUploading"
|
||||||
|
size="lg"
|
||||||
@click="sendMessage()"
|
@click="sendMessage()"
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
|
|||||||
@@ -24,8 +24,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||||
import ChadSpinner from '@shared/components/ui/Spinner.vue'
|
import ChadSpinner from '@shared/components/ui/Spinner.vue'
|
||||||
import ChadToBottom from '@shared/components/ui/ToBottom.vue'
|
import ChadToBottom from '@shared/components/ui/ToBottom.vue'
|
||||||
|
import { useClients } from '@shared/composables/use-clients.ts'
|
||||||
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
||||||
import { useChatScroll } from '@widgets/chat/composables/use-chat-scroll.ts'
|
import { useChatScroll } from '@widgets/chat/composables/use-chat-scroll.ts'
|
||||||
import ChatMessageGroup from '@widgets/chat/ui/ChatMessageGroup.vue'
|
import ChatMessageGroup from '@widgets/chat/ui/ChatMessageGroup.vue'
|
||||||
@@ -33,6 +35,7 @@ import { onUnmounted, ref, useTemplateRef, watch } from 'vue'
|
|||||||
import { useChatHistory } from '../composables/use-chat-history'
|
import { useChatHistory } from '../composables/use-chat-history'
|
||||||
|
|
||||||
const eventBus = useEventBus()
|
const eventBus = useEventBus()
|
||||||
|
const { self } = useClients()
|
||||||
|
|
||||||
const scrollRef = useTemplateRef('scroll')
|
const scrollRef = useTemplateRef('scroll')
|
||||||
|
|
||||||
@@ -64,8 +67,8 @@ onUnmounted(() => {
|
|||||||
eventBus.off('chat:new-message', onNewChatMessage)
|
eventBus.off('chat:new-message', onNewChatMessage)
|
||||||
})
|
})
|
||||||
|
|
||||||
function onNewChatMessage() {
|
function onNewChatMessage(message: ChatMessage) {
|
||||||
if (!arrivedState.start) {
|
if (!arrivedState.start && message.senderUsername !== self.value?.username) {
|
||||||
hasUnreadMessages.value = true
|
hasUnreadMessages.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export default defineConfig({
|
|||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
scss: {
|
scss: {
|
||||||
additionalData: `
|
additionalData: `
|
||||||
@use "@shared/styles/mixins.scss" as *;
|
@use "@shared/styles/mixins" as *;
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1210,6 +1210,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@zag-js/anatomy/-/anatomy-1.41.1.tgz#9cb2a4c6506fd14bbd3387e523885257ed1e37d9"
|
resolved "https://registry.yarnpkg.com/@zag-js/anatomy/-/anatomy-1.41.1.tgz#9cb2a4c6506fd14bbd3387e523885257ed1e37d9"
|
||||||
integrity sha512-wBQVpl8TC9O5AjeJrnmNdJWEUYorTi7iklOcySeXIeaz6D7Y0YY0YbEOSFNsRTpn/NQHwkPejf3i5qkKavNHXw==
|
integrity sha512-wBQVpl8TC9O5AjeJrnmNdJWEUYorTi7iklOcySeXIeaz6D7Y0YY0YbEOSFNsRTpn/NQHwkPejf3i5qkKavNHXw==
|
||||||
|
|
||||||
|
"@zag-js/anatomy@1.41.2":
|
||||||
|
version "1.41.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/anatomy/-/anatomy-1.41.2.tgz#16569b7eb743cb4459fbaf218bdcd5fd72cea32d"
|
||||||
|
integrity sha512-Fm9hqdrvaCzCsdcf19G8WZxYtHElKltkGHdhqMEt4XU+ULTr1DK7KbOtDDv9J27CuzqSLALUz5QfRjPftoKHwg==
|
||||||
|
|
||||||
"@zag-js/aria-hidden@1.41.1":
|
"@zag-js/aria-hidden@1.41.1":
|
||||||
version "1.41.1"
|
version "1.41.1"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/aria-hidden/-/aria-hidden-1.41.1.tgz#69c7c695c718baaaad4db9ae8ddae1c17d5bbfcb"
|
resolved "https://registry.yarnpkg.com/@zag-js/aria-hidden/-/aria-hidden-1.41.1.tgz#69c7c695c718baaaad4db9ae8ddae1c17d5bbfcb"
|
||||||
@@ -1255,6 +1260,14 @@
|
|||||||
"@zag-js/dom-query" "1.41.1"
|
"@zag-js/dom-query" "1.41.1"
|
||||||
"@zag-js/utils" "1.41.1"
|
"@zag-js/utils" "1.41.1"
|
||||||
|
|
||||||
|
"@zag-js/core@1.41.2":
|
||||||
|
version "1.41.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/core/-/core-1.41.2.tgz#d315e84ac55ec4038e5f40d870b3987613882fb3"
|
||||||
|
integrity sha512-xXTN3zKwOtMI4+5dG2cG+T1B4WR3X9alXiYaPJKaGd4N2eYRj9JEPte3Hv9gtFm+RM0b9VIwksHEg25rqE4apw==
|
||||||
|
dependencies:
|
||||||
|
"@zag-js/dom-query" "1.41.2"
|
||||||
|
"@zag-js/utils" "1.41.2"
|
||||||
|
|
||||||
"@zag-js/dialog@^1.41.1":
|
"@zag-js/dialog@^1.41.1":
|
||||||
version "1.41.1"
|
version "1.41.1"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/dialog/-/dialog-1.41.1.tgz#12d3e5e341064103184dbfeeb1e77c0f7f0036a6"
|
resolved "https://registry.yarnpkg.com/@zag-js/dialog/-/dialog-1.41.1.tgz#12d3e5e341064103184dbfeeb1e77c0f7f0036a6"
|
||||||
@@ -1293,6 +1306,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@zag-js/types" "1.41.1"
|
"@zag-js/types" "1.41.1"
|
||||||
|
|
||||||
|
"@zag-js/dom-query@1.41.2":
|
||||||
|
version "1.41.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/dom-query/-/dom-query-1.41.2.tgz#fad51d82f548a55c9116c76370224f0f3fc5f656"
|
||||||
|
integrity sha512-+eBk1nlJA312mNmY/GSThLRwcCRqMIL+A1pLsWvTlQLQjmH1/UxoAuv6l2yvRCT33XmC8FBlBIKnXhOCpDvIZA==
|
||||||
|
dependencies:
|
||||||
|
"@zag-js/types" "1.41.2"
|
||||||
|
|
||||||
"@zag-js/file-upload@^1.41.0":
|
"@zag-js/file-upload@^1.41.0":
|
||||||
version "1.41.0"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/file-upload/-/file-upload-1.41.0.tgz#6ad2deb4d5edc9db1c46fda6823be3a9d404b625"
|
resolved "https://registry.yarnpkg.com/@zag-js/file-upload/-/file-upload-1.41.0.tgz#6ad2deb4d5edc9db1c46fda6823be3a9d404b625"
|
||||||
@@ -1320,6 +1340,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@zag-js/dom-query" "1.41.1"
|
"@zag-js/dom-query" "1.41.1"
|
||||||
|
|
||||||
|
"@zag-js/focus-visible@1.41.1":
|
||||||
|
version "1.41.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/focus-visible/-/focus-visible-1.41.1.tgz#5f27ded76a0ab81bd063e01165ecde68168a2ebf"
|
||||||
|
integrity sha512-uIPkVk7gTVFCEGuDTI/2f3tWEcbsX88SrjgIA31F/m3e5/ieDaeiTfq7pxn+naHwZKEhRNScAgwOpu2AQgc6ag==
|
||||||
|
dependencies:
|
||||||
|
"@zag-js/dom-query" "1.41.1"
|
||||||
|
|
||||||
"@zag-js/i18n-utils@1.41.0":
|
"@zag-js/i18n-utils@1.41.0":
|
||||||
version "1.41.0"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/i18n-utils/-/i18n-utils-1.41.0.tgz#da493ffac2482df38668a99d542d85a568516cc4"
|
resolved "https://registry.yarnpkg.com/@zag-js/i18n-utils/-/i18n-utils-1.41.0.tgz#da493ffac2482df38668a99d542d85a568516cc4"
|
||||||
@@ -1353,6 +1380,17 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@zag-js/dom-query" "1.41.1"
|
"@zag-js/dom-query" "1.41.1"
|
||||||
|
|
||||||
|
"@zag-js/slider@^1.41.2":
|
||||||
|
version "1.41.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/slider/-/slider-1.41.2.tgz#d5f5f5f39a925cdde39b301eaccbacced64fa78b"
|
||||||
|
integrity sha512-mKK2BwoDbIGxAdkdKkPZJA1SHtEQt3lS9hJ6WghefYU2vyd0BXoIKvcDV3xJOzly5LXYhH5cJITn6JtGK8353A==
|
||||||
|
dependencies:
|
||||||
|
"@zag-js/anatomy" "1.41.2"
|
||||||
|
"@zag-js/core" "1.41.2"
|
||||||
|
"@zag-js/dom-query" "1.41.2"
|
||||||
|
"@zag-js/types" "1.41.2"
|
||||||
|
"@zag-js/utils" "1.41.2"
|
||||||
|
|
||||||
"@zag-js/store@1.41.0":
|
"@zag-js/store@1.41.0":
|
||||||
version "1.41.0"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/store/-/store-1.41.0.tgz#97a3ecc1bbb5fe8de51083e4dafaa0d0d3c2d741"
|
resolved "https://registry.yarnpkg.com/@zag-js/store/-/store-1.41.0.tgz#97a3ecc1bbb5fe8de51083e4dafaa0d0d3c2d741"
|
||||||
@@ -1360,6 +1398,18 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
proxy-compare "3.0.1"
|
proxy-compare "3.0.1"
|
||||||
|
|
||||||
|
"@zag-js/switch@^1.41.1":
|
||||||
|
version "1.41.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/switch/-/switch-1.41.1.tgz#81141dfbba50c151800cf4548782d458091b632a"
|
||||||
|
integrity sha512-69F4F/rGD8swQSPJY01J8To6qDVIWp87nfgNpsrUZ2CRcWxRCAjHqC3LwmVARg3pn8sa82jJPhw6VLJ81biXWQ==
|
||||||
|
dependencies:
|
||||||
|
"@zag-js/anatomy" "1.41.1"
|
||||||
|
"@zag-js/core" "1.41.1"
|
||||||
|
"@zag-js/dom-query" "1.41.1"
|
||||||
|
"@zag-js/focus-visible" "1.41.1"
|
||||||
|
"@zag-js/types" "1.41.1"
|
||||||
|
"@zag-js/utils" "1.41.1"
|
||||||
|
|
||||||
"@zag-js/toggle@^1.40.0":
|
"@zag-js/toggle@^1.40.0":
|
||||||
version "1.41.0"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/toggle/-/toggle-1.41.0.tgz#3245e651fc6f7ae2a2c4f86ae244ae90f3d3b517"
|
resolved "https://registry.yarnpkg.com/@zag-js/toggle/-/toggle-1.41.0.tgz#3245e651fc6f7ae2a2c4f86ae244ae90f3d3b517"
|
||||||
@@ -1385,6 +1435,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype "3.2.3"
|
csstype "3.2.3"
|
||||||
|
|
||||||
|
"@zag-js/types@1.41.2":
|
||||||
|
version "1.41.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/types/-/types-1.41.2.tgz#99e8eec960b6c6b5157e281be40cf380d0b7c8ed"
|
||||||
|
integrity sha512-L6CNvK06lIVpy0X8eG3kbDIx8Uuv+3KHElxXYSzRXSJ7/OLCv1sTRgEvnxNtdIWOrksGgxF4JtT7PXtoClGqNQ==
|
||||||
|
dependencies:
|
||||||
|
csstype "3.2.3"
|
||||||
|
|
||||||
"@zag-js/utils@1.41.0":
|
"@zag-js/utils@1.41.0":
|
||||||
version "1.41.0"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/utils/-/utils-1.41.0.tgz#61639369aeadf26f91fe009b4ddce926c5ba607c"
|
resolved "https://registry.yarnpkg.com/@zag-js/utils/-/utils-1.41.0.tgz#61639369aeadf26f91fe009b4ddce926c5ba607c"
|
||||||
@@ -1395,6 +1452,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@zag-js/utils/-/utils-1.41.1.tgz#248fc28510d625034553fd9a4213147204fca323"
|
resolved "https://registry.yarnpkg.com/@zag-js/utils/-/utils-1.41.1.tgz#248fc28510d625034553fd9a4213147204fca323"
|
||||||
integrity sha512-IZGqDpQYvgCQlGcLTVCzWG5DEz318ZLVJhp8TtT9HPDNd+RJTcVHRja7z+vqQ0Su+wKZkuLlIh5gtraxQ+YX9Q==
|
integrity sha512-IZGqDpQYvgCQlGcLTVCzWG5DEz318ZLVJhp8TtT9HPDNd+RJTcVHRja7z+vqQ0Su+wKZkuLlIh5gtraxQ+YX9Q==
|
||||||
|
|
||||||
|
"@zag-js/utils@1.41.2":
|
||||||
|
version "1.41.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@zag-js/utils/-/utils-1.41.2.tgz#1668137bc7b7bf5138203bac1feeeed0059c8e29"
|
||||||
|
integrity sha512-Yj8FSrR7vGA6ahUhjrThfHAF+PM2Y1Yv2lkXkqZZd60mPBhixcot1+SHOfEMV63JimQcWrmQ8QbeYYMmF+ZpLQ==
|
||||||
|
|
||||||
"@zag-js/vue@^1.40.0":
|
"@zag-js/vue@^1.40.0":
|
||||||
version "1.41.0"
|
version "1.41.0"
|
||||||
resolved "https://registry.yarnpkg.com/@zag-js/vue/-/vue-1.41.0.tgz#7e5649c1d5e7979493987de66d25d3deda90976e"
|
resolved "https://registry.yarnpkg.com/@zag-js/vue/-/vue-1.41.0.tgz#7e5649c1d5e7979493987de66d25d3deda90976e"
|
||||||
|
|||||||
Reference in New Issue
Block a user