@zylem/ui 0.8.9 → 0.8.12
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/README.md +3 -0
- package/dist/styles.css +1 -1
- package/package.json +5 -1
- package/src/components/Accordion/Accordion.tsx +10 -1
- package/src/components/AddTile/AddTile.css.ts +17 -0
- package/src/components/AddTile/AddTile.tsx +15 -7
- package/src/components/Callout/Callout.css.ts +45 -0
- package/src/components/Callout/Callout.tsx +32 -0
- package/src/components/Card/Card.css.ts +18 -0
- package/src/components/Card/Card.tsx +25 -2
- package/src/components/Dialog/Dialog.css.ts +69 -2
- package/src/components/Dialog/Dialog.tsx +35 -7
- package/src/components/DropdownMenu/DropdownMenu.css.ts +22 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +67 -20
- package/src/components/EmptyState/EmptyState.css.ts +9 -0
- package/src/components/EmptyState/EmptyState.tsx +3 -1
- package/src/components/FrameStrip/FrameStrip.css.ts +6 -0
- package/src/components/FrameStrip/FrameStrip.tsx +30 -2
- package/src/components/Menubar/Menubar.css.ts +52 -0
- package/src/components/Menubar/Menubar.tsx +77 -0
- package/src/components/Mixer/Knob.tsx +188 -0
- package/src/components/Mixer/Mixer.css.ts +246 -0
- package/src/components/Mixer/MixerStrip.tsx +150 -0
- package/src/components/NumberField/NumberField.tsx +5 -1
- package/src/components/Panel/Panel.tsx +6 -5
- package/src/components/PianoRoll/PianoRoll.css.ts +224 -0
- package/src/components/PianoRoll/PianoRoll.tsx +353 -0
- package/src/components/PianoRoll/piano-roll-math.ts +89 -0
- package/src/components/Property/Property.css.ts +1 -1
- package/src/components/Property/Property.tsx +3 -0
- package/src/components/Sidebar/Sidebar.css.ts +8 -0
- package/src/components/Sidebar/Sidebar.tsx +10 -2
- package/src/components/Slider/Slider.tsx +6 -0
- package/src/components/SpriteSizePicker/SpriteSizePicker.tsx +15 -4
- package/src/components/Tabs/Tabs.css.ts +48 -0
- package/src/components/Tabs/Tabs.tsx +41 -3
- package/src/components/TextField/TextField.tsx +49 -5
- package/src/components/TimelineLanes/TimelineLanes.css.ts +365 -0
- package/src/components/TimelineLanes/TimelineLanes.tsx +509 -0
- package/src/components/TimelineLanes/lanes-math.ts +182 -0
- package/src/components/Toast/Toast.css.ts +48 -0
- package/src/components/Toast/Toast.tsx +37 -0
- package/src/components/TrackHeader/TrackHeader.css.ts +162 -0
- package/src/components/TrackHeader/TrackHeader.tsx +145 -0
- package/src/components/TransportBar/TransportBar.css.ts +65 -0
- package/src/components/TransportBar/TransportBar.tsx +154 -0
- package/src/components/Waveform/Waveform.css.ts +77 -0
- package/src/components/Waveform/Waveform.tsx +195 -0
- package/src/components/Waveform/peaks.ts +49 -0
- package/src/components/index.ts +72 -1
- package/src/global/accordion.css.ts +23 -0
- package/src/icons/create-icon.tsx +71 -0
- package/src/icons/icon-nodes.ts +75 -0
- package/src/icons/index.ts +54 -0
- package/src/icons/svg/camera-isometric.svg +16 -0
- package/src/icons/svg/free-perspective.svg +8 -0
- package/src/icons/svg/game-camera.svg +6 -0
- package/src/icons/svg/perspective.svg +5 -0
- package/src/icons/svg/sideview-perspective.svg +5 -0
- package/src/icons/svg/top-down-perspective.svg +5 -0
- package/src/styles.ts +11 -0
|
@@ -126,6 +126,12 @@ globalStyle('.zylem-frame-actions-spacer', {
|
|
|
126
126
|
flex: '1 1 auto',
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
+
globalStyle('.zylem-frame-actions-group', {
|
|
130
|
+
display: 'inline-flex',
|
|
131
|
+
alignItems: 'center',
|
|
132
|
+
gap: vars.spacing.xs,
|
|
133
|
+
});
|
|
134
|
+
|
|
129
135
|
globalStyle('.zylem-frame-actions-step', {
|
|
130
136
|
width: '34px',
|
|
131
137
|
padding: 0,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ArrowLeftToLine from 'lucide-solid/icons/arrow-left-to-line';
|
|
2
|
+
import ArrowRightToLine from 'lucide-solid/icons/arrow-right-to-line';
|
|
1
3
|
import ChevronLeft from 'lucide-solid/icons/chevron-left';
|
|
2
4
|
import ChevronRight from 'lucide-solid/icons/chevron-right';
|
|
3
5
|
import Copy from 'lucide-solid/icons/copy';
|
|
@@ -67,18 +69,24 @@ export function FrameThumb(props: FrameThumbProps) {
|
|
|
67
69
|
export interface FrameActionsProps {
|
|
68
70
|
onDuplicate?: () => void;
|
|
69
71
|
onDelete?: () => void;
|
|
72
|
+
/** Reorder: swap the selected frame with its left neighbour. Shown when set. */
|
|
73
|
+
onMoveLeft?: () => void;
|
|
74
|
+
/** Reorder: swap the selected frame with its right neighbour. Shown when set. */
|
|
75
|
+
onMoveRight?: () => void;
|
|
70
76
|
onPrev?: () => void;
|
|
71
77
|
onNext?: () => void;
|
|
72
78
|
canDuplicate?: boolean;
|
|
73
79
|
canDelete?: boolean;
|
|
80
|
+
canMoveLeft?: boolean;
|
|
81
|
+
canMoveRight?: boolean;
|
|
74
82
|
canPrev?: boolean;
|
|
75
83
|
canNext?: boolean;
|
|
76
84
|
class?: string;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
/**
|
|
80
|
-
* Frame commands under the strip: Duplicate and Delete,
|
|
81
|
-
* whose chevrons are centered in square toolbar buttons.
|
|
88
|
+
* Frame commands under the strip: Duplicate and Delete, the optional reorder
|
|
89
|
+
* pair, then step arrows whose chevrons are centered in square toolbar buttons.
|
|
82
90
|
*/
|
|
83
91
|
export function FrameActions(props: FrameActionsProps) {
|
|
84
92
|
return (
|
|
@@ -96,6 +104,26 @@ export function FrameActions(props: FrameActionsProps) {
|
|
|
96
104
|
<Trash2 class="zylem-icon" aria-hidden="true" />
|
|
97
105
|
Delete
|
|
98
106
|
</Button>
|
|
107
|
+
<Show when={props.onMoveLeft || props.onMoveRight}>
|
|
108
|
+
<span class="zylem-frame-actions-group" role="group" aria-label="Reorder frame">
|
|
109
|
+
<ToolbarButton
|
|
110
|
+
label="Move frame left"
|
|
111
|
+
disabled={props.canMoveLeft === false}
|
|
112
|
+
onClick={() => props.onMoveLeft?.()}
|
|
113
|
+
class="zylem-frame-actions-step"
|
|
114
|
+
>
|
|
115
|
+
<ArrowLeftToLine class="zylem-icon" />
|
|
116
|
+
</ToolbarButton>
|
|
117
|
+
<ToolbarButton
|
|
118
|
+
label="Move frame right"
|
|
119
|
+
disabled={props.canMoveRight === false}
|
|
120
|
+
onClick={() => props.onMoveRight?.()}
|
|
121
|
+
class="zylem-frame-actions-step"
|
|
122
|
+
>
|
|
123
|
+
<ArrowRightToLine class="zylem-icon" />
|
|
124
|
+
</ToolbarButton>
|
|
125
|
+
</span>
|
|
126
|
+
</Show>
|
|
99
127
|
<span class="zylem-frame-actions-spacer" />
|
|
100
128
|
<ToolbarButton
|
|
101
129
|
label="Previous frame"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '../../theme.css';
|
|
3
|
+
|
|
4
|
+
globalStyle('.zylem-menubar', {
|
|
5
|
+
display: 'flex',
|
|
6
|
+
alignItems: 'center',
|
|
7
|
+
gap: '2px',
|
|
8
|
+
minWidth: 0,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
// Flat chrome (see DESIGN.md): transparent at rest, a tint on hover and a
|
|
12
|
+
// stronger tint while the menu is open. No gloss, so the bar's glossy title
|
|
13
|
+
// surfaces and primary buttons stay the focal points.
|
|
14
|
+
globalStyle('.zylem-menubar-trigger', {
|
|
15
|
+
display: 'inline-flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
gap: vars.spacing.xs,
|
|
18
|
+
padding: `${vars.spacing.xs} ${vars.spacing.sm}`,
|
|
19
|
+
border: '1px solid transparent',
|
|
20
|
+
borderRadius: vars.radii.button,
|
|
21
|
+
background: 'transparent',
|
|
22
|
+
color: vars.colors.textSecondary,
|
|
23
|
+
fontFamily: vars.typography.fontFamily,
|
|
24
|
+
fontSize: `calc(${vars.typography.fontSize} - 1px)`,
|
|
25
|
+
fontWeight: 600,
|
|
26
|
+
letterSpacing: '0.02em',
|
|
27
|
+
lineHeight: 1.2,
|
|
28
|
+
cursor: 'pointer',
|
|
29
|
+
userSelect: 'none',
|
|
30
|
+
outline: 'none',
|
|
31
|
+
transition: `background ${vars.motion.fast} ${vars.motion.easeOut}, color ${vars.motion.fast} ${vars.motion.easeOut}`,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
globalStyle('.zylem-menubar-trigger:hover', {
|
|
35
|
+
background: 'rgba(97, 166, 232, 0.12)',
|
|
36
|
+
color: vars.colors.text,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
globalStyle('.zylem-menubar-trigger[data-expanded]', {
|
|
40
|
+
background: 'rgba(97, 166, 232, 0.22)',
|
|
41
|
+
borderColor: 'rgba(97, 166, 232, 0.35)',
|
|
42
|
+
color: '#F4FAFF',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
globalStyle('.zylem-menubar-trigger:focus-visible', {
|
|
46
|
+
boxShadow: vars.effects.focusRing,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
globalStyle('.zylem-menubar-trigger[data-disabled]', {
|
|
50
|
+
opacity: 0.45,
|
|
51
|
+
cursor: 'not-allowed',
|
|
52
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Menubar as KMenubar } from '@kobalte/core';
|
|
2
|
+
import { For, Show } from 'solid-js';
|
|
3
|
+
import type { JSX } from 'solid-js';
|
|
4
|
+
import { createDismissGuard, useLayer } from '../../layers';
|
|
5
|
+
import { MenuItems, type DropdownMenuItemConfig } from '../DropdownMenu/DropdownMenu';
|
|
6
|
+
|
|
7
|
+
export interface MenubarMenuConfig {
|
|
8
|
+
id: string;
|
|
9
|
+
label: JSX.Element;
|
|
10
|
+
/**
|
|
11
|
+
* Rows of the menu. A function is read each time the menu renders, so a
|
|
12
|
+
* menu whose rows depend on app state (the open stages, the dirty flag) can
|
|
13
|
+
* hand over its accessor instead of rebuilding the whole bar on every change.
|
|
14
|
+
*/
|
|
15
|
+
items: DropdownMenuItemConfig[] | (() => DropdownMenuItemConfig[]);
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface MenubarProps {
|
|
20
|
+
menus: MenubarMenuConfig[];
|
|
21
|
+
class?: string;
|
|
22
|
+
'aria-label'?: string;
|
|
23
|
+
/** Class applied to every trigger button. */
|
|
24
|
+
triggerClass?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Desktop-style menu bar: a row of flat text triggers, one open at a time.
|
|
29
|
+
* Moving the pointer onto a sibling trigger while a menu is open swaps menus,
|
|
30
|
+
* and the arrow keys walk the bar — behaviour that comes from Kobalte's
|
|
31
|
+
* Menubar. Each menu is a `DropdownMenu` sheet, portaled at the `menu` tier.
|
|
32
|
+
*/
|
|
33
|
+
export function Menubar(props: MenubarProps) {
|
|
34
|
+
const layer = useLayer('menu');
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<KMenubar.Root
|
|
38
|
+
ref={layer.anchorRef}
|
|
39
|
+
class={props.class ? `zylem-menubar ${props.class}` : 'zylem-menubar'}
|
|
40
|
+
aria-label={props['aria-label']}
|
|
41
|
+
>
|
|
42
|
+
<For each={props.menus}>
|
|
43
|
+
{(menu) => {
|
|
44
|
+
const dismiss = createDismissGuard();
|
|
45
|
+
const items = () => (typeof menu.items === 'function' ? menu.items() : menu.items);
|
|
46
|
+
return (
|
|
47
|
+
<KMenubar.Menu>
|
|
48
|
+
<KMenubar.Trigger
|
|
49
|
+
class={
|
|
50
|
+
props.triggerClass
|
|
51
|
+
? `zylem-menubar-trigger ${props.triggerClass}`
|
|
52
|
+
: 'zylem-menubar-trigger'
|
|
53
|
+
}
|
|
54
|
+
disabled={menu.disabled}
|
|
55
|
+
>
|
|
56
|
+
{menu.label}
|
|
57
|
+
</KMenubar.Trigger>
|
|
58
|
+
<Show when={layer.mount()} keyed>
|
|
59
|
+
{(mount) => (
|
|
60
|
+
<KMenubar.Portal mount={mount}>
|
|
61
|
+
<KMenubar.Content
|
|
62
|
+
ref={dismiss.ref}
|
|
63
|
+
class="zylem-dropdown-content zylem-menubar-content"
|
|
64
|
+
onInteractOutside={dismiss.onInteractOutside}
|
|
65
|
+
>
|
|
66
|
+
<MenuItems items={items()} />
|
|
67
|
+
</KMenubar.Content>
|
|
68
|
+
</KMenubar.Portal>
|
|
69
|
+
)}
|
|
70
|
+
</Show>
|
|
71
|
+
</KMenubar.Menu>
|
|
72
|
+
);
|
|
73
|
+
}}
|
|
74
|
+
</For>
|
|
75
|
+
</KMenubar.Root>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createSignal, Show } from 'solid-js';
|
|
2
|
+
import { clamp } from '../TimelineLanes/lanes-math';
|
|
3
|
+
|
|
4
|
+
export interface KnobProps {
|
|
5
|
+
value: number;
|
|
6
|
+
onChange?: (value: number) => void;
|
|
7
|
+
/** Fires once when a drag ends or a key edit lands. */
|
|
8
|
+
onCommit?: (value: number) => void;
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
step?: number;
|
|
12
|
+
/** Double-click resets to this. */
|
|
13
|
+
defaultValue?: number;
|
|
14
|
+
label?: string;
|
|
15
|
+
/** Readout under the knob; defaults to the value with `step` precision. */
|
|
16
|
+
format?: (value: number) => string;
|
|
17
|
+
/** Draw the arc from the centre (for pan and other ±ranges). */
|
|
18
|
+
bipolar?: boolean;
|
|
19
|
+
size?: number;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SWEEP = 270;
|
|
25
|
+
const START_ANGLE = -135;
|
|
26
|
+
|
|
27
|
+
function polar(cx: number, cy: number, radius: number, degrees: number) {
|
|
28
|
+
const radians = ((degrees - 90) * Math.PI) / 180;
|
|
29
|
+
return { x: cx + radius * Math.cos(radians), y: cy + radius * Math.sin(radians) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** SVG arc path from `from` to `to` degrees (clockwise, 12 o'clock = 0). */
|
|
33
|
+
export function arcPath(cx: number, cy: number, radius: number, from: number, to: number): string {
|
|
34
|
+
const [a, b] = from <= to ? [from, to] : [to, from];
|
|
35
|
+
const start = polar(cx, cy, radius, a);
|
|
36
|
+
const end = polar(cx, cy, radius, b);
|
|
37
|
+
const large = b - a > 180 ? 1 : 0;
|
|
38
|
+
return `M ${start.x.toFixed(3)} ${start.y.toFixed(3)} A ${radius} ${radius} 0 ${large} 1 ${end.x.toFixed(3)} ${end.y.toFixed(3)}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Rotary control: drag vertically (Shift for fine steps), arrow keys to
|
|
43
|
+
* step, double-click to reset. The arc fills from the minimum, or from the
|
|
44
|
+
* centre when `bipolar`.
|
|
45
|
+
*/
|
|
46
|
+
export function Knob(props: KnobProps) {
|
|
47
|
+
const min = () => props.min ?? 0;
|
|
48
|
+
const max = () => props.max ?? 1;
|
|
49
|
+
const step = () => props.step ?? (max() - min()) / 100;
|
|
50
|
+
const size = () => props.size ?? 36;
|
|
51
|
+
const ratio = () => (max() > min() ? clamp((props.value - min()) / (max() - min()), 0, 1) : 0);
|
|
52
|
+
const angle = () => START_ANGLE + ratio() * SWEEP;
|
|
53
|
+
const zeroAngle = () =>
|
|
54
|
+
START_ANGLE + clamp((0 - min()) / (max() - min() || 1), 0, 1) * SWEEP;
|
|
55
|
+
const [dragging, setDragging] = createSignal(false);
|
|
56
|
+
|
|
57
|
+
const quantize = (value: number) => {
|
|
58
|
+
const s = step();
|
|
59
|
+
const snapped = s > 0 ? Math.round((value - min()) / s) * s + min() : value;
|
|
60
|
+
return clamp(Number(snapped.toFixed(6)), min(), max());
|
|
61
|
+
};
|
|
62
|
+
const readout = () => {
|
|
63
|
+
if (props.format) return props.format(props.value);
|
|
64
|
+
const decimals = Math.max(0, Math.min(3, -Math.floor(Math.log10(step() || 1))));
|
|
65
|
+
return props.value.toFixed(decimals);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const beginDrag = (event: PointerEvent) => {
|
|
69
|
+
if (props.disabled || event.button !== 0) return;
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
const target = event.currentTarget as HTMLElement;
|
|
72
|
+
target.focus({ preventScroll: true });
|
|
73
|
+
target.setPointerCapture?.(event.pointerId);
|
|
74
|
+
const originY = event.clientY;
|
|
75
|
+
const originValue = props.value;
|
|
76
|
+
let last = originValue;
|
|
77
|
+
setDragging(true);
|
|
78
|
+
const move = (moveEvent: PointerEvent) => {
|
|
79
|
+
const pixels = moveEvent.shiftKey ? 600 : 150;
|
|
80
|
+
const delta = ((originY - moveEvent.clientY) / pixels) * (max() - min());
|
|
81
|
+
last = quantize(originValue + delta);
|
|
82
|
+
props.onChange?.(last);
|
|
83
|
+
};
|
|
84
|
+
const stop = () => {
|
|
85
|
+
target.removeEventListener('pointermove', move);
|
|
86
|
+
target.removeEventListener('pointerup', stop);
|
|
87
|
+
target.removeEventListener('pointercancel', stop);
|
|
88
|
+
target.releasePointerCapture?.(event.pointerId);
|
|
89
|
+
setDragging(false);
|
|
90
|
+
props.onCommit?.(last);
|
|
91
|
+
};
|
|
92
|
+
target.addEventListener('pointermove', move);
|
|
93
|
+
target.addEventListener('pointerup', stop);
|
|
94
|
+
target.addEventListener('pointercancel', stop);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const nudge = (direction: number, big: boolean) => {
|
|
98
|
+
const next = quantize(props.value + direction * step() * (big ? 10 : 1));
|
|
99
|
+
props.onChange?.(next);
|
|
100
|
+
props.onCommit?.(next);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
104
|
+
if (props.disabled) return;
|
|
105
|
+
switch (event.key) {
|
|
106
|
+
case 'ArrowUp':
|
|
107
|
+
case 'ArrowRight':
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
nudge(1, event.shiftKey);
|
|
110
|
+
break;
|
|
111
|
+
case 'ArrowDown':
|
|
112
|
+
case 'ArrowLeft':
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
nudge(-1, event.shiftKey);
|
|
115
|
+
break;
|
|
116
|
+
case 'Home':
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
props.onChange?.(min());
|
|
119
|
+
props.onCommit?.(min());
|
|
120
|
+
break;
|
|
121
|
+
case 'End':
|
|
122
|
+
event.preventDefault();
|
|
123
|
+
props.onChange?.(max());
|
|
124
|
+
props.onCommit?.(max());
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const reset = () => {
|
|
131
|
+
if (props.disabled || props.defaultValue === undefined) return;
|
|
132
|
+
const next = quantize(props.defaultValue);
|
|
133
|
+
props.onChange?.(next);
|
|
134
|
+
props.onCommit?.(next);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const radius = () => size() / 2 - 3;
|
|
138
|
+
const center = () => size() / 2;
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div
|
|
142
|
+
class={props.class ? `zylem-knob ${props.class}` : 'zylem-knob'}
|
|
143
|
+
data-disabled={props.disabled ? '' : undefined}
|
|
144
|
+
data-dragging={dragging() ? '' : undefined}
|
|
145
|
+
>
|
|
146
|
+
<div
|
|
147
|
+
class="zylem-knob-dial"
|
|
148
|
+
role="slider"
|
|
149
|
+
tabIndex={props.disabled ? -1 : 0}
|
|
150
|
+
aria-label={props.label}
|
|
151
|
+
aria-valuemin={min()}
|
|
152
|
+
aria-valuemax={max()}
|
|
153
|
+
aria-valuenow={props.value}
|
|
154
|
+
aria-valuetext={readout()}
|
|
155
|
+
aria-disabled={props.disabled ? 'true' : undefined}
|
|
156
|
+
style={{ width: `${size()}px`, height: `${size()}px` }}
|
|
157
|
+
onPointerDown={beginDrag}
|
|
158
|
+
onKeyDown={handleKeyDown}
|
|
159
|
+
onDblClick={reset}
|
|
160
|
+
>
|
|
161
|
+
<svg viewBox={`0 0 ${size()} ${size()}`} width={size()} height={size()} aria-hidden="true">
|
|
162
|
+
<path
|
|
163
|
+
class="zylem-knob-track"
|
|
164
|
+
d={arcPath(center(), center(), radius(), START_ANGLE, START_ANGLE + SWEEP)}
|
|
165
|
+
/>
|
|
166
|
+
<path
|
|
167
|
+
class="zylem-knob-fill"
|
|
168
|
+
d={arcPath(center(), center(), radius(), props.bipolar ? zeroAngle() : START_ANGLE, angle())}
|
|
169
|
+
/>
|
|
170
|
+
<g transform={`rotate(${angle()} ${center()} ${center()})`}>
|
|
171
|
+
<circle class="zylem-knob-cap" cx={center()} cy={center()} r={radius() - 4} />
|
|
172
|
+
<line
|
|
173
|
+
class="zylem-knob-pointer"
|
|
174
|
+
x1={center()}
|
|
175
|
+
y1={center() - radius() + 6}
|
|
176
|
+
x2={center()}
|
|
177
|
+
y2={center() - radius() / 2}
|
|
178
|
+
/>
|
|
179
|
+
</g>
|
|
180
|
+
</svg>
|
|
181
|
+
</div>
|
|
182
|
+
<Show when={props.label}>
|
|
183
|
+
<span class="zylem-knob-label">{props.label}</span>
|
|
184
|
+
</Show>
|
|
185
|
+
<span class="zylem-knob-value">{readout()}</span>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '../../theme.css';
|
|
3
|
+
|
|
4
|
+
// Knob -----------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
globalStyle('.zylem-knob', {
|
|
7
|
+
display: 'inline-flex',
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
alignItems: 'center',
|
|
10
|
+
gap: '2px',
|
|
11
|
+
fontFamily: vars.typography.fontFamily,
|
|
12
|
+
color: vars.colors.text,
|
|
13
|
+
userSelect: 'none',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
globalStyle('.zylem-knob[data-disabled]', {
|
|
17
|
+
opacity: 0.5,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
globalStyle('.zylem-knob-dial', {
|
|
21
|
+
display: 'block',
|
|
22
|
+
borderRadius: '999px',
|
|
23
|
+
cursor: 'ns-resize',
|
|
24
|
+
outline: 'none',
|
|
25
|
+
touchAction: 'none',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
globalStyle('.zylem-knob[data-dragging] .zylem-knob-dial', {
|
|
29
|
+
cursor: 'grabbing',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
globalStyle('.zylem-knob-dial:focus-visible', {
|
|
33
|
+
boxShadow: vars.effects.focusRing,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
globalStyle('.zylem-knob-dial svg', {
|
|
37
|
+
display: 'block',
|
|
38
|
+
overflow: 'visible',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
globalStyle('.zylem-knob-track', {
|
|
42
|
+
fill: 'none',
|
|
43
|
+
stroke: 'rgba(8, 19, 31, 0.9)',
|
|
44
|
+
strokeWidth: 3,
|
|
45
|
+
strokeLinecap: 'round',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
globalStyle('.zylem-knob-fill', {
|
|
49
|
+
fill: 'none',
|
|
50
|
+
stroke: 'rgba(120, 190, 255, 0.95)',
|
|
51
|
+
strokeWidth: 3,
|
|
52
|
+
strokeLinecap: 'round',
|
|
53
|
+
filter: 'drop-shadow(0 0 3px rgba(97, 166, 232, 0.7))',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
globalStyle('.zylem-knob-cap', {
|
|
57
|
+
fill: 'rgba(40, 70, 105, 1)',
|
|
58
|
+
stroke: 'rgba(178, 219, 255, 0.5)',
|
|
59
|
+
strokeWidth: 1,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
globalStyle('.zylem-knob-pointer', {
|
|
63
|
+
stroke: 'rgba(240, 250, 255, 0.98)',
|
|
64
|
+
strokeWidth: 2,
|
|
65
|
+
strokeLinecap: 'round',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
globalStyle('.zylem-knob-label', {
|
|
69
|
+
fontSize: '9px',
|
|
70
|
+
letterSpacing: '0.08em',
|
|
71
|
+
textTransform: 'uppercase',
|
|
72
|
+
color: vars.colors.textSecondary,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
globalStyle('.zylem-knob-value', {
|
|
76
|
+
fontSize: '10px',
|
|
77
|
+
fontVariantNumeric: 'tabular-nums',
|
|
78
|
+
color: vars.colors.text,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Mixer strip ----------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
globalStyle('.zylem-mixer-strip', {
|
|
84
|
+
boxSizing: 'border-box',
|
|
85
|
+
display: 'flex',
|
|
86
|
+
flexDirection: 'column',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
gap: vars.spacing.xs,
|
|
89
|
+
width: '64px',
|
|
90
|
+
padding: `${vars.spacing.sm} ${vars.spacing.xs}`,
|
|
91
|
+
fontFamily: vars.typography.fontFamily,
|
|
92
|
+
color: vars.colors.text,
|
|
93
|
+
background: vars.material.glassPanelDark,
|
|
94
|
+
border: '1px solid rgba(97, 166, 232, 0.2)',
|
|
95
|
+
borderTop: '3px solid var(--zylem-track-color)',
|
|
96
|
+
borderRadius: vars.radii.control,
|
|
97
|
+
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.06)',
|
|
98
|
+
userSelect: 'none',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
globalStyle('.zylem-mixer-strip[data-master]', {
|
|
102
|
+
width: '76px',
|
|
103
|
+
background: vars.material.glassPanel,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
globalStyle('.zylem-mixer-strip[data-selected]', {
|
|
107
|
+
boxShadow: `inset 0 1px 0 rgba(255,255,255,0.06), 0 0 0 1px rgba(178, 219, 255, 0.6), ${vars.effects.glowPrimary}`,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
globalStyle('.zylem-mixer-strip[data-disabled]', {
|
|
111
|
+
opacity: 0.6,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
globalStyle('.zylem-mixer-fader-row', {
|
|
115
|
+
display: 'flex',
|
|
116
|
+
alignItems: 'stretch',
|
|
117
|
+
gap: '4px',
|
|
118
|
+
height: '132px',
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
globalStyle('.zylem-fader', {
|
|
122
|
+
position: 'relative',
|
|
123
|
+
display: 'flex',
|
|
124
|
+
flexDirection: 'column',
|
|
125
|
+
width: '24px',
|
|
126
|
+
height: '100%',
|
|
127
|
+
touchAction: 'none',
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
globalStyle('.zylem-fader[data-disabled]', {
|
|
131
|
+
opacity: 0.5,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
globalStyle('.zylem-fader-sr', {
|
|
135
|
+
position: 'absolute',
|
|
136
|
+
width: '1px',
|
|
137
|
+
height: '1px',
|
|
138
|
+
padding: 0,
|
|
139
|
+
margin: '-1px',
|
|
140
|
+
overflow: 'hidden',
|
|
141
|
+
clip: 'rect(0, 0, 0, 0)',
|
|
142
|
+
whiteSpace: 'nowrap',
|
|
143
|
+
border: 0,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
globalStyle('.zylem-fader-track', {
|
|
147
|
+
position: 'relative',
|
|
148
|
+
flex: '1 1 auto',
|
|
149
|
+
width: '8px',
|
|
150
|
+
margin: '0 auto',
|
|
151
|
+
borderRadius: '999px',
|
|
152
|
+
border: '1px solid rgba(97, 166, 232, 0.28)',
|
|
153
|
+
background: vars.material.fieldGlass,
|
|
154
|
+
boxShadow: vars.effects.shadowInset,
|
|
155
|
+
cursor: 'pointer',
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
globalStyle('.zylem-fader-fill', {
|
|
159
|
+
position: 'absolute',
|
|
160
|
+
bottom: 0,
|
|
161
|
+
width: '100%',
|
|
162
|
+
borderRadius: '999px',
|
|
163
|
+
background:
|
|
164
|
+
'linear-gradient(0deg, rgba(48, 137, 220, 0.8), rgba(178, 219, 255, 0.55))',
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
globalStyle('.zylem-fader-zero', {
|
|
168
|
+
position: 'absolute',
|
|
169
|
+
left: '-6px',
|
|
170
|
+
right: '-6px',
|
|
171
|
+
bottom: 'calc(100% * 60 / 66)',
|
|
172
|
+
height: '1px',
|
|
173
|
+
background: 'rgba(205, 232, 255, 0.4)',
|
|
174
|
+
pointerEvents: 'none',
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
globalStyle('.zylem-fader-thumb', {
|
|
178
|
+
display: 'block',
|
|
179
|
+
left: '-7px',
|
|
180
|
+
width: '22px',
|
|
181
|
+
height: '12px',
|
|
182
|
+
borderRadius: '4px',
|
|
183
|
+
border: '1px solid rgba(255,255,255,0.55)',
|
|
184
|
+
background:
|
|
185
|
+
'linear-gradient(180deg, rgba(240,250,255,0.96), rgba(140,190,235,0.92))',
|
|
186
|
+
boxShadow: `inset 0 1px 0 rgba(255,255,255,0.75), inset 0 -3px 0 rgba(0,0,0,0.18), 0 1px 4px rgba(0,0,0,0.5)`,
|
|
187
|
+
cursor: 'grab',
|
|
188
|
+
outline: 'none',
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
globalStyle('.zylem-fader-thumb:active', {
|
|
192
|
+
cursor: 'grabbing',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
globalStyle('.zylem-fader-thumb:focus-visible', {
|
|
196
|
+
boxShadow: `inset 0 1px 0 rgba(255,255,255,0.75), ${vars.effects.focusRing}`,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
globalStyle('.zylem-mixer-meter', {
|
|
200
|
+
position: 'relative',
|
|
201
|
+
width: '6px',
|
|
202
|
+
borderRadius: '3px',
|
|
203
|
+
background: 'rgba(8, 19, 31, 0.9)',
|
|
204
|
+
border: '1px solid rgba(97, 166, 232, 0.2)',
|
|
205
|
+
overflow: 'hidden',
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
globalStyle('.zylem-mixer-meter-fill', {
|
|
209
|
+
position: 'absolute',
|
|
210
|
+
bottom: 0,
|
|
211
|
+
left: 0,
|
|
212
|
+
right: 0,
|
|
213
|
+
background:
|
|
214
|
+
'linear-gradient(0deg, rgba(28, 174, 63, 0.95) 0%, rgba(170, 255, 190, 0.95) 65%, rgba(255, 220, 130, 0.95) 85%, rgba(230, 69, 52, 0.95) 100%)',
|
|
215
|
+
transition: `height 60ms linear`,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
globalStyle('.zylem-mixer-readout', {
|
|
219
|
+
fontSize: '10px',
|
|
220
|
+
fontVariantNumeric: 'tabular-nums',
|
|
221
|
+
color: vars.colors.textSecondary,
|
|
222
|
+
whiteSpace: 'nowrap',
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
globalStyle('.zylem-mixer-toggles', {
|
|
226
|
+
display: 'flex',
|
|
227
|
+
gap: '3px',
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
globalStyle('.zylem-mixer-name', {
|
|
231
|
+
maxWidth: '100%',
|
|
232
|
+
fontSize: '11px',
|
|
233
|
+
fontWeight: 600,
|
|
234
|
+
whiteSpace: 'nowrap',
|
|
235
|
+
overflow: 'hidden',
|
|
236
|
+
textOverflow: 'ellipsis',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
globalStyle('.zylem-mixer-strip[data-muted] .zylem-mixer-name', {
|
|
240
|
+
color: vars.colors.textSecondary,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
globalStyle('.zylem-mixer-strip[data-master] .zylem-mixer-name', {
|
|
244
|
+
letterSpacing: '0.08em',
|
|
245
|
+
textTransform: 'uppercase',
|
|
246
|
+
});
|