Buttons

Call Link Button

Кнопка связи на настоящей ссылке tel:: номер виден и копируется, а пульсирующее кольцо и строка часов говорят, что линия открыта.

  • button
  • call
  • tel
  • contact

Превью

1440pxHost theme

Использовать с AI

  1. 1. Скопируйте ссылку.
  2. 2. Напишите агенту своими словами и вставьте её в предложение.
  3. 3. Агент откроет ссылку и поставит компонент из registry.

размести это в шапке: https://vibeui.ru/c/button-052

Показать полную инструкцию

То, что лежит по ссылке в развёрнутом виде. Нужна, если агент не может открыть ссылку — тогда вставьте этот текст целиком.

# Установка и размещение «button-052» (Call Link Button) из VibeUI

## 1. Сначала установка — не пропускай, не пересоздавай
Выполни эту команду до того, как писать код:
npx shadcn@latest add https://vibeui.ru/r/button-052.json

Registry item: https://vibeui.ru/r/button-052.json
Ставится в: components/vibeui/button-052.tsx (точный путь следует алиасам components.json этого проекта).
npm-зависимости: нет.
Registry-зависимости: нет.

Ставь из реестра. Не пересоздавай по описанию, не подменяй похожим
компонентом из другой библиотеки и не переписывай под стиль проекта.

## 2. Что это
Кнопка связи на настоящей ссылке tel:: номер виден и копируется, а пульсирующее кольцо и строка часов говорят, что линия открыта.

Кнопка звонка, которая показывает сам номер: сверху назначение, под ним моноширинный номер, ещё ниже — часы работы. Слева трубка в круге с расходящимся кольцом: линия открыта. Это ссылка tel:, поэтому номер копируется из контекстного меню и работает на любой платформе. Ноль зависимостей, один файл.

## 3. Как использовать
import { Button052 } from "@/components/vibeui/button-052"

<Button052 phone="+7 495 120-45-90" hint="Отвечаем с 9:00 до 21:00">
  Позвонить в поддержку
</Button052>

Полный список пропсов — в установленном файле.

## 4. Куда поставить
Это мелкий inline-компонент. Поставь его ровно туда, куда просил
пользователь, внутрь существующей разметки. Не создавай ради него
новую страницу, секцию или обёртку. Если на этом месте уже стоит похожий
контрол — замени его, а не добавляй второй.

Куда поставить: ___
(Эту строку заполняет пользователь. Если она осталась пустой — спроси,
а не угадывай.)

## 5. Сохранить как установлено
- ссылку tel: вместо обработчика клика: только так номер копируется и открывается в звонилке
- видимый номер в человекочитаемом виде — чистится только href, а не то, что читает пользователь
- моноширинный шрифт и tabular-nums у номера: цифры не должны прыгать по ширине
- кольцо доступности и правило prefers-reduced-motion, которое его останавливает
- три строки в порядке «назначение — номер — часы»: сначала зачем звонить, потом куда
- контрастную заливку — кнопка связи должна находиться на странице сразу

## 6. Можно менять
- подпись назначения
- номер — проп phone, из него же собирается href
- строку часов — проп hint
- акцентный цвет — проп accent
- внешние отступы через className

## 7. Правила
- Не показывай пульсирующее кольцо, если линия по факту закрыта: анимация обещает доступность прямо сейчас.
- Держи номер в международном формате с плюсом — иначе звонок из-за границы не пройдёт.
- Не заменяй ссылку на кнопку с обработчиком: на десктопе исчезнет возможность скопировать номер.
- Не выноси CSS-переменные в globals.css: компонент должен оставаться одним файлом.

## 8. Проверка
- рендерится без ошибок в консоли;
- выглядит так же, как превью на странице VibeUI, откуда взята ссылка;
- если нет — ты изменил что-то из раздела 5, верни как было.

Для разработчика

npx shadcn@latest add https://vibeui.ru/r/button-052.json
https://vibeui.ru/r/button-052.json

Компонент самодостаточен: один файл, без зависимостей, собственная палитра в локальных переменных --vibeui-button-052-*. Серверный компонент, корень — <a href="tel:…">: на телефоне вызов уходит в звонилку, на десктопе — в приложение пользователя. Из видимой записи номера пробелы и разделители вычищаются только в href, поэтому человекочитаемый вид сохраняется. Трубка нарисована рамкой с прозрачными гранями, кольцо доступности — анимация на псевдоэлементе, номер набран моноширинным с tabular-nums.

Исходник компонента

Тот же файл, который поставит агент. Нужен, если вы предпочитаете скопировать код руками.

