Skip to content

Commit 7fc49f5

Browse files
committed
refactor: Rework Piano Roll controls for mobile responsiveness, adjusting element sizing, visibility, and adding a showAdvanced state.
1 parent 69469f4 commit 7fc49f5

6 files changed

Lines changed: 379 additions & 156 deletions

File tree

apps/web/src/components/LooperPanel.tsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import { useState, useRef } from "react";
22
import { useStore, LoopTrack } from "@/lib/store";
33
import { Mic, Play, Square, Trash2, Volume2, VolumeX, Repeat, X, Download } from "lucide-react";
44
import { motion, AnimatePresence } from "framer-motion";
@@ -30,9 +30,9 @@ export function LooperPanel() {
3030
animate={{ width: "auto", opacity: 1 }}
3131
exit={{ width: 0, opacity: 0 }}
3232
transition={{ type: "spring", stiffness: 300, damping: 30 }}
33-
className="h-full border-l border-border bg-background/95 backdrop-blur-xl shadow-2xl z-40 bg-background flex flex-col overflow-hidden max-w-[90vw] md:max-w-md"
33+
className="h-full border-l border-border bg-background/95 backdrop-blur-xl shadow-2xl z-40 bg-background flex flex-col overflow-hidden w-full sm:w-auto sm:max-w-md"
3434
>
35-
<div className="w-[320px] md:w-80 h-full flex flex-col p-4">
35+
<div className="w-screen sm:w-80 h-full flex flex-col p-4">
3636
<div className="flex items-center justify-between mb-6 mt-4">
3737
<h2 className="text-xl font-bold flex items-center gap-2">
3838
<span className="w-3 h-3 rounded-full bg-red-500 animate-pulse" />
@@ -74,6 +74,29 @@ function LoopTrackCard({
7474
onVolume: (v: number) => void, onMute: () => void
7575
}) {
7676
const [showVolume, setShowVolume] = useState(false);
77+
const longPressTimer = useRef<NodeJS.Timeout | null>(null);
78+
const isLongPress = useRef(false);
79+
80+
const handleTouchStart = () => {
81+
isLongPress.current = false;
82+
longPressTimer.current = setTimeout(() => {
83+
isLongPress.current = true;
84+
setShowVolume(!showVolume);
85+
}, 400); // 400ms long press
86+
};
87+
88+
const handleTouchEnd = () => {
89+
if (longPressTimer.current) {
90+
clearTimeout(longPressTimer.current);
91+
}
92+
};
93+
94+
const handleClick = () => {
95+
// Only trigger mute if it wasn't a long press
96+
if (!isLongPress.current) {
97+
onMute();
98+
}
99+
};
77100
const isRecording = track.state === "recording";
78101
const isPlaying = track.state === "playing";
79102
const hasLoop = track.state !== "empty" && track.state !== "recording";
@@ -137,10 +160,13 @@ function LoopTrackCard({
137160

138161
{/* Volume Button */}
139162
<button
140-
onClick={onMute}
163+
onClick={handleClick}
141164
onContextMenu={(e) => { e.preventDefault(); setShowVolume(!showVolume); }}
142-
className={`w-8 h-8 rounded-full border border-border flex items-center justify-center transition-all ${track.muted ? "bg-destructive/20 text-destructive border-destructive/50" : "bg-background hover:bg-muted text-muted-foreground"}`}
143-
title="Left-Click: Mute | Right-Click: Volume"
165+
onTouchStart={handleTouchStart}
166+
onTouchEnd={handleTouchEnd}
167+
onTouchCancel={handleTouchEnd}
168+
className={`w-8 h-8 rounded-full border border-border flex items-center justify-center transition-all touch-manipulation ${track.muted ? "bg-destructive/20 text-destructive border-destructive/50" : "bg-background hover:bg-muted text-muted-foreground"}`}
169+
title="Tap: Mute | Hold/Right-Click: Volume"
144170
>
145171
{track.muted ? <VolumeX size={14} /> : <Volume2 size={14} />}
146172
</button>

apps/web/src/components/MiniKeyboard.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,20 @@ const OSCILLATORS: { type: SynthParams['oscillatorType']; label: string }[] = [
2020

2121
// Reusable Knob Component (Vertical Range Slider styled as Knob-ish or Slider)
2222
const Slider = ({
23-
label, value, min, max, step, onChange, unit = ""
23+
label, value, min, max, step, onChange, unit = "", horizontal
2424
}: {
25-
label: string, value: number, min: number, max: number, step: number, onChange: (v: number) => void, unit?: string
25+
label: string, value: number, min: number, max: number, step: number, onChange: (v: number) => void, unit?: string, horizontal?: boolean
2626
}) => (
27-
<div className="flex flex-col items-center gap-2 group">
28-
<div className="h-28 bg-muted/30 rounded-full p-1 relative w-8 flex justify-center">
27+
<div className={horizontal ? "flex flex-row items-center gap-2 group" : "flex flex-col items-center gap-2 group"}>
28+
<div className={horizontal ? "h-8 bg-muted/30 rounded-full p-1 relative w-28 flex justify-center" : "h-28 bg-muted/30 rounded-full p-1 relative w-8 flex justify-center"}>
2929
<input
3030
type="range"
3131
min={min}
3232
max={max}
3333
step={step}
3434
value={value}
3535
onChange={(e) => onChange(parseFloat(e.target.value))}
36-
className="[-webkit-appearance:slider-vertical] w-full h-full opacity-50 group-hover:opacity-100 transition-opacity cursor-pointer accent-primary"
36+
className={horizontal ? "[-webkit-appearance:slider-horizontal] w-full h-full opacity-50 group-hover:opacity-100 transition-opacity cursor-pointer accent-primary" : "[-webkit-appearance:slider-vertical] w-full h-full opacity-50 group-hover:opacity-100 transition-opacity cursor-pointer accent-primary"}
3737
/>
3838
</div>
3939
<div className="text-center">
@@ -100,7 +100,7 @@ export function MiniKeyboard() {
100100
return (
101101
<div className="flex flex-col gap-4 h-full select-none">
102102
{/* Synth Engine Panel */}
103-
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 p-4 bg-muted/10 rounded-xl border border-border relative overflow-hidden">
103+
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 p-4 bg-muted/10 rounded-xl border border-border relative overflow-hidden">
104104
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent pointer-events-none"></div>
105105

106106
{/* 1. Oscillator */}
@@ -151,15 +151,15 @@ export function MiniKeyboard() {
151151
<div className="flex items-center gap-2 text-xs font-bold text-muted-foreground">
152152
<Zap size={14} /> FILTER & FX
153153
</div>
154-
<div className="flex justify-between">
154+
<div className="flex flex-col md:flex-row justify-between">
155155
<Slider label="FREQ" value={synthParams.filterCutoff} min={20} max={20000} step={100} onChange={(v) => {
156156
setSynthParam("filterCutoff", v);
157157
useStore.getState().setMasterEffect("filterFreq", v);
158-
}} unit="Hz" />
158+
}} unit="Hz" horizontal />
159159
<Slider label="RES" value={synthParams.filterResonance} min={0} max={20} step={0.1} onChange={(v) => {
160160
setSynthParam("filterResonance", v);
161161
useStore.getState().setMasterEffect("filterRes", v);
162-
}} />
162+
}} horizontal />
163163
</div>
164164
</div>
165165
</div>

0 commit comments

Comments
 (0)