translation bug fixed
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
import { motion } from "framer-motion";
|
||||
import { CheckCircle, Award, Users, Zap } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export function About() {
|
||||
const t = useTranslations();
|
||||
const {t} = useLanguage();
|
||||
|
||||
const features = [
|
||||
{ icon: Award, labelKey: "Experience", value: "10+ лет" },
|
||||
@@ -37,7 +37,7 @@ export function About() {
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
{t("about.title")}
|
||||
{t.about.title}
|
||||
</h2>
|
||||
<div className="w-20 h-1 bg-blue-600 mx-auto rounded-full" />
|
||||
</motion.div>
|
||||
@@ -51,7 +51,7 @@ export function About() {
|
||||
viewport={{ once: true }}
|
||||
>
|
||||
<p className="text-lg text-gray-700 leading-relaxed mb-8">
|
||||
{t("about.content")}
|
||||
{t.about.content}
|
||||
</p>
|
||||
|
||||
<motion.div
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { sendContactMessage } from "@/lib/api";
|
||||
import { Phone, MessageSquare, MapPin } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
|
||||
export function ContactForm() {
|
||||
const t = useTranslations();
|
||||
const {t} = useLanguage();
|
||||
const pathname = usePathname();
|
||||
const locale = (pathname.split("/")[1] || "uz") as "uz" | "ru";
|
||||
|
||||
@@ -49,13 +49,13 @@ export function ContactForm() {
|
||||
});
|
||||
|
||||
if (result.success) {
|
||||
setMessage({ type: "success", text: t("contact.success") });
|
||||
setMessage({ type: "success", text: t.contact.success });
|
||||
setFormData({ name: "", phone: "", message: "", productSlug: "" });
|
||||
} else {
|
||||
setMessage({ type: "error", text: t("contact.error") });
|
||||
setMessage({ type: "error", text: t.contact.error });
|
||||
}
|
||||
} catch {
|
||||
setMessage({ type: "error", text: t("contact.error") });
|
||||
setMessage({ type: "error", text: t.contact.error });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export function ContactForm() {
|
||||
className="text-center mb-16 bg-white/80 backdrop-blur-md rounded-xl overflow-hidden p-2 max-w-[300px] w-full mx-auto"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{t("contact.title")}
|
||||
{t.contact.title}
|
||||
</h2>
|
||||
<div className="w-20 h-1 bg-blue-600 mx-auto rounded-full" />
|
||||
</motion.div>
|
||||
@@ -155,14 +155,14 @@ export function ContactForm() {
|
||||
{/* Name */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700 font-medium mb-2">
|
||||
{t("contact.name")}
|
||||
{t.contact.name}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
placeholder={t("contact.namePlaceholder")}
|
||||
placeholder={t.contact.namePlaceholder}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
</div>
|
||||
@@ -170,14 +170,14 @@ export function ContactForm() {
|
||||
{/* Phone */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700 font-medium mb-2">
|
||||
{t("contact.phone")} *
|
||||
{t.contact.phone} *
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
name="phone"
|
||||
value={formData.phone}
|
||||
onChange={handleChange}
|
||||
placeholder={t("contact.phonePlaceholder")}
|
||||
placeholder={t.contact.phonePlaceholder}
|
||||
required
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
@@ -186,13 +186,13 @@ export function ContactForm() {
|
||||
{/* Message */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700 font-medium mb-2">
|
||||
{t("contact.message")}
|
||||
{t.contact.message}
|
||||
</label>
|
||||
<textarea
|
||||
name="message"
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
placeholder={t("contact.messagePlaceholder")}
|
||||
placeholder={t.contact.messagePlaceholder}
|
||||
rows={4}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
||||
/>
|
||||
@@ -201,7 +201,7 @@ export function ContactForm() {
|
||||
{/* Product Select */}
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700 font-medium mb-2">
|
||||
{t("contact.product")}
|
||||
{t.contact.product}
|
||||
</label>
|
||||
<select
|
||||
name="productSlug"
|
||||
@@ -239,7 +239,7 @@ export function ContactForm() {
|
||||
disabled={loading}
|
||||
className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? "Sending..." : t("contact.send")}
|
||||
{loading ? "Sending..." : t.contact.send}
|
||||
</motion.button>
|
||||
</motion.form>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useState } from "react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
|
||||
interface FaqItem {
|
||||
questionKey: string;
|
||||
@@ -15,7 +15,7 @@ interface FaqProps {
|
||||
}
|
||||
|
||||
export function FAQ({ items }: FaqProps) {
|
||||
const t = useTranslations();
|
||||
const {t} = useLanguage();
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(0);
|
||||
|
||||
const defaultItems: FaqItem[] = [
|
||||
@@ -60,7 +60,7 @@ export function FAQ({ items }: FaqProps) {
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<h2 className="text-4xl font-bold text-gray-900 mb-4">
|
||||
{t("faq.title")}
|
||||
{t.faq.title}
|
||||
</h2>
|
||||
<div className="w-20 h-1 bg-blue-600 mx-auto rounded-full" />
|
||||
</motion.div>
|
||||
@@ -81,7 +81,7 @@ export function FAQ({ items }: FaqProps) {
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-gray-900 flex-1">
|
||||
{t(item.questionKey)}
|
||||
{t.faq.items[idx].question}
|
||||
</h3>
|
||||
<motion.div
|
||||
animate={{ rotate: openIndex === idx ? 180 : 0 }}
|
||||
@@ -104,7 +104,7 @@ export function FAQ({ items }: FaqProps) {
|
||||
>
|
||||
<div className="bg-blue-50 p-6 rounded-b-lg border-t border-gray-200">
|
||||
<p className="text-gray-700 leading-relaxed">
|
||||
{t(item.answerKey)}
|
||||
{t.faq.items[idx].answer}
|
||||
</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Facebook, Linkedin, Send } from "lucide-react";
|
||||
|
||||
export function Footer() {
|
||||
const t = useTranslations();
|
||||
|
||||
const {t} = useLanguage();
|
||||
const socialLinks = [
|
||||
{ icon: Facebook, href: "#", label: "Facebook" },
|
||||
{ icon: Linkedin, href: "#", label: "LinkedIn" },
|
||||
@@ -86,7 +85,7 @@ export function Footer() {
|
||||
|
||||
{/* Social */}
|
||||
<motion.div variants={itemVariants}>
|
||||
<h4 className="font-semibold mb-4">{t("footer.followUs")}</h4>
|
||||
<h4 className="font-semibold mb-4">{t.footer.followUs}</h4>
|
||||
<div className="flex gap-4">
|
||||
{socialLinks.map((link) => {
|
||||
const Icon = link.icon;
|
||||
@@ -116,7 +115,7 @@ export function Footer() {
|
||||
viewport={{ once: true }}
|
||||
className="text-center text-gray-400 text-sm"
|
||||
>
|
||||
{t("footer.copyright")}
|
||||
{t.footer.copyright}
|
||||
</motion.p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, X } from "lucide-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import LanguageSwitcher from "./languageSwitcher";
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
|
||||
interface NavLink {
|
||||
id: string;
|
||||
@@ -19,20 +19,15 @@ interface NavbarProps {
|
||||
|
||||
export function Navbar({ logoText = "FIRMA" }: NavbarProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const t = useTranslations();
|
||||
const pathname = usePathname();
|
||||
const { t } = useLanguage();
|
||||
|
||||
const navLinks: NavLink[] = [
|
||||
{ id: "about", labelKey: "nav.about", href: "#about" },
|
||||
{ id: "products", labelKey: "nav.products", href: "#products" },
|
||||
{ id: "faq", labelKey: "nav.faq", href: "#faq" },
|
||||
{ id: "contact", labelKey: "nav.contact", href: "#contact" },
|
||||
{ id: "about", labelKey: t.nav.about, href: "#about" },
|
||||
{ id: "products", labelKey: t.nav.products, href: "#products" },
|
||||
{ id: "faq", labelKey: t.nav.faq, href: "#faq" },
|
||||
{ id: "contact", labelKey: t.nav.contact , href: "#contact" },
|
||||
];
|
||||
|
||||
const locale = pathname.split("/")[1];
|
||||
const otherLocale = locale === "uz" ? "ru" : "uz";
|
||||
const otherPath = pathname.replace(`/${locale}`, `/${otherLocale}`);
|
||||
|
||||
const handleScroll = (href: string) => {
|
||||
if (href.startsWith("#")) {
|
||||
const element = document.querySelector(href);
|
||||
@@ -50,7 +45,7 @@ export function Navbar({ logoText = "FIRMA" }: NavbarProps) {
|
||||
{/* Logo */}
|
||||
<motion.div whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}>
|
||||
<Link
|
||||
href={`/${locale}`}
|
||||
href={`/`}
|
||||
className="text-2xl font-bold bg-linear-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent"
|
||||
>
|
||||
{logoText}
|
||||
@@ -66,21 +61,14 @@ export function Navbar({ logoText = "FIRMA" }: NavbarProps) {
|
||||
onClick={() => handleScroll(link.href)}
|
||||
className="text-gray-700 hover:text-blue-600 transition-colors"
|
||||
>
|
||||
{t(link.labelKey)}
|
||||
{link.labelKey}
|
||||
</motion.button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Language & Mobile Menu */}
|
||||
<div className="flex items-center gap-4">
|
||||
<motion.a
|
||||
href={otherPath}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="px-3 py-1 bg-blue-600 text-white rounded-full text-sm font-medium hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
{otherLocale.toUpperCase()}
|
||||
</motion.a>
|
||||
<LanguageSwitcher />
|
||||
|
||||
{/* Mobile Menu Button */}
|
||||
<button
|
||||
@@ -106,7 +94,7 @@ export function Navbar({ logoText = "FIRMA" }: NavbarProps) {
|
||||
onClick={() => handleScroll(link.href)}
|
||||
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-blue-50 rounded-lg transition-colors"
|
||||
>
|
||||
{t(link.labelKey)}
|
||||
{link.labelKey}
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import Image from "next/image";
|
||||
import { motion } from "framer-motion";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { Product } from "@/lib/products";
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
|
||||
interface ProductCardProps {
|
||||
product: Product;
|
||||
@@ -12,8 +12,7 @@ interface ProductCardProps {
|
||||
}
|
||||
|
||||
export function ProductCard({ product, onViewDetails }: ProductCardProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const {t} = useLanguage();
|
||||
return (
|
||||
<motion.div
|
||||
whileHover={{ y: -8 }}
|
||||
@@ -23,7 +22,7 @@ export function ProductCard({ product, onViewDetails }: ProductCardProps) {
|
||||
<div className="relative h-48 bg-gray-100 overflow-hidden group">
|
||||
<Image
|
||||
src={product.images[0]}
|
||||
alt={t(product.nameKey)}
|
||||
alt={product.nameKey}
|
||||
fill
|
||||
className="object-cover group-hover:scale-110 transition-transform duration-300"
|
||||
/>
|
||||
@@ -33,10 +32,10 @@ export function ProductCard({ product, onViewDetails }: ProductCardProps) {
|
||||
{/* Content */}
|
||||
<div className="p-6">
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-2">
|
||||
{t(product.nameKey)}
|
||||
{product.nameKey}
|
||||
</h3>
|
||||
<p className="text-gray-600 text-sm mb-4">
|
||||
{t(product.shortDescriptionKey)}
|
||||
{product.shortDescriptionKey}
|
||||
</p>
|
||||
|
||||
{/* Specs Preview */}
|
||||
@@ -56,7 +55,7 @@ export function ProductCard({ product, onViewDetails }: ProductCardProps) {
|
||||
onClick={() => onViewDetails(product.slug)}
|
||||
className="w-full flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg font-medium hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
{t("products.viewDetails")}
|
||||
Batafsil
|
||||
<ExternalLink size={16} />
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
import { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { ProductCard } from "./ProductCard";
|
||||
import { getAllProducts } from "@/lib/products";
|
||||
import type { Product } from "@/lib/products";
|
||||
import { ProductModal } from "./ProductModal";
|
||||
import Image from "next/image";
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
|
||||
// hello everyone
|
||||
|
||||
export function ProductsGrid() {
|
||||
const t = useTranslations();
|
||||
const {t} = useLanguage();
|
||||
const products = getAllProducts();
|
||||
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
|
||||
|
||||
@@ -58,7 +58,7 @@ export function ProductsGrid() {
|
||||
className="text-center mb-16 bg-white/80 backdrop-blur-md rounded-xl overflow-hidden p-2 max-w-[300px] w-full mx-auto"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{t("products.title")}
|
||||
{t.products.title}
|
||||
</h2>
|
||||
<div className="w-20 h-1 bg-blue-600 mx-auto rounded-full" />
|
||||
</motion.div>
|
||||
|
||||
@@ -4,24 +4,16 @@ import { useState, useEffect } from "react";
|
||||
import Image from "next/image";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
|
||||
//hello again dear
|
||||
|
||||
interface ShowCaseProps {
|
||||
titleKey: string;
|
||||
subtitleKey?: string;
|
||||
ctaLabelKey: string;
|
||||
images: string[];
|
||||
}
|
||||
|
||||
export function ShowCase({
|
||||
titleKey,
|
||||
subtitleKey,
|
||||
ctaLabelKey,
|
||||
images,
|
||||
}: ShowCaseProps) {
|
||||
const t = useTranslations();
|
||||
export function ShowCase({ images }: ShowCaseProps) {
|
||||
const { t } = useLanguage();
|
||||
const [currentImageIndex, setCurrentImageIndex] = useState(0);
|
||||
const [autoPlay, setAutoPlay] = useState(true);
|
||||
|
||||
@@ -54,7 +46,6 @@ export function ShowCase({
|
||||
|
||||
return (
|
||||
<section className="relative min-h-screen pt-20 pb-20">
|
||||
|
||||
{/* background image */}
|
||||
<div className="absolute -z-50 top-0 left-0 h-full w-full">
|
||||
<Image
|
||||
@@ -76,14 +67,11 @@ export function ShowCase({
|
||||
className="bg-white/80 backdrop-blur-md rounded-xl overflow-hidden p-4"
|
||||
>
|
||||
<h1 className="text-4xl lg:text-5xl font-bold text-gray-900 mb-6 leading-tight">
|
||||
{t(titleKey)}
|
||||
{t.hero.title}
|
||||
</h1>
|
||||
|
||||
{subtitleKey && (
|
||||
<p className="text-lg text-gray-600 mb-8 leading-relaxed">
|
||||
{t(subtitleKey)}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-lg text-gray-600 mb-8 leading-relaxed">
|
||||
{t.hero.subtitle}
|
||||
</p>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
@@ -91,7 +79,7 @@ export function ShowCase({
|
||||
onClick={handleContactClick}
|
||||
className="px-8 py-3 bg-linear-to-r from-blue-600 to-blue-700 text-white rounded-lg font-semibold hover:shadow-lg transition-shadow"
|
||||
>
|
||||
{t(ctaLabelKey)}
|
||||
{t.hero.cta}
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
|
||||
@@ -102,7 +90,7 @@ export function ShowCase({
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="relative"
|
||||
>
|
||||
<div className="relative aspect-square rounded-xl overflow-hidden shadow-2xl bg-gray-100">
|
||||
<div className="relative aspect-square rounded-xl overflow-hidden">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={currentImageIndex}
|
||||
|
||||
45
components/languageSwitcher.tsx
Normal file
45
components/languageSwitcher.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useLanguage } from "@/context/language-context";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "./ui/dropdown-menu";
|
||||
import { Globe } from "lucide-react";
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
const { language, setLanguage } = useLanguage();
|
||||
const languages = [
|
||||
{ code: "uz" as const, name: "O'zbekcha" },
|
||||
{ code: "ru" as const, name: "Русский" },
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="flex items-center gap-1 hover:text-primary transition-colors">
|
||||
<Globe size={16} />
|
||||
{language.toUpperCase()}
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="bg-slate-800 border-slate-700"
|
||||
>
|
||||
{languages.map((lang) => (
|
||||
<DropdownMenuItem
|
||||
key={lang.code}
|
||||
onClick={() => setLanguage(lang.code)}
|
||||
className="cursor-pointer hover:bg-slate-700 text-white"
|
||||
>
|
||||
{lang.name}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
257
components/ui/dropdown-menu.tsx
Normal file
257
components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
function DropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Trigger
|
||||
data-slot="dropdown-menu-trigger"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = 'default',
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
variant?: 'default' | 'destructive'
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive! [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
'px-2 py-1.5 text-sm font-medium data-inset:pl-8',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<'span'>) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn(
|
||||
'text-muted-foreground ml-auto text-xs tracking-widest',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />
|
||||
}
|
||||
|
||||
function DropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-inset:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
function DropdownMenuSubContent({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-32 origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuSubContent,
|
||||
}
|
||||
Reference in New Issue
Block a user