import type { ComponentPropsWithoutRef, CSSProperties } from "react"

export type Button052Props = Omit<ComponentPropsWithoutRef<"a">, "href"> & {
  /** Номер в человекочитаемом виде: он же попадает в tel: после чистки. */
  phone?: string
  /** Строка доступности под номером: часы работы или статус линии. */
  hint?: string
  accent?: string
}

// Идея компонента: кнопка связи — это ссылка tel:, а не обработчик клика.
// Тогда на телефоне вызов уходит в звонилку, на десктопе — в приложение
// пользователя, а номер можно скопировать из контекстного меню. Из видимой
// записи номера пробелы и скобки вычищаются только в href.
const STYLES = `
:where([data-vibeui-block="button-052"]){
--vibeui-button-052-accent:oklch(0.55 0.15 150);
--vibeui-button-052-fg:oklch(0.99 0.01 150);
--vibeui-button-052-font:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
--vibeui-button-052-mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
}
[data-vibeui-block="button-052"]{
display:inline-flex;align-items:center;gap:0.75rem;box-sizing:border-box;
padding:0.625rem 1.125rem 0.625rem 0.875rem;border-radius:9999px;
background:var(--vibeui-button-052-accent);color:var(--vibeui-button-052-fg);
font-family:var(--vibeui-button-052-font);text-decoration:none;
transition:filter .16s ease;
}
[data-vibeui-block="button-052"]:hover{filter:brightness(1.07)}
[data-vibeui-block="button-052"]:focus-visible{outline:2px solid var(--vibeui-button-052-accent);outline-offset:3px}
/* Трубка: скруглённый прямоугольник, повёрнутый и обрезанный по углам. */
[data-vibeui-block="button-052"] [data-part="dial"]{
position:relative;flex:none;
display:flex;align-items:center;justify-content:center;
width:2rem;height:2rem;border-radius:50%;background:oklch(1 0 0 / 20%);
}
[data-vibeui-block="button-052"] [data-part="dial"]::before{
content:"";width:0.875rem;height:0.875rem;
border:2px solid currentColor;border-radius:0.4375rem 0.25rem 0.4375rem 0.25rem;
border-top-color:transparent;border-right-color:transparent;
transform:rotate(8deg);
}
/* Кольцо доступности пульсирует: линия открыта прямо сейчас. */
[data-vibeui-block="button-052"] [data-part="dial"]::after{
content:"";position:absolute;inset:-2px;border-radius:50%;
border:2px solid oklch(1 0 0 / 55%);
animation:vibeui-button-052-ring 2.4s ease-out infinite;
}
@keyframes vibeui-button-052-ring{
0%{opacity:.7;transform:scale(1)}
70%{opacity:0;transform:scale(1.35)}
100%{opacity:0;transform:scale(1.35)}
}
[data-vibeui-block="button-052"] [data-part="text"]{display:flex;flex-direction:column;gap:0.1875rem}
[data-vibeui-block="button-052"] [data-part="label"]{font-size:0.75rem;font-weight:600;opacity:.85;letter-spacing:0.02em}
[data-vibeui-block="button-052"] [data-part="phone"]{
font-family:var(--vibeui-button-052-mono);font-size:0.9375rem;font-weight:600;
font-variant-numeric:tabular-nums;letter-spacing:0.01em;
}
[data-vibeui-block="button-052"] [data-part="hint"]{font-size:0.6875rem;opacity:.75}
@media (prefers-reduced-motion:reduce){[data-vibeui-block="button-052"] *{animation:none!important;transition:none!important}}
`

/**
 * Кнопка звонка на настоящей ссылке tel: с номером и статусом линии.
 * Один файл, ноль зависимостей, собственная палитра.
 */
export function Button052({
  phone = "+7 495 120-45-90",
  hint = "Отвечаем с 9:00 до 21:00",
  accent,
  className,
  style,
  children = "Позвонить в поддержку",
  ...props
}: Button052Props) {
  const palette = {
    ...(accent ? { "--vibeui-button-052-accent": accent } : null),
    ...style,
  } as CSSProperties

  return (
    <>
      <style href="vibeui-button-052" precedence="medium">
        {STYLES}
      </style>
      <a
        {...props}
        href={`tel:${phone.replace(/[^\d+]/g, "")}`}
        data-vibeui-block="button-052"
        className={className}
        style={palette}
      >
        <span data-part="dial" aria-hidden="true" />
        <span data-part="text">
          <span data-part="label">{children}</span>
          <span data-part="phone">{phone}</span>
          {hint ? <span data-part="hint">{hint}</span> : null}
        </span>
      </a>
    </>
  )
}