feat: redesign landing page with brand system and section blocks
Build & Push Docker Image / build (push) Has been cancelled

Refresh the homepage around new hero/services/roadmap sections, shared design tokens, and updated brand assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ar.badiei
2026-07-23 17:00:55 +03:30
co-authored by Cursor
parent f0c290786e
commit c2376b3b9e
352 changed files with 34442 additions and 2760 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ function AccordionTrigger({
<AccordionPrimitive.Trigger
data-slot="accordion-trigger"
className={cn(
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-start text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
+2 -2
View File
@@ -70,7 +70,7 @@ function AlertDialogHeader({
return (
<div
data-slot="alert-dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
className={cn("flex flex-col gap-2 text-center sm:text-start", className)}
{...props}
/>
)
@@ -136,7 +136,7 @@ function AlertDialogCancel({
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
return (
<AlertDialogPrimitive.Cancel
className={cn(buttonVariants({ variant: "outline" }), className)}
className={cn(buttonVariants({ variant: "secondary" }), className)}
{...props}
/>
)
+3 -1
View File
@@ -1,6 +1,7 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { type } from "@/lib/typography"
import { cn } from "@/lib/utils"
const alertVariants = cva(
@@ -39,7 +40,8 @@ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="alert-title"
className={cn(
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
type["list-title"],
"col-start-2 line-clamp-1 min-h-4",
className
)}
{...props}
+3 -3
View File
@@ -10,13 +10,13 @@ const badgeVariants = cva(
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary-hover",
secondary:
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive:
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
"border-transparent bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
outline:
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
"border-surface text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
},
},
defaultVariants: {
+28 -19
View File
@@ -1,16 +1,16 @@
"use client"
import { motion, MotionStyle, Transition } from "motion/react"
import { motion, useReducedMotion, type Transition } from "motion/react"
import { cn } from "@/lib/utils"
interface BorderBeamProps {
/**
* The size of the border beam.
* Approximate length of the visible beam. Mapped to conic spread.
*/
size?: number
/**
* The duration of the border beam.
* The duration of one full loop (seconds).
*/
duration?: number
/**
@@ -51,49 +51,58 @@ interface BorderBeamProps {
borderWidth?: number
}
/**
* Soft beam riding the parent border via a masked rotating conic-gradient.
* Avoids offset-path corner breaks while staying flush with rounded frames.
*/
export const BorderBeam = ({
className,
size = 50,
delay = 0,
duration = 6,
colorFrom = "#FCC61D",
colorTo = "#01796F",
colorFrom = "#29A19C",
colorTo = "#044343",
transition,
style,
reverse = false,
initialOffset = 0,
borderWidth = 1,
}: BorderBeamProps) => {
const reduceMotion = useReducedMotion()
// Map legacy px "size" → angular span (deg). Clamped so the beam stays a short arc.
const spread = Math.min(48, Math.max(12, size * 0.18))
const startAngle = initialOffset * 3.6
return (
<div
className="pointer-events-none absolute inset-0 rounded-[inherit] border-(length:--border-beam-width) border-transparent [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)] [mask-composite:intersect] [mask-clip:padding-box,border-box]"
className="pointer-events-none absolute overflow-hidden rounded-[inherit] border-(length:--border-beam-width) border-transparent [mask-image:linear-gradient(transparent,transparent),linear-gradient(#000,#000)] [mask-composite:intersect] [mask-clip:padding-box,border-box]"
style={
{
// Overlay the parent’s border (e.g. .media-frame 1px).
inset: `-${borderWidth}px`,
"--border-beam-width": `${borderWidth}px`,
} as React.CSSProperties
}
>
<motion.div
className={cn(
"absolute aspect-square",
"bg-gradient-to-l from-[var(--color-from)] via-[var(--color-to)] to-transparent",
"absolute top-1/2 left-1/2 aspect-square w-[max(200%,200vw)]",
className
)}
style={
{
width: size,
offsetPath: `rect(0 auto auto 0 round ${size}px)`,
"--color-from": colorFrom,
"--color-to": colorTo,
x: "-50%",
y: "-50%",
background: `conic-gradient(from ${startAngle}deg, transparent 0deg, ${colorFrom} ${spread * 0.35}deg, ${colorTo} ${spread}deg, transparent ${spread * 1.65}deg)`,
...style,
} as MotionStyle
} as React.CSSProperties
}
initial={false}
animate={
reduceMotion
? { rotate: 0 }
: { rotate: reverse ? -360 : 360 }
}
initial={{ offsetDistance: `${initialOffset}%` }}
animate={{
offsetDistance: reverse
? [`${100 - initialOffset}%`, `${-initialOffset}%`]
: [`${initialOffset}%`, `${100 + initialOffset}%`],
}}
transition={{
repeat: Infinity,
ease: "linear",
+4 -4
View File
@@ -1,11 +1,11 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { ChevronLeft, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
return <nav aria-label="مسیر صفحه" data-slot="breadcrumb" {...props} />
}
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
@@ -75,7 +75,7 @@ function BreadcrumbSeparator({
className={cn("[&>svg]:size-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
{children ?? <ChevronLeft />}
</li>
)
}
@@ -93,7 +93,7 @@ function BreadcrumbEllipsis({
{...props}
>
<MoreHorizontal className="size-4" />
<span className="sr-only">More</span>
<span className="sr-only">بیشتر</span>
</span>
)
}
+102 -16
View File
@@ -2,37 +2,119 @@ import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { type as typeScale } from "@/lib/typography"
import { cn } from "@/lib/utils"
/**
* Unified button system
*
* Variants (marketing hierarchy):
* - default → Primary (main CTA / conversion)
* - secondary → Secondary (bordered supporting action)
* - ghost → Ghost (minimal / nav)
*
* Aliases kept for shadcn / existing blocks:
* - outline → same as secondary
*
* Utility variants:
* - destructive, link
*
* Surfaces:
* - default → normal page canvas
* - brand → teal / dark marketing panels (inverse contrast)
*
* Sizes: sm | default (medium) | lg | icon-sm | icon | icon-lg
*
* Persian text: padding and height are sized for IRANSansX so glyphs
* are not clipped; labels use `type.button` / `type["button-lg"]`.
*/
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg font-medium transition-colors 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",
[
"inline-flex items-center justify-center whitespace-nowrap",
"rounded-[var(--btn-radius)]",
"transition-[color,background-color,border-color,box-shadow,opacity,transform]",
"duration-200 ease-out",
"outline-none select-none shrink-0",
"disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none",
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
"[&_svg:not([class*='size-'])]:size-[var(--btn-icon-size)]",
"focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:border-ring",
"active:scale-[0.98]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
].join(" "),
{
variants: {
variant: {
// Primary — brand neon rim via shadow-glow-*
default:
"bg-primary text-primary-foreground hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border border-primary text-primary bg-transparent hover:bg-accent hover:text-foreground hover:border-accent dark:bg-input/30 dark:border-input dark:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
"bg-primary text-primary-foreground shadow-glow-sm hover:bg-primary-hover hover:shadow-glow-primary active:bg-primary-active",
// Secondary (bordered) — preferred name
secondary:
"bg-secondary text-secondary-foreground hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
"border border-surface bg-transparent text-foreground shadow-surface-sm hover:border-primary/45 hover:bg-accent hover:text-accent-foreground hover:shadow-glow-sm active:bg-accent/80 dark:border-white/18 dark:hover:border-primary/50 dark:hover:bg-accent",
// Alias of secondary (shadcn / imported blocks)
outline:
"border border-surface bg-transparent text-foreground shadow-surface-sm hover:border-primary/45 hover:bg-accent hover:text-accent-foreground hover:shadow-glow-sm active:bg-accent/80 dark:border-white/18 dark:hover:border-primary/50 dark:hover:bg-accent",
// Ghost
ghost:
"hover:bg-accent hover:text-foreground dark:hover:bg-[#FCC61D] dark:hover:text-foreground",
"bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground active:bg-accent/80 dark:hover:bg-white/8",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 hover:shadow-md focus-visible:ring-destructive/25 dark:focus-visible:ring-destructive/40 active:bg-destructive/85",
link:
"text-accent underline-offset-4 hover:underline",
"bg-transparent text-primary underline-offset-4 hover:underline hover:text-primary-hover active:text-primary-active",
},
surface: {
default: "",
/** Inverse contrast for brand / dark marketing panels */
brand: "",
},
size: {
default: "h-9 px-4 text-sm has-[>svg]:px-3",
sm: "h-8 gap-1.5 px-3 text-sm has-[>svg]:px-2.5",
lg: "h-11 px-8 text-base has-[>svg]:px-6",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
sm: cn(
"h-[var(--btn-height-sm)] gap-[var(--btn-gap-sm)] px-[var(--btn-px-sm)] [--btn-icon-size:0.875rem] has-[>svg]:px-[calc(var(--btn-px-sm)-0.125rem)]",
typeScale.button
),
default: cn(
"h-[var(--btn-height-md)] gap-[var(--btn-gap-md)] px-[var(--btn-px-md)] [--btn-icon-size:1rem] has-[>svg]:px-[calc(var(--btn-px-md)-0.25rem)]",
typeScale.button
),
lg: cn(
"h-[var(--btn-height-lg)] gap-[var(--btn-gap-lg)] px-[var(--btn-px-lg)] [--btn-icon-size:1.25rem] has-[>svg]:px-[calc(var(--btn-px-lg)-0.25rem)]",
typeScale["button-lg"]
),
"icon-sm":
"size-[var(--btn-height-sm)] gap-0 p-0 [--btn-icon-size:0.875rem]",
icon: "size-[var(--btn-height-md)] gap-0 p-0 [--btn-icon-size:1rem]",
"icon-lg":
"size-[var(--btn-height-lg)] gap-0 p-0 [--btn-icon-size:1.25rem]",
},
},
compoundVariants: [
{
variant: "default",
surface: "brand",
className:
"bg-white text-brand-dark shadow-glow-sm hover:bg-brand-light hover:text-brand-dark hover:shadow-glow-primary active:bg-brand-light/90",
},
{
variant: ["secondary", "outline"],
surface: "brand",
className:
"border-white/40 bg-transparent text-white shadow-none hover:border-white/60 hover:bg-white/10 hover:text-white hover:shadow-[0_0_0_1px_rgb(255_255_255_/_0.35),0_0_18px_-4px_rgb(255_255_255_/_0.35)] active:bg-white/15",
},
{
variant: "ghost",
surface: "brand",
className:
"text-white hover:bg-white/10 hover:text-white active:bg-white/15",
},
{
variant: "link",
surface: "brand",
className: "text-white hover:text-white/90",
},
],
defaultVariants: {
variant: "default",
surface: "default",
size: "default",
},
}
@@ -42,6 +124,7 @@ function Button({
className,
variant,
size,
surface,
asChild = false,
...props
}: React.ComponentProps<"button"> &
@@ -53,7 +136,10 @@ function Button({
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size }), className)}
data-variant={variant ?? "default"}
data-surface={surface ?? "default"}
data-size={size ?? "default"}
className={cn(buttonVariants({ variant, size, surface }), className)}
{...props}
/>
)
+1 -1
View File
@@ -8,7 +8,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm transition-all duration-300 hover:shadow-md hover:border-border/80",
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border border-surface py-6 shadow-glow-sm transition-[border-color,box-shadow] duration-200 hover:border-primary/30 hover:shadow-glow-primary dark:hover:border-primary/40 dark:hover:shadow-glow-lg",
className
)}
{...props}
+15 -8
View File
@@ -132,13 +132,20 @@ function Carousel({
)
}
function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {
function CarouselContent({
className,
viewportClassName,
...props
}: React.ComponentProps<"div"> & {
/** Classes for the Embla viewport (`overflow-hidden`). Use for glow/shadow bleed padding. */
viewportClassName?: string
}) {
const { carouselRef, orientation } = useCarousel()
return (
<div
ref={carouselRef}
className="overflow-hidden"
className={cn("overflow-hidden", viewportClassName)}
data-slot="carousel-content"
>
<div
@@ -173,7 +180,7 @@ function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {
function CarouselPrevious({
className,
variant = "outline",
variant = "secondary",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
@@ -185,7 +192,7 @@ function CarouselPrevious({
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
"absolute",
orientation === "horizontal"
? "top-1/2 -left-12 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
@@ -196,14 +203,14 @@ function CarouselPrevious({
{...props}
>
<ArrowLeft />
<span className="sr-only">Previous slide</span>
<span className="sr-only">اسلاید قبلی</span>
</Button>
)
}
function CarouselNext({
className,
variant = "outline",
variant = "secondary",
size = "icon",
...props
}: React.ComponentProps<typeof Button>) {
@@ -215,7 +222,7 @@ function CarouselNext({
variant={variant}
size={size}
className={cn(
"absolute size-8 rounded-full",
"absolute",
orientation === "horizontal"
? "top-1/2 -right-12 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
@@ -226,7 +233,7 @@ function CarouselNext({
{...props}
>
<ArrowRight />
<span className="sr-only">Next slide</span>
<span className="sr-only">اسلاید بعدی</span>
</Button>
)
}
+1 -1
View File
@@ -84,7 +84,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="dialog-header"
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
className={cn("flex flex-col gap-2 text-center sm:text-start", className)}
{...props}
/>
)
+1 -1
View File
@@ -77,7 +77,7 @@ function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="drawer-header"
className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-start",
className
)}
{...props}
+3 -2
View File
@@ -1,5 +1,6 @@
import { cva, type VariantProps } from "class-variance-authority"
import { type } from "@/lib/typography"
import { cn } from "@/lib/utils"
function Empty({ className, ...props }: React.ComponentProps<"div">) {
@@ -62,7 +63,7 @@ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("text-lg font-medium tracking-tight", className)}
className={cn(type.h3, "text-center font-medium", className)}
{...props}
/>
)
@@ -73,7 +74,7 @@ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
<div
data-slot="empty-description"
className={cn(
"text-muted-foreground [&>a]:text-accent text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4 hover:[&>a]:no-underline",
"text-muted-foreground [&>a]:text-primary text-sm leading-relaxed [&>a]:underline [&>a]:underline-offset-4 hover:[&>a]:no-underline",
className
)}
{...props}
+1 -1
View File
@@ -145,7 +145,7 @@ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
className={cn(
"text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance",
"last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
"[&>a]:text-accent [&>a]:underline [&>a]:underline-offset-4 hover:[&>a]:no-underline",
"[&>a]:text-primary [&>a]:underline [&>a]:underline-offset-4 hover:[&>a]:no-underline",
className
)}
{...props}
+6 -6
View File
@@ -80,15 +80,15 @@ function InputGroupAddon({
}
const inputGroupButtonVariants = cva(
"text-sm shadow-none flex gap-2 items-center",
"shadow-none flex items-center rounded-[var(--btn-radius)]",
{
variants: {
size: {
xs: "h-6 gap-1 px-2 rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-3.5 has-[>svg]:px-2",
sm: "h-8 px-2.5 gap-1.5 rounded-md has-[>svg]:px-2.5",
"icon-xs":
"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
"icon-sm": "size-8 p-0 has-[>svg]:p-0",
xs: "h-6 gap-[var(--btn-gap-sm)] px-2 text-xs [--btn-icon-size:0.875rem] has-[>svg]:px-2",
sm: "h-[var(--btn-height-sm)] gap-[var(--btn-gap-sm)] px-[var(--btn-px-sm)] text-sm [--btn-icon-size:0.875rem] has-[>svg]:px-[calc(var(--btn-px-sm)-0.125rem)]",
"icon-xs": "size-6 gap-0 p-0 [--btn-icon-size:0.875rem]",
"icon-sm":
"size-[var(--btn-height-sm)] gap-0 p-0 [--btn-icon-size:0.875rem]",
},
},
defaultVariants: {
+2 -2
View File
@@ -8,8 +8,8 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type}
data-slot="input"
className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground border-input bg-background h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base text-foreground shadow-xs transition-[color,background-color,border-color,box-shadow] duration-200 outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-surface/60",
"focus-visible:border-primary focus-visible:ring-primary/25 focus-visible:ring-[3px]",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
className
)}
+1 -1
View File
@@ -135,7 +135,7 @@ function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
data-slot="item-description"
className={cn(
"text-muted-foreground line-clamp-2 text-sm leading-normal font-normal text-balance",
"[&>a]:text-accent [&>a]:underline [&>a]:underline-offset-4 hover:[&>a]:no-underline",
"[&>a]:text-primary [&>a]:underline [&>a]:underline-offset-4 hover:[&>a]:no-underline",
className
)}
{...props}
+3 -3
View File
@@ -32,9 +32,9 @@ export function LogoItem({
<div className={cn("flex items-center justify-center", className)}>
<div
className={cn(
"flex items-center justify-center rounded-2xl border border-border/50 bg-white shadow-sm transition-all duration-300",
"hover:-translate-y-1 hover:shadow-md",
"dark:border-white/10 dark:bg-white/95 dark:shadow-lg dark:shadow-black/25",
"flex items-center justify-center rounded-xl border border-surface bg-card shadow-glow-sm transition-[border-color,box-shadow,transform] duration-200",
"hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-glow-primary",
"dark:bg-card dark:hover:border-primary/40",
tileSizeMap[size]
)}
>
+3 -3
View File
@@ -75,7 +75,7 @@ function NavigationMenuTrigger({
>
{children}{" "}
<ChevronDownIcon
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
className="relative top-[1px] ms-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
@@ -90,7 +90,7 @@ function NavigationMenuContent({
<NavigationMenuPrimitive.Content
data-slot="navigation-menu-content"
className={cn(
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 start-0 w-full p-2 pe-2.5 md:absolute md:w-auto",
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
className
)}
@@ -106,7 +106,7 @@ function NavigationMenuViewport({
return (
<div
className={cn(
"absolute top-full left-0 isolate z-50 flex justify-center"
"absolute top-full start-0 isolate z-50 flex justify-center"
)}
>
<NavigationMenuPrimitive.Viewport
+14 -11
View File
@@ -7,12 +7,13 @@ import {
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
import { type } from "@/lib/typography"
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return (
<nav
role="navigation"
aria-label="pagination"
aria-label="صفحه‌بندی"
data-slot="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
@@ -55,7 +56,7 @@ function PaginationLink({
data-active={isActive}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
variant: isActive ? "secondary" : "ghost",
size,
}),
className
@@ -71,13 +72,14 @@ function PaginationPrevious({
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to previous page"
aria-label="صفحه قبلی"
size="default"
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
className={cn("gap-1 px-2.5 sm:ps-2.5", className)}
{...props}
>
<ChevronLeftIcon />
<span className="hidden sm:block">Previous</span>
{/* In RTL, previous is toward the right */}
<ChevronRightIcon />
<span className={cn(type.button, "hidden sm:inline")}>قبلی</span>
</PaginationLink>
)
}
@@ -88,13 +90,14 @@ function PaginationNext({
}: React.ComponentProps<typeof PaginationLink>) {
return (
<PaginationLink
aria-label="Go to next page"
aria-label="صفحه بعدی"
size="default"
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
className={cn("gap-1 px-2.5 sm:pe-2.5", className)}
{...props}
>
<span className="hidden sm:block">Next</span>
<ChevronRightIcon />
<span className={cn(type.button, "hidden sm:inline")}>بعدی</span>
{/* In RTL, next is toward the left */}
<ChevronLeftIcon />
</PaginationLink>
)
}
@@ -111,7 +114,7 @@ function PaginationEllipsis({
{...props}
>
<MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span>
<span className="sr-only">صفحات بیشتر</span>
</span>
)
}
+3 -3
View File
@@ -37,7 +37,7 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-primary focus-visible:ring-primary/25 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-surface/60 dark:hover:bg-surface flex w-fit items-center justify-between gap-2 rounded-md border bg-background px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,background-color,border-color,box-shadow] duration-200 outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
@@ -109,12 +109,12 @@ function SelectItem({
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pe-8 ps-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<span className="absolute end-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
+2 -2
View File
@@ -70,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
<th
data-slot="table-head"
className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
"text-foreground h-10 px-2 text-start align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
@@ -83,7 +83,7 @@ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
<td
data-slot="table-cell"
className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pe-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
+2 -2
View File
@@ -51,8 +51,8 @@ export const BorderBeam = ({
size = 50,
delay = 0,
duration = 6,
colorFrom = "#FCC61D",
colorTo = "#0046FF",
colorFrom = "#29A19C",
colorTo = "#044343",
transition,
style,
reverse = false,
+1 -1
View File
@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea
data-slot="textarea"
className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"border-input bg-background placeholder:text-muted-foreground text-foreground focus-visible:border-primary focus-visible:ring-primary/25 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-surface/60 flex field-sizing-content min-h-16 w-full rounded-md border px-3 py-2 text-base shadow-xs transition-[color,background-color,border-color,box-shadow] duration-200 outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props}
+13
View File
@@ -17,10 +17,13 @@ const defaultElement: Record<TypographyVariant, React.ElementType> = {
body: "p",
"body-sm": "p",
caption: "span",
micro: "span",
eyebrow: "p",
label: "span",
nav: "span",
"nav-mobile": "span",
button: "span",
"button-lg": "span",
"card-title": "h3",
"card-desc": "p",
chat: "p",
@@ -75,6 +78,8 @@ export {
Eyebrow,
CardTitleText,
CardDescText,
ButtonText,
ButtonTextLarge,
};
function Display(props: Omit<TypographyProps, "variant">) {
@@ -124,3 +129,11 @@ function CardTitleText(props: Omit<TypographyProps, "variant">) {
function CardDescText(props: Omit<TypographyProps, "variant">) {
return <Typography variant="card-desc" {...props} />;
}
function ButtonText(props: Omit<TypographyProps, "variant">) {
return <Typography variant="button" {...props} />;
}
function ButtonTextLarge(props: Omit<TypographyProps, "variant">) {
return <Typography variant="button-lg" {...props} />;
}