11import { useState } from 'react' ;
2- import { Pipette } from 'lucide-react' ;
2+ import { Pipette , Sliders } from 'lucide-react' ;
3+ import { motion , AnimatePresence } from 'framer-motion' ;
34import Slider from '../ui/Slider' ;
45import ColorWheel from '../ui/ColorWheel' ;
56import { ColorAdjustment , ColorCalibration , HueSatLum , INITIAL_ADJUSTMENTS } from '../../utils/adjustments' ;
@@ -114,6 +115,8 @@ const ColorSwatch = ({ color, name, isActive, onClick }: ColorSwatchProps) => {
114115} ;
115116
116117const ColorGradingPanel = ( { adjustments, setAdjustments, onDragStateChange } : ColorPanelProps ) => {
118+ const [ activeTab , setActiveTab ] = useState < '3way' | 'global' > ( '3way' ) ;
119+ const [ isExpanded , setIsExpanded ] = useState ( false ) ;
117120 const colorGrading = adjustments . colorGrading || INITIAL_ADJUSTMENTS . colorGrading ;
118121
119122 const handleChange = ( grading : ColorGrading , newValue : HueSatLum ) => {
@@ -136,39 +139,130 @@ const ColorGradingPanel = ({ adjustments, setAdjustments, onDragStateChange }: C
136139 } ) ) ;
137140 } ;
138141
142+ const tabs = [
143+ {
144+ id : '3way' ,
145+ icon : (
146+ < svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "currentColor" >
147+ < circle cx = "12" cy = "6" r = "4.5" />
148+ < circle cx = "5" cy = "18" r = "4.5" />
149+ < circle cx = "19" cy = "18" r = "4.5" />
150+ </ svg >
151+ ) ,
152+ } ,
153+ {
154+ id : 'global' ,
155+ icon : < div className = "w-3.5 h-3.5 rounded-full" style = { { background : 'linear-gradient(to top, #666, #fff)' } } /> ,
156+ } ,
157+ ] ;
158+
139159 return (
140160 < div >
141- < div className = "flex justify-center mb-4" >
142- < div className = "w-[calc(50%-0.5rem)]" >
143- < ColorWheel
144- defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . midtones }
145- label = "Midtones"
146- onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Midtones , val ) }
147- value = { colorGrading . midtones }
148- onDragStateChange = { onDragStateChange }
149- />
150- </ div >
161+ < div className = "flex items-center justify-start gap-2 mb-4 mt-2" >
162+ { tabs . map ( ( tab ) => {
163+ const isActive = activeTab === tab . id ;
164+ return (
165+ < button
166+ key = { tab . id }
167+ onClick = { ( ) => setActiveTab ( tab . id as '3way' | 'global' ) }
168+ className = { `w-7 h-7 rounded-full flex items-center justify-center transition-all focus:outline-none
169+ ${
170+ isActive
171+ ? 'ring-2 ring-offset-2 ring-offset-surface ring-accent text-text-primary'
172+ : 'bg-bg-secondary text-text-secondary hover:text-text-primary hover:bg-bg-secondary/80'
173+ } `}
174+ >
175+ { tab . icon }
176+ </ button >
177+ ) ;
178+ } ) }
179+
180+ < div className = "w-px h-5 bg-text-secondary/20 mx-1" />
181+
182+ < button
183+ onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
184+ className = { `w-7 h-7 rounded-full flex items-center justify-center transition-all focus:outline-none
185+ ${
186+ isExpanded
187+ ? 'bg-accent text-button-text'
188+ : 'bg-bg-secondary text-text-secondary hover:text-text-primary hover:bg-bg-secondary/80'
189+ } `}
190+ data-tooltip = "Toggle Sliders"
191+ >
192+ < Sliders size = { 14 } />
193+ </ button >
151194 </ div >
152- < div className = "flex justify-between mb-2 gap-4" >
153- < div className = "w-full" >
154- < ColorWheel
155- defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . shadows }
156- label = "Shadows"
157- onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Shadows , val ) }
158- value = { colorGrading . shadows }
159- onDragStateChange = { onDragStateChange }
160- />
161- </ div >
162- < div className = "w-full" >
163- < ColorWheel
164- defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . highlights }
165- label = "Highlights"
166- onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Highlights , val ) }
167- value = { colorGrading . highlights }
168- onDragStateChange = { onDragStateChange }
169- />
170- </ div >
195+
196+ < div className = "relative w-full mb-4" >
197+ < AnimatePresence mode = "wait" >
198+ { activeTab === '3way' ? (
199+ < motion . div
200+ key = "3way"
201+ initial = { { opacity : 0 , x : - 15 } }
202+ animate = { { opacity : 1 , x : 0 } }
203+ exit = { { opacity : 0 , x : - 15 } }
204+ transition = { { duration : 0.2 } }
205+ className = "w-full"
206+ >
207+ < div className = "flex justify-center mb-4" >
208+ < div className = "w-[calc(50%-0.5rem)]" >
209+ < ColorWheel
210+ defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . midtones }
211+ label = "Midtones"
212+ onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Midtones , val ) }
213+ value = { colorGrading . midtones }
214+ onDragStateChange = { onDragStateChange }
215+ isExpanded = { isExpanded }
216+ />
217+ </ div >
218+ </ div >
219+ < div className = "flex justify-between mb-2 gap-4" >
220+ < div className = "w-full flex-1 min-w-0" >
221+ < ColorWheel
222+ defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . shadows }
223+ label = "Shadows"
224+ onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Shadows , val ) }
225+ value = { colorGrading . shadows }
226+ onDragStateChange = { onDragStateChange }
227+ isExpanded = { isExpanded }
228+ />
229+ </ div >
230+ < div className = "w-full flex-1 min-w-0" >
231+ < ColorWheel
232+ defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . highlights }
233+ label = "Highlights"
234+ onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Highlights , val ) }
235+ value = { colorGrading . highlights }
236+ onDragStateChange = { onDragStateChange }
237+ isExpanded = { isExpanded }
238+ />
239+ </ div >
240+ </ div >
241+ </ motion . div >
242+ ) : (
243+ < motion . div
244+ key = "global"
245+ initial = { { opacity : 0 , x : 15 } }
246+ animate = { { opacity : 1 , x : 0 } }
247+ exit = { { opacity : 0 , x : 15 } }
248+ transition = { { duration : 0.2 } }
249+ className = "w-full flex justify-center pb-2"
250+ >
251+ < div className = "w-full max-w-70" >
252+ < ColorWheel
253+ defaultValue = { INITIAL_ADJUSTMENTS . colorGrading . global }
254+ label = "Global"
255+ onChange = { ( val : HueSatLum ) => handleChange ( ColorGrading . Global , val ) }
256+ value = { colorGrading . global || INITIAL_ADJUSTMENTS . colorGrading . global }
257+ onDragStateChange = { onDragStateChange }
258+ isExpanded = { isExpanded }
259+ />
260+ </ div >
261+ </ motion . div >
262+ ) }
263+ </ AnimatePresence >
171264 </ div >
265+
172266 < div >
173267 < Slider
174268 defaultValue = { 50 }
@@ -346,7 +440,7 @@ export default function ColorPanel({
346440 onChange = { ( e : any ) => handleGlobalChange ( ColorAdjustment . Temperature , e . target . value ) }
347441 step = { 1 }
348442 value = { adjustments . temperature || 0 }
349- trackClassName = ' temperature-gradient-track'
443+ trackClassName = " temperature-gradient-track"
350444 onDragStateChange = { onDragStateChange }
351445 />
352446 < Slider
@@ -356,7 +450,7 @@ export default function ColorPanel({
356450 onChange = { ( e : any ) => handleGlobalChange ( ColorAdjustment . Tint , e . target . value ) }
357451 step = { 1 }
358452 value = { adjustments . tint || 0 }
359- trackClassName = ' tint-gradient-track'
453+ trackClassName = " tint-gradient-track"
360454 onDragStateChange = { onDragStateChange }
361455 />
362456 </ div >
0 commit comments