پسندِ تپندهPulse Heart

قرصِ پسندیدنی که با هر فشار می‌تپه: قلب جمع می‌شه، از جاش فنر می‌زنه و رقمِ شمارنده با رولِ اودومتری یکی بالا می‌ره.

محمدجواد

ساخته شده توسط محمدجواد

سازنده فارسی‌فای، بچه دولوپر

وابستگی‌ها

بدون وابستگی

نوع تعامل

روی قرص کلیک کن یا با `Enter`/`Space` فشارش بده تا وضعیت بین پسندیده و نپسندیده بچرخه و شمار یکی بالا یا پایین بره. با ماوس و لمس، ضربان کامل اجرا می‌شه — قلب تا `dotSize` جمع می‌شه و با `overshoot` از جاش رد می‌شه، قرص به اندازه‌ی `beat` درصد کم می‌شه و رقمِ عوض‌شده با `rollDuration` می‌چرخه. با صفحه‌کلید ضربان رد می‌شه: وضعیت و شمار بی‌درنگ عوض می‌شن تا کاربر منتظرِ انیمیشن نمونه. اگه `prefers-reduced-motion` روشن باشه، مسیرِ اشاره‌گر هم به همون تعویضِ بی‌درنگ تبدیل می‌شه.

پراپ‌ها

گزینه‌هایی که می‌تونی به کامپوننت بدی؛ ستاره‌دارها اجباری‌اند.

liked
boolean

حالتِ controlled؛ بده تا مالکیتِ وضعیت با تو باشه. تغییرِ بیرونی بدونِ اجرای ضربان می‌نشینه.

defaultLiked
booleanپیش‌فرض: false

وضعیتِ اولیه توی حالتِ uncontrolled.

count
numberپیش‌فرض: 0

عددی که نشون داده می‌شه؛ هر فشار یکی اضافه یا کم می‌کنه. نمایش با ارقامِ فارسی و جداکننده‌ی «٬»ـه.

onChange
(liked: boolean, count: number) => void

با هر فشار، وضعیت و شمارِ تازه رو می‌ده.

showCount
booleanپیش‌فرض: true

نمایشِ شمار؛ خاموش‌کردنش قرص رو به دایره جمع می‌کنه.

icon
"heart" | "star" | "thumb" | ReactNodeپیش‌فرض: "heart"heart · star · thumb

گلیفِ آماده یا هر عنصرِ دلخواه.

نصب

۱. این آدرس رو یک‌بار توی components.json بذار تا shadcn مخزن فارسی‌فای رو بشناسه.

components.jsonjson
{
  "registries": {
    "@farsify": "https://farsify.ir/r/{name}.json"
  }
}

۲. بعد با دستورِ زیر کامپوننت رو نصب کن.

npx shadcn@latest add @farsify/pulse-heart

پیش‌نیاز: Tailwind CSS v4 و صفحهٔ راست‌به‌چپ dir="rtl".

نمونه

pulse-heart.tsxtsx
"use client";

import { useState } from "react";
import { PulseHeart } from "@/components/farsify/pulse-heart";

export function PostLike() {
  const [liked, setLiked] = useState(false);
  const [count, setCount] = useState(1204);

  return (
    <PulseHeart
      liked={liked}
      count={count}
      onChange={(next, total) => {
        setLiked(next);
        setCount(total);
      }}
    />
  );
}

// بدون کنترل — خودش وضعیت و شمار را نگه می‌دارد
<PulseHeart count={1204} />

// ستاره به‌جای قلب، بدونِ شمار
<PulseHeart icon="star" showCount={false} likedColor="#f5b942" />

نکته‌ها

  • برای اتصال به سرور، `onChange` رو بگیر و **بعد** از پاسخِ موفق `liked` و `count` رو بده — حالتِ controlled تغییرِ بیرونی رو بدونِ اجرای ضربان می‌نشونه، پس دوبار نمی‌تپه.
  • دمو با `count={1204}` شروع می‌شه ولی خودِ کامپوننت پیش‌فرضِ `0` داره؛ شمارِ واقعی (مثلاً جمعِ پسندهای سرور) رو از والد بده و به مقدارِ دمو تکیه نکن.
  • رولِ اودومتری فقط وقتی یه رقم عوض شه همون یه خونه رو می‌چرخونه؛ اگه طولِ رشته عوض شه (۹۹۹ ← ۱٬۰۰۰) کلِ عدد یه‌جا جابه‌جا می‌شه — عیناً رفتارِ مرجع.
  • iconِ آماده داخلِ گروهِ مقیاس‌شدنیِ SVG می‌نشینه و ضخامتِ خط از `--ph-stroke` می‌آد (`1.5 × size / 24`). اگه `icon` رو یه `ReactNode` بدی، اون عنصر جای کلِ `<svg>` رو می‌گیره و ضربان روی `<span>`ِ بیرونی اعمال می‌شه — پس ضخامتِ خطش رو خودت با `size` هماهنگ کن.
  • با `prefers-reduced-motion`، هم ضربان و هم رول خاموش می‌شن و رنگ با گذارِ ملایم عوض می‌شه؛ شمار هنوز فوراً به‌روز می‌شه، پس چیزی از قلم نمی‌افته.
  • این پورت **راست‌به‌چپ نمی‌شه** و این عمدیه: چیدمان LTR-safeـه (`inline-flex` و `padding-inline-end` خودشون آینه می‌شن) و محورِ ضربان مقیاسِ متقارنِ دورِ مرکزه. فقط متن و ارقام فارسی شدن — همون قاعده‌ی `jelly-radio`.

مجوز

کد بعد از نصب مالِ خودته؛ هر تغییری خواستی بده و توی پروژه‌ی خودت نگهش دار. برای پروژه‌های شخصی و تجاری آزاده، حتی اگه کدت بسته باشه. فقط اسم و لینک فارسی‌فای رو یه جا توی پروژه نگه دار.