A web app to help you design things, local, offline, on device. In your browser.
TypeScript
53.1%
JSON
45.6%
CSS
0.9%
Markdown
0.3%
JavaScript
0.1%
'use client';
import React from 'react';
import { RotateCcw, Sun, Contrast, Droplets, Sparkles } from 'lucide-react';
import { useEditorStore } from '@/lib/store/editor-store';
import type { ImageLayerData, ImageFilters } from '@/types';
const DEFAULT_FILTERS: ImageFilters = {
brightness: 0,
contrast: 0,
saturation: 0,
blur: 0,
};
interface FilterSliderProps {
label: string;
icon: React.ReactNode;
value: number;
min: number;
max: number;
onChange: (value: number) => void;
unit?: string;
}
function FilterSlider({ label, icon, value, min, max, onChange, unit = '' }: FilterSliderProps) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2 text-neutral-400">
{icon}
<span className="text-xs">{label}</span>
</div>
<span className="text-xs text-white tabular-nums">
{value > 0 ? '+' : ''}{value}{unit}
</span>
</div>
<input
type="range"
min={min}
max={max}
value={value}
onChange={(e) => onChange(Number(e.target.value))}
className="w-full h-1.5 bg-neutral-700 rounded-full appearance-none cursor-pointer
[&::-webkit-slider-thumb]:appearance-none
[&::-webkit-slider-thumb]:w-3
[&::-webkit-slider-thumb]:h-3
[&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-white
[&::-webkit-slider-thumb]:cursor-pointer
[&::-webkit-slider-thumb]:shadow-md
"
/>
</div>
);
}
export function AdjustmentsPanel() {
const { layers, selectedLayerIds, updateLayer } = useEditorStore();
const selectedLayer = selectedLayerIds.length === 1
? layers.find((l) => l.id === selectedLayerIds[0])
: null;
const isImageLayer = selectedLayer?.type === 'image';
const imageData = isImageLayer ? (selectedLayer.data as ImageLayerData) : null;
const filters = imageData?.filters || DEFAULT_FILTERS;
const updateFilter = (key: keyof ImageFilters, value: number) => {
if (!selectedLayer || !isImageLayer) return;
updateLayer(selectedLayer.id, {
data: {
...selectedLayer.data,
filters: {
...filters,
[key]: value,
},
} as ImageLayerData,
});
};
const resetFilters = () => {
if (!selectedLayer || !isImageLayer) return;
updateLayer(selectedLayer.id, {
data: {
...selectedLayer.data,
filters: DEFAULT_FILTERS,
} as ImageLayerData,
});
};
const hasChanges = filters.brightness !== 0 || filters.contrast !== 0 ||
filters.saturation !== 0 || filters.blur !== 0;
if (!isImageLayer) {
return (
<div className="p-4">
<h2 className="text-lg font-semibold text-white mb-4">Adjustments</h2>
<div className="flex flex-col items-center justify-center py-12 text-neutral-500">
<Sparkles className="w-12 h-12 mb-2 opacity-50" />
<span className="text-sm text-center">Select an image layer to adjust its appearance</span>
</div>
</div>
);
}
return (
<div className="p-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold text-white">Adjustments</h2>
{hasChanges && (
<button
onClick={resetFilters}
className="flex items-center gap-1 px-2 py-1 rounded text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 transition-colors"
>
<RotateCcw className="w-3 h-3" />
Reset
</button>
)}
</div>
{/* Selected layer info */}
<div className="mb-4 p-2 rounded-lg bg-neutral-800/50 border border-neutral-700/50">
<p className="text-xs text-neutral-400">
Editing: <span className="text-white">{selectedLayer.name}</span>
</p>
</div>
{/* Filter controls */}
<div className="space-y-5">
<FilterSlider
label="Brightness"
icon={<Sun className="w-4 h-4" />}
value={filters.brightness}
min={-100}
max={100}
onChange={(v) => updateFilter('brightness', v)}
/>
<FilterSlider
label="Contrast"
icon={<Contrast className="w-4 h-4" />}
value={filters.contrast}
min={-100}
max={100}
onChange={(v) => updateFilter('contrast', v)}
/>
<FilterSlider
label="Saturation"
icon={<Droplets className="w-4 h-4" />}
value={filters.saturation}
min={-100}
max={100}
onChange={(v) => updateFilter('saturation', v)}
/>
<FilterSlider
label="Blur"
icon={<Sparkles className="w-4 h-4" />}
value={filters.blur}
min={0}
max={20}
unit="px"
onChange={(v) => updateFilter('blur', v)}
/>
</div>
{/* Presets */}
<div className="mt-6">
<label className="text-xs text-neutral-500 uppercase tracking-wider block mb-2">
Quick Presets
</label>
<div className="grid grid-cols-2 gap-2">
<button
onClick={() => {
if (!selectedLayer) return;
updateLayer(selectedLayer.id, {
data: {
...selectedLayer.data,
filters: { brightness: 10, contrast: 10, saturation: 15, blur: 0 },
} as ImageLayerData,
});
}}
className="px-3 py-2 rounded-lg bg-neutral-800 hover:bg-neutral-700 text-white text-xs transition-colors"
>
Vibrant
</button>
<button
onClick={() => {
if (!selectedLayer) return;
updateLayer(selectedLayer.id, {
data: {
...selectedLayer.data,
filters: { brightness: 5, contrast: -10, saturation: -100, blur: 0 },
} as ImageLayerData,
});
}}
className="px-3 py-2 rounded-lg bg-neutral-800 hover:bg-neutral-700 text-white text-xs transition-colors"
>
B&W
</button>
<button
onClick={() => {
if (!selectedLayer) return;
updateLayer(selectedLayer.id, {
data: {
...selectedLayer.data,
filters: { brightness: 15, contrast: -15, saturation: -20, blur: 0 },
} as ImageLayerData,
});
}}
className="px-3 py-2 rounded-lg bg-neutral-800 hover:bg-neutral-700 text-white text-xs transition-colors"
>
Faded
</button>
<button
onClick={() => {
if (!selectedLayer) return;
updateLayer(selectedLayer.id, {
data: {
...selectedLayer.data,
filters: { brightness: -10, contrast: 20, saturation: 10, blur: 0 },
} as ImageLayerData,
});
}}
className="px-3 py-2 rounded-lg bg-neutral-800 hover:bg-neutral-700 text-white text-xs transition-colors"
>
Dramatic
</button>
</div>
</div>
{/* Info */}
<div className="mt-6 p-3 rounded-lg bg-neutral-800/50 border border-neutral-700/50">
<h3 className="text-sm font-medium text-white mb-2">Tips</h3>
<ul className="text-xs text-neutral-400 space-y-1">
<li>• Adjustments are non-destructive</li>
<li>• Use brightness for exposure fixes</li>
<li>• Contrast adds depth to images</li>
<li>• Saturation controls color intensity</li>
</ul>
</div>
</div>
);
}
About
A web app to help you design things, local, offline, on device. In your browser.
0 stars
0 forks