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, { useState } from 'react';
import { Smartphone, Tablet, Monitor, Watch } from 'lucide-react';
import { useEditorStore } from '@/lib/store/editor-store';
import { DEVICE_SPECS, DEVICE_COLORS, getDevicesByCategory } from '@/lib/devices/device-specs';
import type { DeviceType, DeviceColor, DeviceLayerData } from '@/types';
const categories = [
{ id: 'iphone', label: 'iPhone', icon: Smartphone },
{ id: 'ipad', label: 'iPad', icon: Tablet },
{ id: 'mac', label: 'Mac', icon: Monitor },
{ id: 'android', label: 'Android', icon: Smartphone },
{ id: 'watch', label: 'Watch', icon: Watch },
] as const;
export function DevicePanel() {
const [activeCategory, setActiveCategory] = useState<(typeof categories)[number]['id']>('iphone');
const [selectedColor, setSelectedColor] = useState<DeviceColor>('black');
const [showFrame, setShowFrame] = useState(true);
const { addLayer, canvas } = useEditorStore();
const devices = getDevicesByCategory(activeCategory);
const handleAddDevice = (deviceId: DeviceType) => {
const spec = DEVICE_SPECS[deviceId];
if (!spec) return;
// Calculate a good default size that fits in the canvas
const scale = Math.min(
(canvas.width * 0.6) / spec.frameSize.width,
(canvas.height * 0.7) / spec.frameSize.height
);
const width = spec.frameSize.width * scale;
const height = spec.frameSize.height * scale;
const layerData: DeviceLayerData = {
type: 'device',
deviceId,
color: selectedColor,
showFrame,
};
addLayer({
type: 'device',
name: spec.name,
visible: true,
locked: false,
opacity: 1,
position: {
x: (canvas.width - width) / 2,
y: (canvas.height - height) / 2,
},
size: { width, height },
rotation: 0,
data: layerData,
});
};
return (
<div className="p-4">
<h2 className="text-lg font-semibold text-white mb-4">Device Mockups</h2>
{/* Category tabs */}
<div className="flex gap-1 mb-4 overflow-x-auto pb-1">
{categories.map((cat) => {
const Icon = cat.icon;
return (
<button
key={cat.id}
onClick={() => setActiveCategory(cat.id)}
className={`
flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm whitespace-nowrap
transition-colors
${
activeCategory === cat.id
? 'bg-indigo-500/20 text-indigo-400'
: 'text-neutral-400 hover:text-white hover:bg-neutral-800'
}
`}
>
<Icon className="w-4 h-4" />
{cat.label}
</button>
);
})}
</div>
{/* Options */}
<div className="space-y-4 mb-6">
{/* Color selection */}
<div>
<label className="text-sm text-neutral-400 block mb-2">Device Color</label>
<div className="flex gap-2 flex-wrap">
{['black', 'white', 'silver', 'gold', 'blue', 'purple', 'pink', 'green'].map(
(color) => (
<button
key={color}
onClick={() => setSelectedColor(color as DeviceColor)}
className={`
w-7 h-7 rounded-full border-2 transition-all
${
selectedColor === color
? 'border-indigo-500 scale-110'
: 'border-transparent hover:border-neutral-500'
}
`}
style={{ backgroundColor: DEVICE_COLORS[color] }}
title={color.charAt(0).toUpperCase() + color.slice(1)}
/>
)
)}
</div>
</div>
{/* Frame toggle */}
<div className="flex items-center gap-3">
<button
onClick={() => setShowFrame(!showFrame)}
className={`
relative w-10 h-6 rounded-full transition-colors
${showFrame ? 'bg-indigo-500' : 'bg-neutral-700'}
`}
>
<div
className={`
absolute top-1 w-4 h-4 rounded-full bg-white transition-transform
${showFrame ? 'left-5' : 'left-1'}
`}
/>
</button>
<span className="text-sm text-neutral-300">Show device frame</span>
</div>
</div>
{/* Device list */}
<div className="space-y-2">
{devices.map((device) => (
<button
key={device.id}
onClick={() => handleAddDevice(device.id)}
className="
w-full p-3 rounded-lg bg-neutral-800/50 hover:bg-neutral-800
border border-neutral-700/50 hover:border-neutral-600
transition-all group text-left
"
>
<div className="flex items-center gap-3">
{/* Device preview */}
<div
className="w-10 h-16 rounded-lg flex items-center justify-center"
style={{ backgroundColor: DEVICE_COLORS[selectedColor] }}
>
<div className="w-8 h-12 bg-black rounded" />
</div>
<div className="flex-1">
<div className="text-sm font-medium text-white group-hover:text-indigo-400 transition-colors">
{device.name}
</div>
<div className="text-xs text-neutral-500">
{device.screenSize.width} x {device.screenSize.height}
</div>
</div>
<div className="text-neutral-500 group-hover:text-indigo-400 transition-colors">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
</div>
</div>
</button>
))}
</div>
{/* Tips */}
<div className="mt-6 p-3 rounded-lg bg-indigo-500/10 border border-indigo-500/20">
<p className="text-xs text-indigo-300">
Tip: Drag and drop screenshots onto devices to fill them. You can also double-click a
device to import an image.
</p>
</div>
</div>
);
}
About
A web app to help you design things, local, offline, on device. In your browser.
0 stars
0 forks