diff --git a/src/app/(features)/alphabet/AlphabetCard.tsx b/src/app/(features)/alphabet/AlphabetCard.tsx index e985c64..38b4036 100644 --- a/src/app/(features)/alphabet/AlphabetCard.tsx +++ b/src/app/(features)/alphabet/AlphabetCard.tsx @@ -3,7 +3,7 @@ import { useState, useEffect, useCallback } from "react"; import { useTranslations } from "next-intl"; import { Letter, SupportedAlphabets } from "@/lib/interfaces"; -import IconClick from "@/components/ui/buttons/IconClick"; +import { IconClick } from "@/components/ui/buttons"; import IMAGES from "@/config/images"; import { ChevronLeft, ChevronRight } from "lucide-react"; diff --git a/src/app/(features)/alphabet/MemoryCard.tsx b/src/app/(features)/alphabet/MemoryCard.tsx index ecb8d0b..a14bf8a 100644 --- a/src/app/(features)/alphabet/MemoryCard.tsx +++ b/src/app/(features)/alphabet/MemoryCard.tsx @@ -1,5 +1,5 @@ -import LightButton from "@/components/ui/buttons/LightButton"; -import IconClick from "@/components/ui/buttons/IconClick"; +import { LightButton } from "@/components/ui/buttons"; +import { IconClick } from "@/components/ui/buttons"; import IMAGES from "@/config/images"; import { Letter, SupportedAlphabets } from "@/lib/interfaces"; import { diff --git a/src/app/(features)/alphabet/page.tsx b/src/app/(features)/alphabet/page.tsx index b341799..b38131f 100644 --- a/src/app/(features)/alphabet/page.tsx +++ b/src/app/(features)/alphabet/page.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { Letter, SupportedAlphabets } from "@/lib/interfaces"; import Container from "@/components/ui/Container"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import AlphabetCard from "./AlphabetCard"; export default function Alphabet() { diff --git a/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx b/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx index 37e53e9..5c1e469 100644 --- a/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx +++ b/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx @@ -1,6 +1,6 @@ import { useState, useRef, forwardRef, useEffect, useCallback } from "react"; import SubtitleDisplay from "./SubtitleDisplay"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { getIndex, parseSrt, getNearistIndex } from "../subtitle"; import { useTranslations } from "next-intl"; diff --git a/src/app/(features)/srt-player/components/atoms/PlayButton.tsx b/src/app/(features)/srt-player/components/atoms/PlayButton.tsx index d3d7332..c9bb4d3 100644 --- a/src/app/(features)/srt-player/components/atoms/PlayButton.tsx +++ b/src/app/(features)/srt-player/components/atoms/PlayButton.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useTranslations } from "next-intl"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { PlayButtonProps } from "../../types/player"; export default function PlayButton({ isPlaying, onToggle, disabled, className }: PlayButtonProps) { diff --git a/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx b/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx index 0ef233e..36644f1 100644 --- a/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx +++ b/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { SpeedControlProps } from "../../types/player"; import { getPlaybackRateOptions, getPlaybackRateLabel } from "../../utils/timeUtils"; diff --git a/src/app/(features)/srt-player/components/compounds/ControlBar.tsx b/src/app/(features)/srt-player/components/compounds/ControlBar.tsx index 1c4a3cd..91f946d 100644 --- a/src/app/(features)/srt-player/components/compounds/ControlBar.tsx +++ b/src/app/(features)/srt-player/components/compounds/ControlBar.tsx @@ -3,7 +3,7 @@ import React from "react"; import { useTranslations } from "next-intl"; import { ChevronLeft, ChevronRight, RotateCcw, Pause } from "lucide-react"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { ControlBarProps } from "../../types/controls"; import PlayButton from "../atoms/PlayButton"; import SpeedControl from "../atoms/SpeedControl"; diff --git a/src/app/(features)/srt-player/components/compounds/UploadZone.tsx b/src/app/(features)/srt-player/components/compounds/UploadZone.tsx index fb9a694..7f7d9b7 100644 --- a/src/app/(features)/srt-player/components/compounds/UploadZone.tsx +++ b/src/app/(features)/srt-player/components/compounds/UploadZone.tsx @@ -4,7 +4,7 @@ import React from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { Video, FileText } from "lucide-react"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { FileUploadProps } from "../../types/controls"; import { useFileUpload } from "../../hooks/useFileUpload"; diff --git a/src/app/(features)/srt-player/page.tsx b/src/app/(features)/srt-player/page.tsx index 9f82083..bc1add3 100644 --- a/src/app/(features)/srt-player/page.tsx +++ b/src/app/(features)/srt-player/page.tsx @@ -14,7 +14,7 @@ import SubtitleArea from "./components/compounds/SubtitleArea"; import ControlBar from "./components/compounds/ControlBar"; import UploadZone from "./components/compounds/UploadZone"; import SeekBar from "./components/atoms/SeekBar"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; export default function SrtPlayerPage() { const t = useTranslations("home"); diff --git a/src/app/(features)/text-speaker/SaveList.tsx b/src/app/(features)/text-speaker/SaveList.tsx index 25d81e0..e67d065 100644 --- a/src/app/(features)/text-speaker/SaveList.tsx +++ b/src/app/(features)/text-speaker/SaveList.tsx @@ -6,7 +6,7 @@ import { TextSpeakerArraySchema, TextSpeakerItemSchema, } from "@/lib/interfaces"; -import IconClick from "@/components/ui/buttons/IconClick"; +import { IconClick } from "@/components/ui/buttons"; import IMAGES from "@/config/images"; import { useTranslations } from "next-intl"; import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators"; diff --git a/src/app/(features)/text-speaker/page.tsx b/src/app/(features)/text-speaker/page.tsx index 6ac9e94..7bc6529 100644 --- a/src/app/(features)/text-speaker/page.tsx +++ b/src/app/(features)/text-speaker/page.tsx @@ -1,7 +1,7 @@ "use client"; -import LightButton from "@/components/ui/buttons/LightButton"; -import IconClick from "@/components/ui/buttons/IconClick"; +import { LightButton } from "@/components/ui/buttons"; +import { IconClick } from "@/components/ui/buttons"; import IMAGES from "@/config/images"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { diff --git a/src/app/(features)/translator/AddToFolder.tsx b/src/app/(features)/translator/AddToFolder.tsx index bd47cfc..382f263 100644 --- a/src/app/(features)/translator/AddToFolder.tsx +++ b/src/app/(features)/translator/AddToFolder.tsx @@ -1,6 +1,6 @@ "use client"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import Container from "@/components/ui/Container"; import { TranslationHistorySchema } from "@/lib/interfaces"; import { Dispatch, useEffect, useState } from "react"; diff --git a/src/app/(features)/translator/FolderSelector.tsx b/src/app/(features)/translator/FolderSelector.tsx index b7a6388..5bb696c 100644 --- a/src/app/(features)/translator/FolderSelector.tsx +++ b/src/app/(features)/translator/FolderSelector.tsx @@ -2,7 +2,7 @@ import Container from "@/components/ui/Container"; import { useEffect, useState } from "react"; import { Folder } from "../../../../generated/prisma/browser"; import { getFoldersByUserId } from "@/lib/server/services/folderService"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { Folder as Fd } from "lucide-react"; interface FolderSelectorProps { diff --git a/src/app/(features)/translator/page.tsx b/src/app/(features)/translator/page.tsx index b257ecb..4a0f501 100644 --- a/src/app/(features)/translator/page.tsx +++ b/src/app/(features)/translator/page.tsx @@ -1,7 +1,7 @@ "use client"; -import LightButton from "@/components/ui/buttons/LightButton"; -import IconClick from "@/components/ui/buttons/IconClick"; +import { LightButton } from "@/components/ui/buttons"; +import { IconClick } from "@/components/ui/buttons"; import IMAGES from "@/config/images"; import { VOICES } from "@/config/locales"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; diff --git a/src/app/auth/AuthForm.tsx b/src/app/auth/AuthForm.tsx index 030a44d..0e7f2a7 100644 --- a/src/app/auth/AuthForm.tsx +++ b/src/app/auth/AuthForm.tsx @@ -5,7 +5,7 @@ import { useTranslations } from "next-intl"; import { signInAction, signUpAction, SignUpState } from "@/lib/actions/auth"; import Container from "@/components/ui/Container"; import Input from "@/components/ui/Input"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { authClient } from "@/lib/auth-client"; interface AuthFormProps { diff --git a/src/app/folders/[folder_id]/AddTextPairModal.tsx b/src/app/folders/[folder_id]/AddTextPairModal.tsx index 431b60f..1c0a39b 100644 --- a/src/app/folders/[folder_id]/AddTextPairModal.tsx +++ b/src/app/folders/[folder_id]/AddTextPairModal.tsx @@ -1,8 +1,9 @@ -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import Input from "@/components/ui/Input"; import { X } from "lucide-react"; -import { useRef } from "react"; +import { useRef, useState } from "react"; import { useTranslations } from "next-intl"; +import { LOCALES } from "@/config/locales"; interface AddTextPairModalProps { isOpen: boolean; @@ -15,6 +16,53 @@ interface AddTextPairModalProps { ) => void; } +const COMMON_LOCALES = [ + { label: "中文", value: "zh-CN" }, + { label: "英文", value: "en-US" }, + { label: "意大利语", value: "it-IT" }, + { label: "日语", value: "ja-JP" }, + { label: "其他", value: "other" }, +]; + +interface LocaleSelectorProps { + value: string; + onChange: (val: string) => void; +} + +function LocaleSelector({ value, onChange }: LocaleSelectorProps) { + const isCommonLocale = COMMON_LOCALES.some((l) => l.value === value && l.value !== "other"); + const showFullList = value === "other" || !isCommonLocale; + + return ( +
+ + {showFullList && ( + + )} +
+ ); +} + export default function AddTextPairModal({ isOpen, onClose, @@ -23,23 +71,23 @@ export default function AddTextPairModal({ const t = useTranslations("folder_id"); const input1Ref = useRef(null); const input2Ref = useRef(null); - const input3Ref = useRef(null); - const input4Ref = useRef(null); + const [locale1, setLocale1] = useState("en-US"); + const [locale2, setLocale2] = useState("zh-CN"); + if (!isOpen) return null; const handleAdd = () => { if ( !input1Ref.current?.value || !input2Ref.current?.value || - !input3Ref.current?.value || - !input4Ref.current?.value + !locale1 || + !locale2 ) return; const text1 = input1Ref.current.value; const text2 = input2Ref.current.value; - const locale1 = input3Ref.current.value; - const locale2 = input4Ref.current.value; + if ( typeof text1 === "string" && typeof text2 === "string" && @@ -55,6 +103,7 @@ export default function AddTextPairModal({ input2Ref.current.value = ""; } }; + return (
{t("locale1")} - +
{t("locale2")} - +
{t("add")} diff --git a/src/app/folders/[folder_id]/InFolder.tsx b/src/app/folders/[folder_id]/InFolder.tsx index fa5b9ce..2c1dc40 100644 --- a/src/app/folders/[folder_id]/InFolder.tsx +++ b/src/app/folders/[folder_id]/InFolder.tsx @@ -12,8 +12,8 @@ import AddTextPairModal from "./AddTextPairModal"; import TextPairCard from "./TextPairCard"; import { useTranslations } from "next-intl"; import PageLayout from "@/components/ui/PageLayout"; -import GreenButton from "@/components/ui/buttons/GreenButton"; -import IconButton from "@/components/ui/buttons/IconButton"; +import { GreenButton } from "@/components/ui/buttons"; +import { IconButton } from "@/components/ui/buttons"; import CardList from "@/components/ui/CardList"; export interface TextPair { diff --git a/src/app/folders/[folder_id]/UpdateTextPairModal.tsx b/src/app/folders/[folder_id]/UpdateTextPairModal.tsx index 533e18e..9ee3a57 100644 --- a/src/app/folders/[folder_id]/UpdateTextPairModal.tsx +++ b/src/app/folders/[folder_id]/UpdateTextPairModal.tsx @@ -1,4 +1,4 @@ -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import Input from "@/components/ui/Input"; import { X } from "lucide-react"; import { useRef } from "react"; diff --git a/src/app/profile/LogoutButton.tsx b/src/app/profile/LogoutButton.tsx index ad553b0..02511df 100644 --- a/src/app/profile/LogoutButton.tsx +++ b/src/app/profile/LogoutButton.tsx @@ -1,6 +1,6 @@ "use client"; -import LightButton from "@/components/ui/buttons/LightButton"; +import { LightButton } from "@/components/ui/buttons"; import { authClient } from "@/lib/auth-client"; import { useTranslations } from "next-intl"; import { useRouter } from "next/navigation"; diff --git a/src/components/LanguageSettings.tsx b/src/components/LanguageSettings.tsx index fb68c8a..0d4c259 100644 --- a/src/components/LanguageSettings.tsx +++ b/src/components/LanguageSettings.tsx @@ -1,9 +1,8 @@ "use client"; import IMAGES from "@/config/images"; -import IconClick from "./ui/buttons/IconClick"; +import { IconClick, GhostButton } from "./ui/buttons"; import { useState } from "react"; -import GhostButton from "./ui/buttons/GhostButton"; export default function LanguageSettings() { const [showLanguageMenu, setShowLanguageMenu] = useState(false); @@ -21,6 +20,7 @@ export default function LanguageSettings() { alt="language" disableOnHoverBgChange={true} onClick={handleLanguageClick} + size={40} >
{showLanguageMenu && ( diff --git a/src/components/layout/Navbar.tsx b/src/components/layout/Navbar.tsx index 3f91b70..05781ca 100644 --- a/src/components/layout/Navbar.tsx +++ b/src/components/layout/Navbar.tsx @@ -1,11 +1,11 @@ import Image from "next/image"; import IMAGES from "@/config/images"; -import { Folder, Home } from "lucide-react"; +import { Folder, Home, User } from "lucide-react"; import LanguageSettings from "../LanguageSettings"; import { auth } from "@/auth"; import { headers } from "next/headers"; import { getTranslations } from "next-intl/server"; -import GhostButton from "../ui/buttons/GhostButton"; +import { GhostButton } from "../ui/buttons"; export async function Navbar() { const t = await getTranslations("navbar"); @@ -14,46 +14,56 @@ export async function Navbar() { }); return ( -
- +
+ {t("title")} - - + + -
+
+ GitHub - - + {t("folders")} - - + + - { - (() => { - return session && - {t("profile")} - || {t("sign_in")}; - - })() - } {t("sourceCode")} + { + (() => { + return session && + <> + {t("profile")} + + + + + || <> + {t("sign_in")} + + + + ; + + })() + }
); diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx new file mode 100644 index 0000000..267f47a --- /dev/null +++ b/src/components/ui/Button.tsx @@ -0,0 +1,163 @@ +"use client"; + +import React from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { COLORS } from "@/lib/theme/colors"; + +export type ButtonVariant = "primary" | "secondary" | "ghost" | "icon"; +export type ButtonSize = "sm" | "md" | "lg"; + +export interface ButtonProps { + // Content + children?: React.ReactNode; + + // Behavior + onClick?: () => void; + disabled?: boolean; + type?: "button" | "submit" | "reset"; + + // Styling + variant?: ButtonVariant; + size?: ButtonSize; + className?: string; + selected?: boolean; + style?: React.CSSProperties; + + // Icons + leftIcon?: React.ReactNode; + rightIcon?: React.ReactNode; + iconSrc?: string; // For Next.js Image icons + iconAlt?: string; + + // Navigation + href?: string; +} + +export default function Button({ + variant = "secondary", + size = "md", + selected = false, + href, + iconSrc, + iconAlt, + leftIcon, + rightIcon, + children, + className = "", + style, + type = "button", + disabled = false, + ...props +}: ButtonProps) { + // Base classes + const baseClasses = "inline-flex items-center justify-center gap-2 rounded font-bold shadow hover:cursor-pointer transition-colors"; + + // Variant-specific classes + const variantStyles: Record = { + primary: ` + text-white + hover:opacity-90 + `, + secondary: ` + text-black + hover:bg-gray-100 + `, + ghost: ` + hover:bg-black/30 + p-2 + `, + icon: ` + p-2 bg-gray-200 rounded-full + hover:bg-gray-300 + ` + }; + + // Size-specific classes + const sizeStyles: Record = { + sm: "px-3 py-1 text-sm", + md: "px-4 py-2", + lg: "px-6 py-3 text-lg" + }; + + const variantClass = variantStyles[variant]; + const sizeClass = sizeStyles[size]; + + // Selected state for secondary variant + const selectedClass = variant === "secondary" && selected ? "bg-gray-100" : ""; + + // Background color for primary variant + const backgroundColor = variant === "primary" ? COLORS.primary : undefined; + + // Combine all classes + const combinedClasses = ` + ${baseClasses} + ${variantClass} + ${sizeClass} + ${selectedClass} + ${disabled ? 'opacity-50 cursor-not-allowed' : ''} + ${className} + `.trim().replace(/\s+/g, " "); + + // Icon rendering helper for SVG icons + const renderSvgIcon = (icon: React.ReactNode, position: "left" | "right") => { + if (!icon) return null; + return ( + + {icon} + + ); + }; + + // Image icon rendering for Next.js Image + const renderImageIcon = () => { + if (!iconSrc) return null; + const sizeMap = { sm: 16, md: 20, lg: 24 }; + const imgSize = sizeMap[size] || 20; + + return ( + {iconAlt + ); + }; + + // Content assembly + const content = ( + <> + {renderImageIcon()} + {renderSvgIcon(leftIcon, "left")} + {children} + {renderSvgIcon(rightIcon, "right")} + + ); + + // If href is provided, render as Link + if (href) { + return ( + + {content} + + ); + } + + // Otherwise render as button + return ( + + ); +} diff --git a/src/components/ui/buttons/GhostButton.tsx b/src/components/ui/buttons/GhostButton.tsx deleted file mode 100644 index a6ef6b7..0000000 --- a/src/components/ui/buttons/GhostButton.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import Link from "next/link"; - -export type ButtonType = "button" | "submit" | "reset" | undefined; - -export default function GhostButton({ - onClick, - className, - children, - type = "button", - href -}: { - onClick?: () => void; - className?: string; - children?: React.ReactNode; - type?: ButtonType; - href?: string; -}) { - return ( - - ); -} diff --git a/src/components/ui/buttons/GreenButton.tsx b/src/components/ui/buttons/GreenButton.tsx deleted file mode 100644 index 818909f..0000000 --- a/src/components/ui/buttons/GreenButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import PlainButton, { ButtonType } from "./PlainButton"; -import { COLORS } from "@/lib/theme/colors"; - -interface GreenButtonProps { - onClick?: () => void; - children: React.ReactNode; - className?: string; - type?: ButtonType; - disabled?: boolean; -} - -export default function GreenButton({ - onClick, - children, - className = "", - type = "button", - disabled = false, -}: GreenButtonProps) { - return ( - - {children} - - ); -} diff --git a/src/components/ui/buttons/IconButton.tsx b/src/components/ui/buttons/IconButton.tsx deleted file mode 100644 index 26d809c..0000000 --- a/src/components/ui/buttons/IconButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import PlainButton, { ButtonType } from "./PlainButton"; - -interface IconButtonProps { - onClick?: () => void; - icon: React.ReactNode; - className?: string; - type?: ButtonType; - disabled?: boolean; -} - -export default function IconButton({ - onClick, - icon, - className = "", - type = "button", - disabled = false, -}: IconButtonProps) { - return ( - - {icon} - - ); -} diff --git a/src/components/ui/buttons/IconClick.tsx b/src/components/ui/buttons/IconClick.tsx deleted file mode 100644 index 1e21a14..0000000 --- a/src/components/ui/buttons/IconClick.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import Image from "next/image"; - -interface IconClickProps { - src: string; - alt: string; - onClick?: () => void; - className?: string; - size?: number; - disableOnHoverBgChange?: boolean; -} -export default function IconClick({ - src, - alt, - onClick = () => {}, - className = "", - size = 32, - disableOnHoverBgChange = false, -}: IconClickProps) { - return ( - <> -
- {alt} -
- - ); -} diff --git a/src/components/ui/buttons/LightButton.tsx b/src/components/ui/buttons/LightButton.tsx deleted file mode 100644 index 0e4de45..0000000 --- a/src/components/ui/buttons/LightButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import PlainButton, { ButtonType } from "../buttons/PlainButton"; - -export default function LightButton({ - onClick, - className, - selected, - children, - type = "button", - disabled -}: { - onClick?: (() => void) | undefined; - className?: string; - selected?: boolean; - children?: React.ReactNode; - type?: ButtonType; - disabled?: boolean; -}) { - return ( - - {children} - - ); -} diff --git a/src/components/ui/buttons/PlainButton.tsx b/src/components/ui/buttons/PlainButton.tsx deleted file mode 100644 index c4e2d59..0000000 --- a/src/components/ui/buttons/PlainButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -export type ButtonType = "button" | "submit" | "reset" | undefined; - -export default function PlainButton({ - onClick, - className, - children, - type = "button", - disabled, - style -}: { - onClick?: () => void; - className?: string; - children?: React.ReactNode; - type?: ButtonType; - disabled?: boolean; - style?: React.CSSProperties; -}) { - return ( - - ); -} diff --git a/src/components/ui/buttons/index.tsx b/src/components/ui/buttons/index.tsx new file mode 100644 index 0000000..fee0a4a --- /dev/null +++ b/src/components/ui/buttons/index.tsx @@ -0,0 +1,56 @@ +// 向后兼容的按钮组件包装器 +// 这些组件将新 Button 组件包装,以保持向后兼容 + +import Button from "../Button"; + +// LightButton: 次要按钮,支持 selected 状态 +export const LightButton = (props: any) => + ); +}; + +// IconClick: 图片图标按钮 +export const IconClick = (props: any) => { + // IconClick 使用 src/alt 属性,需要映射到 Button 的 iconSrc/iconAlt + const { src, alt, size, disableOnHoverBgChange, className, ...rest } = props; + let buttonSize: "sm" | "md" | "lg" = "md"; + if (typeof size === "number") { + if (size <= 20) buttonSize = "sm"; + else if (size >= 32) buttonSize = "lg"; + } else if (typeof size === "string") { + buttonSize = (size === "sm" || size === "md" || size === "lg") ? size : "md"; + } + + // 如果禁用悬停背景变化,通过 className 覆盖 + const hoverClass = disableOnHoverBgChange ? "hover:bg-black/30 hover:cursor-pointer border-0 bg-transparent shadow-none" : ""; + + return ( +