feat: redesign landing page with brand system and section blocks
Build & Push Docker Image / build (push) Has been cancelled
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:
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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]
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user