项目文件夹

文件
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

75 行
3.2 KiB
TypeScript

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import React, { useEffect, useRef } from 'react';
import { dimens } from '../res/dimens';
import { pad2 } from '../utils';
const PREPEND_WHEEL = 3;
export const WheelColumn: React.FC<{
value: number;
options: number[];
onChange: (value: number) => void;
/** 计时页使用大号滚轮 */
large?: boolean;
/** 闹钟编辑页使用中号滚轮(更大字体) */
medium?: boolean;
/** 循环滚轮:首尾相接(时 00 接 23,分 00 接 59 */
wrap?: boolean;
}> = ({ value, options, onChange, large, medium, wrap }) => {
const containerRef = useRef<HTMLDivElement | null>(null);
const itemHeight = large ? dimens.wheel_item_height_large : medium ? dimens.wheel_item_height_medium : dimens.wheel_item_height_small;
const displayOptions = wrap
? [...options.slice(-PREPEND_WHEEL), ...options, ...options.slice(0, PREPEND_WHEEL)]
: options;
const scrollIndexForValue = wrap ? PREPEND_WHEEL + options.indexOf(value) : options.indexOf(value);
const mainStart = wrap ? PREPEND_WHEEL : 0;
const mainEnd = wrap ? PREPEND_WHEEL + options.length - 1 : options.length - 1;
useEffect(() => {
if (containerRef.current && scrollIndexForValue >= 0) {
containerRef.current.scrollTop = scrollIndexForValue * itemHeight;
}
}, [value, options, scrollIndexForValue, itemHeight]);
useEffect(() => {
const el = containerRef.current;
if (!el) return;
let raf = 0;
const onScroll = () => {
cancelAnimationFrame(raf);
raf = requestAnimationFrame(() => {
const idx = Math.round(el.scrollTop / itemHeight);
const clamped = Math.min(displayOptions.length - 1, Math.max(0, idx));
const nextValue = displayOptions[clamped];
if (nextValue !== value) onChange(nextValue);
if (wrap && (clamped < mainStart || clamped > mainEnd)) {
el.scrollTop = (PREPEND_WHEEL + nextValue) * itemHeight;
}
});
};
el.addEventListener('scroll', onScroll);
return () => {
el.removeEventListener('scroll', onScroll);
cancelAnimationFrame(raf);
};
}, [displayOptions, value, onChange, wrap, mainStart, mainEnd, itemHeight]);
const padHeight = large ? dimens.wheel_pad_height_large : medium ? dimens.wheel_pad_height_medium : dimens.wheel_pad_height_small;
const cellClass = large ? 'h-(--app-wheel-item-height-large) text-[42px]' : medium ? 'h-(--app-wheel-item-height-medium) text-[36px]' : 'h-(--app-wheel-item-height-small) text-[28px]';
const sizeClass = large ? 'w-(--app-wheel-width-large) h-(--app-wheel-height-large)' : medium ? 'w-(--app-wheel-width-medium) h-(--app-wheel-height-medium)' : 'w-(--app-wheel-width-small) h-(--app-wheel-height-small)';
return (
<div className={`overflow-y-scroll no-scrollbar ${sizeClass}`} ref={containerRef}>
<div style={{ height: padHeight }} />
{displayOptions.map((option, index) => (
<div
key={wrap ? `disp-${index}-${option}` : option}
className={`flex items-center justify-center ${cellClass} ${option === value ? 'text-black font-medium' : 'text-gray-300'}`}
>
{pad2(option)}
</div>
))}
<div style={{ height: padHeight }} />
</div>
);
};