abstract range

This commit is contained in:
2026-02-24 08:02:39 +08:00
parent 72ced7866e
commit 94840c1b0a
7 changed files with 121 additions and 51 deletions

View File

@@ -1,7 +1,7 @@
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
import { SubtitleDisplay } from "./SubtitleDisplay";
import { LightButton } from "@/design-system/base/button";
import { RangeInput } from "@/components/ui/RangeInput";
import { RangeInput } from "@/design-system/base/range";
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
import { useTranslations } from "next-intl";

View File

@@ -2,7 +2,7 @@
import React from "react";
import { SeekBarProps } from "../../types/player";
import { RangeInput } from "@/components/ui/RangeInput";
import { RangeInput } from "@/design-system/base/range";
export function SeekBar({ value, max, onChange, disabled, className }: SeekBarProps) {
return (

View File

@@ -1,45 +0,0 @@
"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<HTMLInputElement>) => {
const newValue = parseInt(event.target.value);
onChange(newValue);
}, [onChange]);
const progressPercentage = ((value - min) / (max - min)) * 100;
return (
<input
type="range"
min={min}
max={max}
value={value}
onChange={handleChange}
disabled={disabled}
className={`w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500 ${
disabled ? "opacity-50 cursor-not-allowed" : ""
} ${className}`}
style={{
background: `linear-gradient(to right, #374151 0%, #374151 ${progressPercentage}%, #e5e7eb ${progressPercentage}%, #e5e7eb 100%)`
}}
/>
);
}

View File

@@ -19,9 +19,7 @@ export {
type ButtonSize,
type ButtonProps
} from '@/design-system/base/button';
// 业务特定组件
export { RangeInput } from './RangeInput';
export { RangeInput, Range, type RangeProps } from '@/design-system/base/range';
export { Container } from './Container';
export { PageLayout } from './PageLayout';
export { PageHeader } from './PageHeader';

View File

@@ -7,6 +7,7 @@
```
src/design-system/
├── lib/ # 工具函数
│ └── utils.ts
├── base/ # 基础组件
│ ├── button.tsx
│ ├── input.tsx
@@ -15,7 +16,8 @@ src/design-system/
│ ├── checkbox.tsx
│ ├── radio.tsx
│ ├── switch.tsx
── select.tsx
── select.tsx
│ └── range.tsx
├── feedback/ # 反馈组件
│ ├── alert.tsx
│ ├── progress.tsx
@@ -82,6 +84,7 @@ export function MyComponent() {
| [Radio](#radio) | 单选按钮 | ✅ |
| [Switch](#switch) | 开关 | ✅ |
| [Select](#select) | 下拉选择框 | ✅ |
| [Range](#range) | 范围滑块 | ✅ |
### 反馈组件
@@ -232,6 +235,19 @@ import { Switch } from '@/design-system/base/switch';
<Switch checked={enabled} onChange={setEnabled} />
```
### Range
范围滑块组件。
```tsx
import { Range } from '@/design-system/base/range';
<Range value={50} min={0} max={100} onChange={setValue} />
<Range value={75} min={0} max={100} disabled />
```
**别名**: `RangeInput`(向后兼容)
### Alert
警告提示组件。

View File

@@ -0,0 +1,81 @@
"use client";
/**
* Range - 范围滑块组件
*
* 支持自定义进度条颜色、禁用状态和样式覆盖的滑块输入组件。
*
* @example
* ```tsx
* import { Range } from '@/design-system/base/range';
*
* <Range value={50} min={0} max={100} onChange={setValue} />
* <Range value={75} min={0} max={100} disabled />
* ```
*/
import * as React from "react";
import { cn } from "@/design-system/lib/utils";
export interface RangeProps extends Omit<React.ComponentPropsWithoutRef<"input">, "onChange"> {
/** 当前值 */
value: number;
/** 值变化回调 */
onChange: (value: number) => void;
/** 最小值 (默认: 0) */
min?: number;
/** 最大值 */
max: number;
}
export const Range = React.forwardRef<HTMLInputElement, RangeProps>(
(
{
value,
min = 0,
max,
onChange,
disabled = false,
className,
...props
},
ref
) => {
const handleChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const newValue = parseInt(event.target.value);
onChange(newValue);
},
[onChange]
);
const progressPercentage = ((value - min) / (max - min)) * 100;
return (
<input
ref={ref}
type="range"
min={min}
max={max}
value={value}
onChange={handleChange}
disabled={disabled}
className={cn(
"w-full h-2 rounded-lg appearance-none cursor-pointer",
"focus:outline-none focus:ring-2 focus:ring-primary-500",
disabled && "opacity-50 cursor-not-allowed",
className
)}
style={{
background: `linear-gradient(to right, #374151 0%, #374151 ${progressPercentage}%, #e5e7eb ${progressPercentage}%, #e5e7eb 100%)`
}}
{...props}
/>
);
}
);
Range.displayName = "Range";
// 向后兼容别名
export const RangeInput = Range;

View File

@@ -0,0 +1,20 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
/**
* 合并 Tailwind CSS 类名的工具函数
*
* @example
* ```tsx
* import { cn } from '@/design-system/lib/utils';
*
* const className = cn(
* 'base-class',
* isActive && 'active-class',
* 'another-class'
* );
* ```
*/
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}