feat: add keep alive
This commit is contained in:
@@ -2,12 +2,8 @@ import { useMemo, useRef, useState, useEffect } from 'react';
|
||||
|
||||
import './VerticalSlider.css';
|
||||
|
||||
// Design size 260*70 (height*width) — kept as the reference ratio.
|
||||
// All CSS dimensions are expressed in container-query width units (cqw)
|
||||
// relative to this DESIGN_WIDTH, so the whole component scales together
|
||||
// and preserves this aspect ratio at any rendered size.
|
||||
|
||||
const DESIGN_WIDTH = 70;
|
||||
const DESIGN_HEIGHT = 240;
|
||||
|
||||
type VerticalSliderProps = {
|
||||
min: number;
|
||||
@@ -59,6 +55,24 @@ export default function VerticalSlider({
|
||||
const thumbRef = useRef<HTMLDivElement>(null);
|
||||
const keyboardEditing = useRef(false);
|
||||
|
||||
const viewportRef = useRef<HTMLDivElement>(null);
|
||||
const [scale, setScale] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const el = viewportRef.current;
|
||||
if (!el) return;
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
const entry = entries[0];
|
||||
const { width, height } = entry.contentRect;
|
||||
if (width === 0 || height === 0) return;
|
||||
setScale(Math.min(width / DESIGN_WIDTH, height / DESIGN_HEIGHT));
|
||||
});
|
||||
|
||||
observer.observe(el);
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (keyboardEditing.current) return;
|
||||
|
||||
@@ -76,26 +90,6 @@ export default function VerticalSlider({
|
||||
[step, switchStep]
|
||||
);
|
||||
|
||||
const maxChars = useMemo(() => {
|
||||
const minStr = min.toFixed(decimals);
|
||||
const maxStr = max.toFixed(decimals);
|
||||
return Math.max(minStr.length, maxStr.length) + unit.length;
|
||||
}, [min, max, decimals, unit]);
|
||||
|
||||
// Computed in "design pixels" (i.e. assuming the component is rendered at
|
||||
// DESIGN_WIDTH px wide), then converted below to cqw so it scales with
|
||||
// the actual rendered size instead of staying a fixed pixel value.
|
||||
const valueFontSize = useMemo(() => {
|
||||
const availableWidth = DESIGN_WIDTH - 8;
|
||||
const AVG_CHAR_WIDTH_RATIO = 0.6;
|
||||
const sizeByWidth = availableWidth / (maxChars * AVG_CHAR_WIDTH_RATIO);
|
||||
return Math.min(16, Math.max(8, sizeByWidth));
|
||||
}, [maxChars]);
|
||||
|
||||
// Convert the "design px" font size into cqw units relative to the
|
||||
// component's own width, so it scales proportionally with everything else.
|
||||
const valueFontSizeCqw = useMemo(() => (valueFontSize / DESIGN_WIDTH) * 100, [valueFontSize]);
|
||||
|
||||
function normalizeValue(input: number) {
|
||||
const clamped = Math.min(max, Math.max(min, input));
|
||||
const snapped = Math.round(clamped / currentStep) * currentStep;
|
||||
@@ -202,8 +196,15 @@ export default function VerticalSlider({
|
||||
const percent = (value - min) / (max - min);
|
||||
|
||||
return (
|
||||
<div className={['gauge-viewport', className].filter(Boolean).join(' ')} style={style}>
|
||||
<div className="gauge-container">
|
||||
<div
|
||||
className={['gauge-viewport', className].filter(Boolean).join(' ')}
|
||||
style={style}
|
||||
ref={viewportRef}
|
||||
>
|
||||
<div
|
||||
className="gauge-container"
|
||||
style={{ transform: `translate(-50%, -50%) scale(${scale})` }}
|
||||
>
|
||||
<div className="gauge-track">
|
||||
<div className="ticks left">
|
||||
{Array.from({ length: 15 }).map((_, i) => (
|
||||
@@ -233,7 +234,7 @@ export default function VerticalSlider({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="value" style={{ fontSize: `${valueFontSizeCqw}cqw` }}>
|
||||
<div className="value">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
|
||||
Reference in New Issue
Block a user