Стек: Vue/Nuxt 3, shadcn-vue, Telegram WebApp, Tailwindcss 3

Всем привет, я фронтенд разработчик, зовут меня Егор, по батьке Душин.

В этой статье я умышленно буду опускать вопросы, почему я пишу именно на Vue, а не на React, почему я выбрал такой проект, а не иной, и так далее; тема на повестке дня только одна: стоит ли использовать Vue версию shadcn в продакшне или нет

Описание проекта

UI
UI

Телеграм бот с миниапп (веб интерфейс внутри телеграма), дающий возможность пользоваться различными нейросетями, там же функционал поиска в интернете, ролей и так далее

Минусы и плюсы

Далее я хочу выделить главные проблемы, с которыми я столкнулся и которые отняли у меня достаточно много времени, а также расскажу о некоторых фишках, которые мне зашли, вне зависимости от того, есть они в других библиотеках компонентов или нет

Проблемы

CLI

Разработчики shadcn-vue дали нам команду для добавления компонентов локально:

npx shadcn-vue@latest add name-of-component

Это удобно, однако работает далеко не всегда, например, компонент button добавить получится, а вот accordion уже нет, ибо будет вылетать неосмысленная ошибка:

В целом, баг не критичный, и можно руками скачивать компоненты, копируя их из репозитория, но впечатление портит. Кстати, добавить issue я решил только недавно.

Анимации

При отсутствии видимых ошибок или предупреждений часть анимаций (**transitions**) у некоторых компонентов не работает, хоть в официальной документации все ок.

Сходу пару приглянувшихся мне изначально компонентов в реальности потеряли большую часть своих анимаций, например, Drawer:

Потерянные анимации у drawer
Потерянные анимации у drawer

Это напрямую связано с этим issue и прочими.

Я считаю это довольно жирным минусом, потому что в этом случае происходит расхождение ожиданий и реальности.

Обработка событий

Речь идет про то, что у некоторых компонентов есть булевое свойство disabled, при true компонент (например, кнопка) блокируется и не дает себя нажимать.

Так вот, я хотел блокировать некоторые элементы интерфейса, если у пользователя нет подписки и при нажатии соответственно вывешивать предупреждение.

Однако, по видимому компонент внутри себя обрубает распространение событий, если disabled=true, из-за этого даже если использовать обертку с @click, события обрабатываться не будут.

Я пробовал различные обходы, хаки, пробовал залезть внутрь компонентов, насколько это возможно, но исправить эту проблему у меня не получилось.

disabled = false
disabled = false
disabled = true
disabled = true

Пример компонента (под капотом Select):

<div @click="clickResponseStyles">
  <MultiChoiceContainer
      v-model="responseStyle"
      :default-value="responseStyle"
      placeholder="Выберите стиль ответа"
      :disabled="!useSettings().hasFeature(FeatureType.responseStyle)"
  >
    <SelectItem
        v-for="style in responseStyles"
        :key="style.id"
        :value="style.id"
    >
      {{ style.title }}
    </SelectItem>
  </MultiChoiceContainer>
</div>

SSR

Уже совсем критической проблемой для меня стала слабая поддержка SSR. Неприятно, что заметить это сразу не очень то просто, потому что Hydration Mismatch предупреждения подсвечиваются далеко не на всё, поэтому проблема становится заметна только при деплое и определенных условиях.

Понятно, что данная проблема "решается" оборачиванием в <client-only>, да и в Telegram WebApp по факту использование SSR можно считать излишним, но я считаю что поддержка SSR в современных приложениях более, чем необходима.

На данный момент открыт один issue на эту тему.

Приятности

Доступность / гибкость

Компоненты shadcn-vue написаны на Tailwindcss, а также придерживаются Typescript, поэтому они довольно компактные, легко читаемые и править их удобно.

Например, для того, чтобы добавить новый тип кнопки достаточно добавить одну строчку в buttonVariants.variants.variant в button/index.ts:

import type { VariantProps } from "class-variance-authority"
import { cva } from "class-variance-authority"

export { default as Button } from "./Button.vue"

export const buttonVariants = cva(
  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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",
  {
    variants: {
      variant: {
        default:
          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
        destructive:
          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
        outline:
          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
        ghost:
          "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-9 px-4 py-2 has-[>svg]:px-3",
        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
        icon: "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
)

export type ButtonVariants = VariantProps<typeof buttonVariants>

Помимо этого, shadcn-vue генерирует css переменные, поэтому поменять цветовую гамму не составляет никакого труда.

Визуал

Несмотря на те проблемы, с которыми я столкнулся, я доволен: мне нравятся цвета интерфейса, форма компонентов и прочее.

Так или иначе, этот ui kit остается моим любимым, и я готов терпеть некоторые трудности, лишь бы получить такой уровень стиля. Может, все таки красота требует жертв?

Заключение

Shadcn определенно является красивой и строгой библиотекой компонентов с, порой, интересными дизайнерскими решениями (как, например, анимация Drawer), однако конкретно реализация shadcn-vue по моему мнению пока что еще недостаточно готова для production-ready решений.

Ссылка на продукт, над которым я работал: https://t.me/AIHubGPTBot

Комментарии (0)