abstract range
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
|
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
|
||||||
import { SubtitleDisplay } from "./SubtitleDisplay";
|
import { SubtitleDisplay } from "./SubtitleDisplay";
|
||||||
import { LightButton } from "@/design-system/base/button";
|
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 { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { SeekBarProps } from "../../types/player";
|
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) {
|
export function SeekBar({ value, max, onChange, disabled, className }: SeekBarProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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%)`
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -19,9 +19,7 @@ export {
|
|||||||
type ButtonSize,
|
type ButtonSize,
|
||||||
type ButtonProps
|
type ButtonProps
|
||||||
} from '@/design-system/base/button';
|
} from '@/design-system/base/button';
|
||||||
|
export { RangeInput, Range, type RangeProps } from '@/design-system/base/range';
|
||||||
// 业务特定组件
|
|
||||||
export { RangeInput } from './RangeInput';
|
|
||||||
export { Container } from './Container';
|
export { Container } from './Container';
|
||||||
export { PageLayout } from './PageLayout';
|
export { PageLayout } from './PageLayout';
|
||||||
export { PageHeader } from './PageHeader';
|
export { PageHeader } from './PageHeader';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
```
|
```
|
||||||
src/design-system/
|
src/design-system/
|
||||||
├── lib/ # 工具函数
|
├── lib/ # 工具函数
|
||||||
|
│ └── utils.ts
|
||||||
├── base/ # 基础组件
|
├── base/ # 基础组件
|
||||||
│ ├── button.tsx
|
│ ├── button.tsx
|
||||||
│ ├── input.tsx
|
│ ├── input.tsx
|
||||||
@@ -15,7 +16,8 @@ src/design-system/
|
|||||||
│ ├── checkbox.tsx
|
│ ├── checkbox.tsx
|
||||||
│ ├── radio.tsx
|
│ ├── radio.tsx
|
||||||
│ ├── switch.tsx
|
│ ├── switch.tsx
|
||||||
│ └── select.tsx
|
│ ├── select.tsx
|
||||||
|
│ └── range.tsx
|
||||||
├── feedback/ # 反馈组件
|
├── feedback/ # 反馈组件
|
||||||
│ ├── alert.tsx
|
│ ├── alert.tsx
|
||||||
│ ├── progress.tsx
|
│ ├── progress.tsx
|
||||||
@@ -82,6 +84,7 @@ export function MyComponent() {
|
|||||||
| [Radio](#radio) | 单选按钮 | ✅ |
|
| [Radio](#radio) | 单选按钮 | ✅ |
|
||||||
| [Switch](#switch) | 开关 | ✅ |
|
| [Switch](#switch) | 开关 | ✅ |
|
||||||
| [Select](#select) | 下拉选择框 | ✅ |
|
| [Select](#select) | 下拉选择框 | ✅ |
|
||||||
|
| [Range](#range) | 范围滑块 | ✅ |
|
||||||
|
|
||||||
### 反馈组件
|
### 反馈组件
|
||||||
|
|
||||||
@@ -232,6 +235,19 @@ import { Switch } from '@/design-system/base/switch';
|
|||||||
<Switch checked={enabled} onChange={setEnabled} />
|
<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
|
### Alert
|
||||||
|
|
||||||
警告提示组件。
|
警告提示组件。
|
||||||
|
|||||||
81
src/design-system/base/range.tsx
Normal file
81
src/design-system/base/range.tsx
Normal 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;
|
||||||
20
src/design-system/lib/utils.ts
Normal file
20
src/design-system/lib/utils.ts
Normal 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));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user