منوی نقطه‌ایBounce Sidebar

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

محمدجواد

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

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

وابستگی‌ها

motion

نوع تعامل

روی هر ردیف بزن تا انتخاب شه؛ نقطه از مرکزِ ردیفِ فعلی روی یه کمانِ کوچیکِ رو‌به‌بیرون به مرکزِ ردیفِ تازه می‌پره. هرچی فاصله بیشتر باشه کمان کم‌عمق‌تره تا پرشِ بلند قوسِ غول نسازه. ردیف‌ها می‌تونن سرفصلِ گروه داشته باشن (`{ label, heading: true }`): سرفصل انتخاب‌شدنی نیست ولی توی شمارشِ ایندکس‌هاست. اگه `href` بدی ردیف پیوند می‌شه، وگرنه دکمه؛ در هر دو حالت `onChange` صدا زده می‌شه. با `prefers-reduced-motion` نقطه بی‌کمان و بی‌درنگ جابه‌جا می‌شه.

پراپ‌ها

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

items*
BounceSidebarItem[]پیش‌فرض: [{ label: "عمومی", heading: true }, { label: "داشبورد", href: "/" }, "امنیت"]

ردیف‌ها. هر آیتم یا رشته‌ست، یا `{ label, href }`، یا `{ label, heading: true }` برای سرفصلِ گروه. سرفصل‌ها در ایندکس شمرده می‌شن ولی انتخاب‌شدنی نیستن.

value
number

ایندکسِ انتخاب‌شده در حالتِ کنترل‌شده. برای هم‌گام‌کردن با مسیر همینه.

defaultValue
numberپیش‌فرض: 0

ایندکسِ اولیه در حالتِ نام‌کنترل‌شده.

onChange
(index: number) => void

با هر انتخاب، ایندکسِ تازه رو برمی‌گردونه.

dotColor
stringپیش‌فرض: var(--brand, currentColor)

رنگِ نقطه. هر مقدارِ CSS قبوله؛ اگه پروژه‌ات `--brand` نداره، رنگِ متن می‌شه.

className
string

کلاسِ بیرونی روی `ul`؛ عرضِ ستون رو از همین‌جا بده.

نصب

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

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

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

npx shadcn@latest add @farsify/bounce-sidebar

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

نمونه

bounce-sidebar.tsxtsx
"use client";

import { useState } from "react";
import { BounceSidebar } from "@/components/farsify/bounce-sidebar";

const items = [
  { label: "عمومی", heading: true },
  { label: "داشبورد", href: "/dashboard" },
  { label: "گزارش‌ها", href: "/reports" },
  { label: "حساب", heading: true },
  { label: "پروفایل", href: "/profile" },
  { label: "امنیت", href: "/security" },
];

export function SettingsMenu() {
  const [active, setActive] = useState(1);

  return (
    <BounceSidebar
      aria-label="حساب کاربری"
      className="w-52"
      items={items}
      value={active}
      onChange={setActive}
    />
  );
}

// فهرستِ تخت، بدونِ سرفصل و با رنگِ دلخواه
<BounceSidebar
  className="w-44"
  items={["خانه", "محصولات", "درباره"]}
  dotColor="#007bff"
/>

// هم‌گام با مسیر — خودِ کامپوننت به Next وابسته نیست، پس مسیر را شما می‌دهید
const pathname = usePathname();
const index = items.findIndex(
  (item) => typeof item !== "string" && "href" in item && item.href === pathname,
);

<BounceSidebar items={items} value={index} />

نکته‌ها

  • رنگِ برند فقط روی نقطه می‌نشیند؛ سرفصل‌ها سفید و هم‌اندازه‌ی ردیف‌ها هستن تا سلسله‌مراتب از وزن ساخته شه، نه از رنگِ تأکید.
  • `next/link` عمداً import نشده — نسخه‌ی تحویلی باید در پروژه‌ی بدونِ Next هم کامپایل شه. اگه ناوبریِ نرم می‌خوای، `value` رو از مسیر بده.
  • جهتِ کمان از `dir`ِ خودِ المان خونده می‌شه: در راست‌به‌چپ نقطه به بیرونِ راست می‌پره و در چپ‌به‌راست به بیرونِ چپ — چیزی رو دستی آینه نکن.
  • موقعیت با اندازه‌گیریِ DOM حساب می‌شه و روی مرزِ پیکسلِ دستگاه گرد می‌شه، پس روی نمایشگرِ ۱٫۲۵ برابر نقطه مات و لرزان نمی‌شه.
  • برای فهرست‌های بلند ارتفاعِ ثابت با `overflow-y-auto` بهتره تا ستونِ بی‌پایان؛ نقطه داخلِ همون قاب اندازه‌گیری می‌شه.

مجوز

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