@ambientcss/components 1.2.1 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +97 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +97 -28
- package/dist/index.js.map +1 -1
- package/dist/styles.css +336 -66
- package/package.json +2 -2
- package/src/components/AmbientButton.tsx +27 -4
- package/src/components/AmbientFader.tsx +3 -7
- package/src/components/AmbientKnob.tsx +95 -9
- package/src/components/AmbientSlider.tsx +3 -9
- package/src/components/AmbientSwitch.tsx +5 -2
- package/src/index.ts +2 -2
- package/src/styles.css +336 -66
|
@@ -2,6 +2,73 @@ import { useId, useRef } from "react";
|
|
|
2
2
|
import type { HTMLAttributes, KeyboardEvent, PointerEvent } from "react";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
|
|
5
|
+
/* Straight-knurled silhouettes for the rotating face, in
|
|
6
|
+
objectBoundingBox units so the clip scales with the component. Each
|
|
7
|
+
variant family mirrors its referent's rib section
|
|
8
|
+
(ambient3d/ground_components.py): trapezoid teeth between the outer
|
|
9
|
+
radius and the tooth-root radius. The circular body underneath keeps
|
|
10
|
+
the smooth drop shadow. */
|
|
11
|
+
type KnurlSpec = {
|
|
12
|
+
teeth: number; // rib count
|
|
13
|
+
root: number; // tooth-root radius (bounding-box units; outer is 0.5)
|
|
14
|
+
rise: number; // fraction of the pitch spent climbing to the crest
|
|
15
|
+
fall: [number, number]; // crest-end and root-return pitch fractions
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const KNURLS: Record<"standard" | "flute" | "fine", KnurlSpec> = {
|
|
19
|
+
/* 36 ribs — the grounded referent knob (dot and line variants) */
|
|
20
|
+
standard: { teeth: 36, root: 0.468, rise: 0.12, fall: [0.5, 0.62] },
|
|
21
|
+
/* 14 broad flutes with deeper roots (OP-Z-style flute variant) */
|
|
22
|
+
flute: { teeth: 14, root: 0.44, rise: 0.08, fall: [0.72, 0.8] },
|
|
23
|
+
/* 48-rib fine knurl, shallower (cap and wheel variants) */
|
|
24
|
+
fine: { teeth: 48, root: 0.476, rise: 0.12, fall: [0.5, 0.62] }
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function knurlPath({ teeth, root, rise, fall }: KnurlSpec): string {
|
|
28
|
+
const outer = 0.5;
|
|
29
|
+
const pitch = (Math.PI * 2) / teeth;
|
|
30
|
+
const pts: string[] = [];
|
|
31
|
+
for (let i = 0; i < teeth; i++) {
|
|
32
|
+
const a = i * pitch;
|
|
33
|
+
const tooth: Array<[number, number]> = [
|
|
34
|
+
[0, root],
|
|
35
|
+
[rise, outer],
|
|
36
|
+
[fall[0], outer],
|
|
37
|
+
[fall[1], root]
|
|
38
|
+
];
|
|
39
|
+
for (const [frac, radius] of tooth) {
|
|
40
|
+
const t = a + frac * pitch;
|
|
41
|
+
pts.push(
|
|
42
|
+
`${(0.5 + radius * Math.cos(t)).toFixed(4)} ${(0.5 + radius * Math.sin(t)).toFixed(4)}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return `M${pts.join(" L")} Z`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const KNURL_PATHS = {
|
|
50
|
+
standard: knurlPath(KNURLS.standard),
|
|
51
|
+
flute: knurlPath(KNURLS.flute),
|
|
52
|
+
fine: knurlPath(KNURLS.fine)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/* Knob types from the referent lineup (ambient3d/generate.py):
|
|
56
|
+
- "dot": the grounded referent — 36-rib knurl, offset indicator dot
|
|
57
|
+
- "line": same body with a radial indicator line (classic pot)
|
|
58
|
+
- "flute": 14 broad flutes, centered dot (OP-Z-style encoder)
|
|
59
|
+
- "cap": fine knurl under a smooth accent top disc (OP-1-style)
|
|
60
|
+
- "wheel": bare fine knurl, no indicator (machined wheel — pair with
|
|
61
|
+
material="shiny") */
|
|
62
|
+
export type AmbientKnobVariant = "dot" | "line" | "flute" | "cap" | "wheel";
|
|
63
|
+
|
|
64
|
+
const VARIANT_FAMILY: Record<AmbientKnobVariant, keyof typeof KNURL_PATHS> = {
|
|
65
|
+
dot: "standard",
|
|
66
|
+
line: "standard",
|
|
67
|
+
flute: "flute",
|
|
68
|
+
cap: "fine",
|
|
69
|
+
wheel: "fine"
|
|
70
|
+
};
|
|
71
|
+
|
|
5
72
|
export type AmbientKnobProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange"> & {
|
|
6
73
|
value: number;
|
|
7
74
|
min?: number;
|
|
@@ -9,6 +76,7 @@ export type AmbientKnobProps = Omit<HTMLAttributes<HTMLDivElement>, "onChange">
|
|
|
9
76
|
step?: number;
|
|
10
77
|
label?: string;
|
|
11
78
|
material?: "matte" | "shiny" | "glass";
|
|
79
|
+
variant?: AmbientKnobVariant;
|
|
12
80
|
onChange?: (nextValue: number) => void;
|
|
13
81
|
};
|
|
14
82
|
|
|
@@ -23,14 +91,17 @@ export function AmbientKnob({
|
|
|
23
91
|
step = 1,
|
|
24
92
|
label,
|
|
25
93
|
material,
|
|
94
|
+
variant = "dot",
|
|
26
95
|
onChange,
|
|
27
96
|
className,
|
|
28
97
|
...props
|
|
29
98
|
}: AmbientKnobProps) {
|
|
30
99
|
const id = useId();
|
|
100
|
+
const clipId = `amb-knurl-${id.replace(/:/g, "")}`;
|
|
31
101
|
const draggingRef = useRef(false);
|
|
32
102
|
const knobRef = useRef<HTMLDivElement>(null);
|
|
33
103
|
const safeStep = step > 0 ? step : 1;
|
|
104
|
+
const family = VARIANT_FAMILY[variant];
|
|
34
105
|
|
|
35
106
|
const percent = (value - min) / (max - min || 1);
|
|
36
107
|
const rotation = percent * 270 - 135;
|
|
@@ -102,7 +173,7 @@ export function AmbientKnob({
|
|
|
102
173
|
<div className={cn("ambx-stack", className)} {...props}>
|
|
103
174
|
<div
|
|
104
175
|
ref={knobRef}
|
|
105
|
-
className=
|
|
176
|
+
className="amb-knob ambx-knob"
|
|
106
177
|
role="slider"
|
|
107
178
|
aria-label={label}
|
|
108
179
|
aria-valuemin={min}
|
|
@@ -127,14 +198,29 @@ export function AmbientKnob({
|
|
|
127
198
|
}}
|
|
128
199
|
onKeyDown={onKeyDown}
|
|
129
200
|
>
|
|
130
|
-
<
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
201
|
+
<svg width={0} height={0} style={{ position: "absolute" }} aria-hidden focusable={false}>
|
|
202
|
+
<defs>
|
|
203
|
+
<clipPath id={clipId} clipPathUnits="objectBoundingBox">
|
|
204
|
+
<path d={KNURL_PATHS[family]} />
|
|
205
|
+
</clipPath>
|
|
206
|
+
</defs>
|
|
207
|
+
</svg>
|
|
208
|
+
<span className="amb-knob-body ambient amb-thickness-2 amb-surface" />
|
|
209
|
+
<div
|
|
210
|
+
className={cn(
|
|
211
|
+
"ambx-knob-rotation amb-knob-face",
|
|
212
|
+
family === "flute" && "amb-knob-face-flute",
|
|
213
|
+
family === "fine" && "amb-knob-face-fine",
|
|
214
|
+
variant === "cap" && "amb-knob-face-cap",
|
|
215
|
+
material && `amb-mat-${material}`
|
|
216
|
+
)}
|
|
217
|
+
style={{ transform: `rotate(${rotation}deg)`, clipPath: `url(#${clipId})` }}
|
|
218
|
+
>
|
|
219
|
+
{variant === "dot" ? <span className="amb-knob-indicator-dot" /> : null}
|
|
220
|
+
{variant === "line" ? <span className="amb-knob-indicator-line" /> : null}
|
|
221
|
+
{variant === "flute" ? (
|
|
222
|
+
<span className="amb-knob-indicator-dot amb-knob-indicator-dot-center" />
|
|
223
|
+
) : null}
|
|
138
224
|
</div>
|
|
139
225
|
</div>
|
|
140
226
|
{label ? (
|
|
@@ -91,7 +91,7 @@ export function AmbientSlider({
|
|
|
91
91
|
return (
|
|
92
92
|
<div className={cn("ambx-stack", className)} {...props}>
|
|
93
93
|
<div
|
|
94
|
-
className="amb-slider ambx-slider"
|
|
94
|
+
className="amb-slider amb-groove ambx-slider"
|
|
95
95
|
ref={trackRef}
|
|
96
96
|
role="slider"
|
|
97
97
|
aria-label={label}
|
|
@@ -109,15 +109,9 @@ export function AmbientSlider({
|
|
|
109
109
|
onKeyDown={onKeyDown}
|
|
110
110
|
>
|
|
111
111
|
<div
|
|
112
|
-
className={cn("amb-slider-thumb ambient amb-fillet
|
|
112
|
+
className={cn("amb-slider-thumb ambient amb-fillet ambx-slider-thumb", material !== "glass" && "amb-surface-convex", material && `amb-mat-${material}`)}
|
|
113
113
|
style={{ left: `${percent}%` }}
|
|
114
|
-
|
|
115
|
-
<div className="amb-slider-grip">
|
|
116
|
-
<span className="amb-fader-dot" />
|
|
117
|
-
<span className="amb-fader-dot" />
|
|
118
|
-
<span className="amb-fader-dot" />
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
114
|
+
/>
|
|
121
115
|
</div>
|
|
122
116
|
{label ? (
|
|
123
117
|
<span id={id} className="ambx-label">
|
|
@@ -38,8 +38,8 @@ export function AmbientSwitch({
|
|
|
38
38
|
aria-checked={active}
|
|
39
39
|
aria-labelledby={label ? labelId : props["aria-labelledby"]}
|
|
40
40
|
className={cn(
|
|
41
|
-
"
|
|
42
|
-
active
|
|
41
|
+
"amb-switch",
|
|
42
|
+
active && "amb-switch-on",
|
|
43
43
|
"ambx-switch",
|
|
44
44
|
`ambx-switch-${size}`,
|
|
45
45
|
className
|
|
@@ -58,6 +58,9 @@ export function AmbientSwitch({
|
|
|
58
58
|
style={typeof led === "string" ? { "--amb-led-color": led } as React.CSSProperties : undefined}
|
|
59
59
|
/>
|
|
60
60
|
) : null}
|
|
61
|
+
<span className="amb-switch-track amb-groove">
|
|
62
|
+
<span className="amb-switch-pill ambient amb-fillet amb-surface-convex" />
|
|
63
|
+
</span>
|
|
61
64
|
{children}
|
|
62
65
|
</button>
|
|
63
66
|
);
|
package/src/index.ts
CHANGED
|
@@ -5,13 +5,13 @@ export { AmbientPanel } from "./components/AmbientPanel";
|
|
|
5
5
|
export type { AmbientPanelProps } from "./components/AmbientPanel";
|
|
6
6
|
|
|
7
7
|
export { AmbientButton } from "./components/AmbientButton";
|
|
8
|
-
export type { AmbientButtonProps } from "./components/AmbientButton";
|
|
8
|
+
export type { AmbientButtonProps, AmbientButtonShape } from "./components/AmbientButton";
|
|
9
9
|
|
|
10
10
|
export { AmbientSwitch } from "./components/AmbientSwitch";
|
|
11
11
|
export type { AmbientSwitchProps, AmbientSwitchSize } from "./components/AmbientSwitch";
|
|
12
12
|
|
|
13
13
|
export { AmbientKnob } from "./components/AmbientKnob";
|
|
14
|
-
export type { AmbientKnobProps } from "./components/AmbientKnob";
|
|
14
|
+
export type { AmbientKnobProps, AmbientKnobVariant } from "./components/AmbientKnob";
|
|
15
15
|
|
|
16
16
|
export { AmbientFader } from "./components/AmbientFader";
|
|
17
17
|
export type { AmbientFaderProps } from "./components/AmbientFader";
|