Initial release: dreamknob library + showcase
React + TypeScript knob/fader library with a headless interaction core (rotary, relative-drag and track modes, wheel, keyboard, a11y), SVG primitives, nine prebuilt skins, seven-segment display engine, and a Vite showcase with gallery, live playground and API docs.
This commit is contained in:
commit
60a02cb1db
46 changed files with 6186 additions and 0 deletions
122
packages/dreamknob/src/skins/MetalKnob.tsx
Normal file
122
packages/dreamknob/src/skins/MetalKnob.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import * as React from 'react';
|
||||
import { Knob } from '../components/Knob';
|
||||
import { Arc } from '../primitives/Arc';
|
||||
import { Pointer } from '../primitives/Pointer';
|
||||
import { Ticks } from '../primitives/Ticks';
|
||||
import { KnobLabel } from '../primitives/Text';
|
||||
import { UI_FONT, type SkinProps } from './shared';
|
||||
|
||||
export interface MetalKnobProps extends SkinProps {
|
||||
/** Accent color for the active ticks / value arc. */
|
||||
color?: string;
|
||||
/** Overall metal tone. 'silver' | 'dark' or any base hex for the cap. */
|
||||
tone?: 'silver' | 'dark';
|
||||
tickColor?: string;
|
||||
labelColor?: string;
|
||||
indicatorColor?: string;
|
||||
showArc?: boolean;
|
||||
tickCount?: number;
|
||||
}
|
||||
|
||||
/** 3D brushed-aluminum knob with a knurled rim — classic hi-fi hardware. */
|
||||
export const MetalKnob: React.FC<MetalKnobProps> = ({
|
||||
size = 90,
|
||||
color = '#4cc2ff',
|
||||
tone = 'silver',
|
||||
tickColor = 'rgba(255,255,255,0.18)',
|
||||
labelColor = 'rgba(255,255,255,0.45)',
|
||||
indicatorColor,
|
||||
showArc = true,
|
||||
tickCount = 21,
|
||||
label,
|
||||
className,
|
||||
style,
|
||||
...core
|
||||
}) => {
|
||||
const id = React.useId();
|
||||
const rimId = `${id}-rim`;
|
||||
const faceId = `${id}-face`;
|
||||
const shadowId = `${id}-shadow`;
|
||||
const c = size / 2;
|
||||
const rimR = size / 2 - size * 0.14;
|
||||
const faceR = rimR * 0.78;
|
||||
|
||||
const silver = tone === 'silver';
|
||||
const rimStops = silver
|
||||
? ['#f4f5f7', '#b9bbc2', '#63656d', '#33343a']
|
||||
: ['#6a6c74', '#43444b', '#232429', '#101114'];
|
||||
const faceStops = silver
|
||||
? ['#fbfcfd', '#d3d5da', '#9c9ea6']
|
||||
: ['#585a63', '#33343b', '#1b1c21'];
|
||||
const indicator = indicatorColor ?? (silver ? '#1b1c21' : color);
|
||||
|
||||
return (
|
||||
<Knob size={size} className={className} style={style} {...core}>
|
||||
<defs>
|
||||
<linearGradient id={rimId} x1="0" y1="0" x2="0.8" y2="1">
|
||||
<stop offset="0" stopColor={rimStops[0]} />
|
||||
<stop offset="0.45" stopColor={rimStops[1]} />
|
||||
<stop offset="0.8" stopColor={rimStops[2]} />
|
||||
<stop offset="1" stopColor={rimStops[3]} />
|
||||
</linearGradient>
|
||||
<radialGradient id={faceId} cx="0.35" cy="0.28" r="0.9">
|
||||
<stop offset="0" stopColor={faceStops[0]} />
|
||||
<stop offset="0.6" stopColor={faceStops[1]} />
|
||||
<stop offset="1" stopColor={faceStops[2]} />
|
||||
</radialGradient>
|
||||
<filter id={shadowId} x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feDropShadow
|
||||
dx="0"
|
||||
dy={size * 0.02}
|
||||
stdDeviation={size * 0.025}
|
||||
floodColor="#000"
|
||||
floodOpacity="0.55"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
{showArc && (
|
||||
<Arc
|
||||
radius={size / 2 - 2}
|
||||
thickness={Math.max(2, size * 0.03)}
|
||||
color={color}
|
||||
trackColor="rgba(255,255,255,0.07)"
|
||||
/>
|
||||
)}
|
||||
<Ticks
|
||||
count={tickCount}
|
||||
radius={size / 2 - size * 0.055}
|
||||
length={size * 0.05}
|
||||
width={Math.max(1, size * 0.015)}
|
||||
color={tickColor}
|
||||
activeColor={color}
|
||||
/>
|
||||
|
||||
{/* cap */}
|
||||
<g filter={`url(#${shadowId})`}>
|
||||
<circle cx={c} cy={c} r={rimR} fill={`url(#${rimId})`} />
|
||||
</g>
|
||||
{/* knurled rim */}
|
||||
<circle
|
||||
cx={c}
|
||||
cy={c}
|
||||
r={rimR - size * 0.014}
|
||||
fill="none"
|
||||
stroke="rgba(0,0,0,0.35)"
|
||||
strokeWidth={size * 0.028}
|
||||
strokeDasharray={`${size * 0.014} ${size * 0.02}`}
|
||||
/>
|
||||
<circle cx={c} cy={c} r={faceR} fill={`url(#${faceId})`} />
|
||||
{/* concentric machining lines */}
|
||||
<circle cx={c} cy={c} r={faceR * 0.72} fill="none" stroke="rgba(255,255,255,0.25)" strokeWidth="0.6" />
|
||||
<circle cx={c} cy={c} r={faceR * 0.5} fill="none" stroke="rgba(0,0,0,0.12)" strokeWidth="0.6" />
|
||||
|
||||
<Pointer radius={faceR - size * 0.02} length={faceR * 0.55} width={Math.max(2.5, size * 0.038)} color={indicator} />
|
||||
{label && (
|
||||
<KnobLabel color={labelColor} fontFamily={UI_FONT} dy={size * 0.44} fontSize={size * 0.1}>
|
||||
{label}
|
||||
</KnobLabel>
|
||||
)}
|
||||
</Knob>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue