aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
383 lines (380 loc) • 13.8 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { Palette, Shuffle, Check, Copy, RotateCcw, Upload, Download } from 'lucide-react';
import { useState, useCallback } from 'react';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import { MotionFramer } from '../../primitives/motion/MotionFramer.js';
import { GlassButton } from '../button/GlassButton.js';
import '../button/GlassFab.js';
import '../button/GlassMagneticButton.js';
import { CardHeader, CardTitle, CardContent } from '../card/index.js';
import { GlassCard } from '../card/GlassCard.js';
/**
* GlassGradientPicker component
* Advanced gradient picker with presets and custom creation
*/
const GlassGradientPicker = ({
value,
presets = [],
enableCustom = true,
showTypeSelector = true,
showAngleControl = true,
showStopsEditor = true,
maxStops = 5,
onChange,
onPresetSelect,
className,
...props
}) => {
const [selectedType, setSelectedType] = useState("linear");
const [angle, setAngle] = useState(45);
const [stops, setStops] = useState([{
color: "var(--glass-color-primary)",
position: 0
}, {
color: "#8b5cf6",
position: 100
}]);
const [selectedStopIndex, setSelectedStopIndex] = useState(null);
const [copied, setCopied] = useState(false);
// Default presets
const defaultPresets = [{
id: "sunset",
name: "Sunset",
type: "linear",
angle: 45,
stops: [{
color: "#ff6b6b",
position: 0
}, {
color: "#ffa500",
position: 50
}, {
color: "#ff1493",
position: 100
}]
}, {
id: "ocean",
name: "Ocean",
type: "linear",
angle: 90,
stops: [{
color: "#0077be",
position: 0
}, {
color: "#00a8cc",
position: 50
}, {
color: "#90e0ef",
position: 100
}]
}, {
id: "forest",
name: "Forest",
type: "radial",
stops: [{
color: "#2d5016",
position: 0
}, {
color: "#4a7c59",
position: 70
}, {
color: "#90a955",
position: 100
}]
}, {
id: "fire",
name: "Fire",
type: "conic",
stops: [{
color: "#ff0000",
position: 0
}, {
color: "#ff8000",
position: 25
}, {
color: "#ffff00",
position: 50
}, {
color: "#ff0000",
position: 100
}]
}];
const allPresets = [...defaultPresets, ...presets];
// Generate CSS gradient string
const generateGradient = useCallback(() => {
if (stops.length === 0) return "transparent";
const colorStops = stops.sort((a, b) => a.position - b.position).map(stop => `${stop.color} ${stop.position}%`).join(", ");
switch (selectedType) {
case "linear":
return `linear-gradient(${angle}deg, ${colorStops})`;
case "radial":
return `radial-gradient(circle, ${colorStops})`;
case "conic":
return `conic-gradient(from 0deg, ${colorStops})`;
default:
return `linear-gradient(45deg, ${colorStops})`;
}
}, [stops, selectedType, angle]);
// Handle stop change
const handleStopChange = useCallback((index, updates) => {
setStops(prev => prev.map((stop, i) => i === index ? {
...stop,
...updates
} : stop));
}, []);
// Add new stop
const handleAddStop = useCallback(() => {
if (stops.length >= maxStops) return;
const newStop = {
color: "var(--glass-white)",
position: 50
};
setStops(prev => [...prev, newStop]);
setSelectedStopIndex(stops.length);
}, [stops.length, maxStops]);
// Remove stop
const handleRemoveStop = useCallback(index => {
if (stops.length <= 2) return; // Keep at least 2 stops
setStops(prev => prev.filter((_, i) => i !== index));
setSelectedStopIndex(null);
}, [stops.length]);
// Handle preset selection
const handlePresetSelect = useCallback(preset => {
setSelectedType(preset.type);
if (preset.angle !== undefined) {
setAngle(preset.angle);
}
setStops([...preset.stops]);
setSelectedStopIndex(null);
const gradient = generateGradient();
onChange?.(gradient, preset.stops, preset.type, preset.angle);
onPresetSelect?.(preset);
}, [generateGradient, onChange, onPresetSelect]);
// Generate random gradient
const handleRandomGradient = useCallback(() => {
const randomColor = () => "#" + Math.floor(Math.random() * 16777215).toString(16);
const newStops = [{
color: randomColor(),
position: 0
}, {
color: randomColor(),
position: 100
}];
if (Math.random() > 0.5) {
newStops.push({
color: randomColor(),
position: 50
});
}
setStops(newStops);
setSelectedStopIndex(null);
const gradient = generateGradient();
onChange?.(gradient, newStops, selectedType, angle);
}, [generateGradient, selectedType, angle, onChange]);
// Copy gradient to clipboard
const handleCopyGradient = useCallback(async () => {
const gradient = generateGradient();
try {
await navigator.clipboard.writeText(gradient);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy gradient:", err);
}
}, [generateGradient]);
// Reset to default
const handleReset = useCallback(() => {
setSelectedType("linear");
setAngle(45);
setStops([{
color: "var(--glass-color-primary)",
position: 0
}, {
color: "#8b5cf6",
position: 100
}]);
setSelectedStopIndex(null);
}, []);
const currentGradient = generateGradient();
return jsx(MotionFramer, {
"data-glass-component": true,
preset: "fadeIn",
className: "glass-w-full",
children: jsxs(GlassCard, {
className: cn("overflow-hidden", className),
...props,
children: [jsx(CardHeader, {
className: 'pb-3',
children: jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsxs(CardTitle, {
className: 'text-primary glass-text-lg font-semibold glass-flex glass-items-center glass-gap-2',
children: [jsx(Palette, {
className: 'w-5 h-5'
}), "Gradient Picker"]
}), jsxs("div", {
className: "glass-flex glass-gap-2",
children: [jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: handleRandomGradient,
className: "glass-p-2 glass-focus glass-touch-target",
title: "Random Gradient",
children: jsx(Shuffle, {
className: 'w-4 h-4'
})
}), jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: handleCopyGradient,
className: "glass-p-2 glass-focus glass-touch-target",
title: "Copy Gradient",
children: copied ? jsx(Check, {
className: 'w-4 h-4'
}) : jsx(Copy, {
className: 'w-4 h-4'
})
})]
})]
})
}), jsxs(CardContent, {
className: 'pt-0 glass-auto-gap glass-auto-gap-2xl',
children: [jsxs("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: [jsx("div", {
className: 'glass-w-full h-32 glass-radius-lg glass-border glass-border-white/20',
style: {
background: currentGradient
}
}), jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsx("code", {
className: 'glass-text-xs text-primary/60 glass-surface-subtle/10 glass-px-2 glass-py-1 glass-radius-md font-mono truncate glass-flex-1 glass-mr-2',
children: currentGradient
}), jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: handleReset,
className: "glass-p-2 glass-focus glass-touch-target",
title: "Reset",
children: jsx(RotateCcw, {
className: 'w-4 h-4'
})
})]
})]
}), showTypeSelector && jsxs("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: [jsx("h4", {
className: 'text-primary/80 glass-text-sm font-medium',
children: "Type"
}), jsx("div", {
className: "glass-flex glass-gap-2",
children: ["linear", "radial", "conic"].map(type => jsx(GlassButton, {
variant: selectedType === type ? "primary" : "ghost",
size: "sm",
onClick: e => setSelectedType(type),
className: 'capitalize glass-focus glass-touch-target',
children: type
}, type))
})]
}), showAngleControl && selectedType === "linear" && jsxs("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: [jsxs("h4", {
className: 'text-primary/80 glass-text-sm font-medium',
children: ["Angle: ", angle, "\u00B0"]
}), jsx("input", {
type: "range",
min: "0",
max: "360",
value: angle,
onChange: e => setAngle(Number(e.target.value)),
className: 'glass-w-full h-2 glass-surface-subtle/20 glass-radius-lg appearance-none cursor-pointer glass-focus glass-touch-target glass-contrast-guard'
})]
}), showStopsEditor && enableCustom && jsxs("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: [jsxs("div", {
className: "glass-flex glass-items-center glass-justify-between",
children: [jsx("h4", {
className: 'text-primary/80 glass-text-sm font-medium',
children: "Color Stops"
}), stops.length < maxStops && jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: handleAddStop,
className: "glass-p-1 glass-focus glass-touch-target",
children: jsx(Upload, {
className: 'w-4 h-4'
})
})]
}), jsx("div", {
className: "glass-auto-gap glass-auto-gap-sm",
children: stops.map((stop, index) => jsxs("div", {
className: cn("flex items-center glass-gap-3 glass-p-3 glass-radius-lg border transition-all", selectedStopIndex === index ? "border-primary bg-primary/20" : "border-white/20 bg-white/5"),
onClick: e => setSelectedStopIndex(index),
children: [jsx("input", {
type: "color",
value: stop.color,
onChange: e => handleStopChange(index, {
color: e.target.value
}),
className: 'w-8 h-8 glass-radius-md glass-border glass-border-white/20 cursor-pointer glass-focus glass-touch-target glass-contrast-guard'
}), jsxs("div", {
className: "glass-flex-1",
children: [jsx("input", {
type: "range",
min: "0",
max: "100",
value: stop.position,
onChange: e => handleStopChange(index, {
position: Number(e.target.value)
}),
className: 'glass-w-full h-2 glass-surface-subtle/20 glass-radius-lg appearance-none cursor-pointer glass-focus glass-touch-target glass-contrast-guard'
}), jsxs("div", {
className: 'glass-text-xs text-primary/60 glass-mt-1',
children: ["Position: ", stop.position, "%"]
})]
}), stops.length > 2 && jsx(GlassButton, {
variant: "ghost",
size: "sm",
onClick: e => handleRemoveStop(index),
className: 'glass-p-1 text-primary hover:glass-text-secondary glass-focus glass-touch-target',
children: jsx(Download, {
className: 'w-4 h-4'
})
})]
}, index))
})]
}), allPresets.length > 0 && jsxs("div", {
className: "glass-auto-gap glass-auto-gap-md",
children: [jsx("h4", {
className: 'text-primary/80 glass-text-sm font-medium',
children: "Presets"
}), jsx("div", {
className: "glass-grid glass-grid-cols-2 glass-gap-3",
children: allPresets.map(preset => jsxs("div", {
className: 'cursor-pointer group glass-focus glass-touch-target',
onClick: e => handlePresetSelect(preset),
children: [jsx("div", {
className: 'glass-w-full h-16 glass-radius-lg glass-border glass-border-white/20 group-hover:border-white/40 transition-all',
style: {
background: preset.stops.length > 0 ? `${preset.type}-gradient(${preset.angle || 0}deg, ${preset.stops.map(s => `${s.color} ${s.position}%`).join(", ")})` : "transparent"
}
}), jsx("p", {
className: 'text-primary/80 glass-text-xs glass-mt-2 text-center group-hover:text-primary transition-colors',
children: preset.name
})]
}, preset.id))
})]
})]
})]
})
});
};
export { GlassGradientPicker, GlassGradientPicker as default };
//# sourceMappingURL=GlassGradientPicker.js.map