diff --git a/src/app/(features)/dictionary/SearchForm.tsx b/src/app/(features)/dictionary/SearchForm.tsx index 8b95665..71bf3b8 100644 --- a/src/app/(features)/dictionary/SearchForm.tsx +++ b/src/app/(features)/dictionary/SearchForm.tsx @@ -1,6 +1,7 @@ "use client"; import { LightButton } from "@/components/ui/buttons"; +import { Input } from "@/components/ui/Input"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { useRouter } from "next/navigation"; @@ -47,12 +48,12 @@ export function SearchForm({ defaultQueryLang = "english", defaultDefinitionLang {/* 搜索表单 */}
- ( {t("autoPause", { enabled: autoPause ? "Yes" : "No" })} - { + if (videoRef.current && parsedSrtRef.current) { + videoRef.current.currentTime = parsedSrtRef.current[value]?.start || 0; + setProgress(value); + } + }} value={progress} - > + /> {spanText} ); diff --git a/src/app/(features)/srt-player/components/atoms/SeekBar.tsx b/src/app/(features)/srt-player/components/atoms/SeekBar.tsx index ff8203f..5dc0df7 100644 --- a/src/app/(features)/srt-player/components/atoms/SeekBar.tsx +++ b/src/app/(features)/srt-player/components/atoms/SeekBar.tsx @@ -2,25 +2,16 @@ import React from "react"; import { SeekBarProps } from "../../types/player"; +import { RangeInput } from "@/components/ui/RangeInput"; export function SeekBar({ value, max, onChange, disabled, className }: SeekBarProps) { - const handleChange = React.useCallback((event: React.ChangeEvent) => { - const newValue = parseInt(event.target.value); - onChange(newValue); - }, [onChange]); - return ( - ); } \ No newline at end of file diff --git a/src/app/(features)/translator/page.tsx b/src/app/(features)/translator/page.tsx index c8d0ea4..20f9229 100644 --- a/src/app/(features)/translator/page.tsx +++ b/src/app/(features)/translator/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { LightButton, GreenButton } from "@/components/ui/buttons"; +import { LightButton, PrimaryButton } from "@/components/ui/buttons"; import { IconClick } from "@/components/ui/buttons"; import { IMAGES } from "@/config/images"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; @@ -210,13 +210,13 @@ export default function TranslatorPage() { {/* TranslateButton Component */}
- {t("translate")} - +
); diff --git a/src/app/folders/[folder_id]/InFolder.tsx b/src/app/folders/[folder_id]/InFolder.tsx index 818e1dc..471b6c2 100644 --- a/src/app/folders/[folder_id]/InFolder.tsx +++ b/src/app/folders/[folder_id]/InFolder.tsx @@ -7,7 +7,7 @@ import { AddTextPairModal } from "./AddTextPairModal"; import { TextPairCard } from "./TextPairCard"; import { useTranslations } from "next-intl"; import { PageLayout } from "@/components/ui/PageLayout"; -import { GreenButton, IconButton, LinkButton } from "@/components/ui/buttons"; +import { PrimaryButton, IconButton, LinkButton } from "@/components/ui/buttons"; import { CardList } from "@/components/ui/CardList"; import { actionCreatePair, actionDeletePairById, actionGetPairsByFolderId } from "@/modules/folder/folder-aciton"; import { TSharedPair } from "@/shared/folder-type"; @@ -73,13 +73,13 @@ export function InFolder({ folderId, isReadOnly }: { folderId: number; isReadOnl {/* 操作按钮区域 */}
- { redirect(`/memorize?folder_id=${folderId}`); }} > {t("memorize")} - + {!isReadOnly && ( { diff --git a/src/app/page.tsx b/src/app/page.tsx index fb63b54..89dfa52 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,7 +13,7 @@ function LinkArea({ href, name, description, color }: LinkAreaProps) {

{name}

diff --git a/src/components/ui/Input.tsx b/src/components/ui/Input.tsx index 5f1b4f7..a58b248 100644 --- a/src/components/ui/Input.tsx +++ b/src/components/ui/Input.tsx @@ -1,3 +1,5 @@ +export type InputVariant = "default" | "search" | "bordered" | "filled"; + interface Props { ref?: React.Ref; placeholder?: string; @@ -5,6 +7,11 @@ interface Props { className?: string; name?: string; defaultValue?: string; + value?: string; + variant?: InputVariant; + required?: boolean; + disabled?: boolean; + onChange?: (e: React.ChangeEvent) => void; } export function Input({ @@ -14,15 +21,34 @@ export function Input({ className = "", name = "", defaultValue = "", + value, + variant = "default", + required = false, + disabled = false, + onChange, }: Props) { + // Variant-specific classes + const variantStyles: Record = { + default: "block focus:outline-none border-b-2 border-gray-600", + search: "flex-1 min-w-0 px-4 py-3 text-lg text-gray-800 focus:outline-none border-b-2 border-gray-600 bg-white/90 rounded", + bordered: "w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f]", + filled: "w-full px-3 py-2 bg-gray-100 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f]" + }; + + const variantClass = variantStyles[variant]; + return ( ); } diff --git a/src/components/ui/LocaleSelector.tsx b/src/components/ui/LocaleSelector.tsx index b1bccd7..726b876 100644 --- a/src/components/ui/LocaleSelector.tsx +++ b/src/components/ui/LocaleSelector.tsx @@ -1,5 +1,7 @@ import { useTranslations } from "next-intl"; import { useState } from "react"; +import { Input } from "@/components/ui/Input"; +import { Select, Option } from "@/components/ui/Select"; const COMMON_LANGUAGES = [ { label: "chinese", value: "chinese" }, @@ -47,24 +49,24 @@ export function LocaleSelector({ value, onChange }: LocaleSelectorProps) { return (
- + {showCustomInput && ( - handleCustomInputChange(e.target.value)} placeholder={t("folder_id.enterLanguageName")} - className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f] mt-2" + variant="bordered" + className="mt-2" /> )}
diff --git a/src/components/ui/RangeInput.tsx b/src/components/ui/RangeInput.tsx new file mode 100644 index 0000000..e851b39 --- /dev/null +++ b/src/components/ui/RangeInput.tsx @@ -0,0 +1,43 @@ +"use client"; + +import React from "react"; + +interface RangeInputProps { + value: number; + max: number; + onChange: (value: number) => void; + disabled?: boolean; + className?: string; + min?: number; +} + +export function RangeInput({ + value, + max, + onChange, + disabled = false, + className = "", + min = 0, +}: RangeInputProps) { + const handleChange = React.useCallback((event: React.ChangeEvent) => { + const newValue = parseInt(event.target.value); + onChange(newValue); + }, [onChange]); + + const progressPercentage = ((value - min) / (max - min)) * 100; + + return ( + + ); +} diff --git a/src/components/ui/Select.tsx b/src/components/ui/Select.tsx new file mode 100644 index 0000000..b7f9a7b --- /dev/null +++ b/src/components/ui/Select.tsx @@ -0,0 +1,56 @@ +export type SelectSize = "sm" | "md" | "lg"; + +interface Props { + value?: string; + onChange?: (value: string) => void; + children: React.ReactNode; + className?: string; + size?: SelectSize; + disabled?: boolean; + required?: boolean; +} + +export function Select({ + value, + onChange, + children, + className = "", + size = "md", + disabled = false, + required = false, +}: Props) { + // Size-specific classes + const sizeStyles: Record = { + sm: "px-2 py-1 text-sm", + md: "px-3 py-2 text-base", + lg: "px-4 py-3 text-lg" + }; + + const sizeClass = sizeStyles[size]; + + return ( + + ); +} + +interface OptionProps { + value: string; + children: React.ReactNode; + disabled?: boolean; +} + +export function Option({ value, children, disabled = false }: OptionProps) { + return ( + + ); +} diff --git a/src/components/ui/Textarea.tsx b/src/components/ui/Textarea.tsx new file mode 100644 index 0000000..1de9929 --- /dev/null +++ b/src/components/ui/Textarea.tsx @@ -0,0 +1,50 @@ +export type TextareaVariant = "default" | "bordered" | "filled"; + +interface Props { + value?: string; + defaultValue?: string; + onChange?: (value: string) => void; + placeholder?: string; + className?: string; + variant?: TextareaVariant; + disabled?: boolean; + required?: boolean; + rows?: number; + name?: string; +} + +export function Textarea({ + value, + defaultValue, + onChange, + placeholder = "", + className = "", + variant = "default", + disabled = false, + required = false, + rows = 3, + name = "", +}: Props) { + // Variant-specific classes + const variantStyles: Record = { + default: "block focus:outline-none border-b-2 border-gray-600 resize-none", + bordered: "w-full border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f] resize-none", + filled: "w-full bg-gray-100 rounded focus:outline-none focus:ring-2 focus:ring-[#35786f] resize-none" + }; + + const variantClass = variantStyles[variant]; + + return ( +