@designtools/shadows 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +46 -34
- package/dist/client/assets/index-JzB7eypf.css +1 -0
- package/{src/client/dist → dist/client}/index.html +2 -2
- package/package.json +3 -5
- package/src/client/app.tsx +0 -123
- package/src/client/components/shadow-controls.tsx +0 -287
- package/src/client/components/shadow-editor-panel.tsx +0 -163
- package/src/client/components/shadow-list.tsx +0 -343
- package/src/client/components/shadow-overview.tsx +0 -111
- package/src/client/components/shadow-preview-settings.tsx +0 -210
- package/src/client/components/shadow-preview.tsx +0 -34
- package/src/client/dist/assets/index-Bb9AiwJs.css +0 -1
- package/src/client/index.html +0 -13
- package/src/client/main.tsx +0 -5
- package/src/client/styles.css +0 -1
- /package/{src/client/dist/assets/index-CA2rtXvo.js → dist/client/assets/index-B-QvQdUy.js} +0 -0
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { useState, useRef } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ChevronRightIcon,
|
|
4
|
-
ChevronDownIcon,
|
|
5
|
-
Pencil1Icon,
|
|
6
|
-
BookmarkIcon,
|
|
7
|
-
CheckIcon,
|
|
8
|
-
} from "@radix-ui/react-icons";
|
|
9
|
-
import { ShadowControls } from "./shadow-controls.js";
|
|
10
|
-
import { ShadowPreview } from "./shadow-preview.js";
|
|
11
|
-
import type { PreviewSettings } from "./shadow-preview-settings.js";
|
|
12
|
-
|
|
13
|
-
interface ShadowListProps {
|
|
14
|
-
shadows: any[];
|
|
15
|
-
cssFilePath: string;
|
|
16
|
-
stylingType: string;
|
|
17
|
-
onPreviewShadow: (variableName: string, value: string, shadowName?: string) => void;
|
|
18
|
-
previewSettings: PreviewSettings;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function ShadowList({
|
|
22
|
-
shadows,
|
|
23
|
-
cssFilePath,
|
|
24
|
-
stylingType,
|
|
25
|
-
onPreviewShadow,
|
|
26
|
-
previewSettings,
|
|
27
|
-
}: ShadowListProps) {
|
|
28
|
-
const [expandedShadow, setExpandedShadow] = useState<string | null>(null);
|
|
29
|
-
const [editMode, setEditMode] = useState<Record<string, "custom" | "preset">>({});
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div>
|
|
33
|
-
{shadows.map((shadow: any) => (
|
|
34
|
-
<ShadowRow
|
|
35
|
-
key={shadow.name}
|
|
36
|
-
shadow={shadow}
|
|
37
|
-
isExpanded={expandedShadow === shadow.name}
|
|
38
|
-
onToggle={() =>
|
|
39
|
-
setExpandedShadow(
|
|
40
|
-
expandedShadow === shadow.name ? null : shadow.name
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
mode={editMode[shadow.name] || "custom"}
|
|
44
|
-
onModeChange={(mode) =>
|
|
45
|
-
setEditMode({ ...editMode, [shadow.name]: mode })
|
|
46
|
-
}
|
|
47
|
-
cssFilePath={cssFilePath}
|
|
48
|
-
stylingType={stylingType}
|
|
49
|
-
onPreviewShadow={onPreviewShadow}
|
|
50
|
-
previewSettings={previewSettings}
|
|
51
|
-
/>
|
|
52
|
-
))}
|
|
53
|
-
|
|
54
|
-
{shadows.length === 0 && (
|
|
55
|
-
<div
|
|
56
|
-
className="px-4 py-6 text-center text-[11px]"
|
|
57
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
58
|
-
>
|
|
59
|
-
No shadows found. Add shadow CSS variables to your global CSS file.
|
|
60
|
-
</div>
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function ShadowRow({
|
|
67
|
-
shadow,
|
|
68
|
-
isExpanded,
|
|
69
|
-
onToggle,
|
|
70
|
-
mode,
|
|
71
|
-
onModeChange,
|
|
72
|
-
cssFilePath,
|
|
73
|
-
stylingType,
|
|
74
|
-
onPreviewShadow,
|
|
75
|
-
previewSettings,
|
|
76
|
-
}: {
|
|
77
|
-
shadow: any;
|
|
78
|
-
isExpanded: boolean;
|
|
79
|
-
onToggle: () => void;
|
|
80
|
-
mode: "custom" | "preset";
|
|
81
|
-
onModeChange: (mode: "custom" | "preset") => void;
|
|
82
|
-
cssFilePath: string;
|
|
83
|
-
stylingType: string;
|
|
84
|
-
onPreviewShadow: (variableName: string, value: string, shadowName?: string) => void;
|
|
85
|
-
previewSettings: PreviewSettings;
|
|
86
|
-
}) {
|
|
87
|
-
const [saving, setSaving] = useState(false);
|
|
88
|
-
const [currentValue, setCurrentValue] = useState(shadow.value);
|
|
89
|
-
|
|
90
|
-
const handleSave = async (newValue: string) => {
|
|
91
|
-
setCurrentValue(newValue);
|
|
92
|
-
setSaving(true);
|
|
93
|
-
try {
|
|
94
|
-
// Design token shadows write to their own endpoint
|
|
95
|
-
if (shadow.source === "design-token" && shadow.tokenFilePath && shadow.tokenPath) {
|
|
96
|
-
const res = await fetch("/api/shadows/design-token", {
|
|
97
|
-
method: "POST",
|
|
98
|
-
headers: { "Content-Type": "application/json" },
|
|
99
|
-
body: JSON.stringify({
|
|
100
|
-
filePath: shadow.tokenFilePath,
|
|
101
|
-
tokenPath: shadow.tokenPath,
|
|
102
|
-
value: newValue,
|
|
103
|
-
}),
|
|
104
|
-
});
|
|
105
|
-
const data = await res.json();
|
|
106
|
-
if (!data.ok) console.error("Design token save failed:", data.error);
|
|
107
|
-
} else {
|
|
108
|
-
// CSS/SCSS shadow writes
|
|
109
|
-
const variableName = shadow.sassVariable || shadow.cssVariable || `--${shadow.name}`;
|
|
110
|
-
let selector: string;
|
|
111
|
-
let filePath = cssFilePath;
|
|
112
|
-
|
|
113
|
-
if (stylingType === "tailwind-v4") {
|
|
114
|
-
selector = "@theme";
|
|
115
|
-
} else if (stylingType === "bootstrap" && shadow.sassVariable) {
|
|
116
|
-
selector = "scss";
|
|
117
|
-
// Use the SCSS file if available, fall back to CSS file
|
|
118
|
-
filePath = shadow.filePath || cssFilePath;
|
|
119
|
-
} else {
|
|
120
|
-
selector = ":root";
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const endpoint = shadow.isOverridden ? "/api/shadows" : "/api/shadows/create";
|
|
124
|
-
const res = await fetch(endpoint, {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers: { "Content-Type": "application/json" },
|
|
127
|
-
body: JSON.stringify({
|
|
128
|
-
filePath,
|
|
129
|
-
variableName,
|
|
130
|
-
value: newValue,
|
|
131
|
-
selector,
|
|
132
|
-
}),
|
|
133
|
-
});
|
|
134
|
-
const data = await res.json();
|
|
135
|
-
if (!data.ok) console.error("Shadow save failed:", data.error);
|
|
136
|
-
}
|
|
137
|
-
} catch (err) {
|
|
138
|
-
console.error("Shadow save error:", err);
|
|
139
|
-
}
|
|
140
|
-
setTimeout(() => setSaving(false), 1000);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<div style={{ borderTop: "1px solid var(--studio-border-subtle)" }}>
|
|
145
|
-
<button
|
|
146
|
-
onClick={onToggle}
|
|
147
|
-
className="studio-section-hdr"
|
|
148
|
-
style={{ gap: 8 }}
|
|
149
|
-
>
|
|
150
|
-
{isExpanded ? <ChevronDownIcon /> : <ChevronRightIcon />}
|
|
151
|
-
<ShadowPreview
|
|
152
|
-
value={currentValue}
|
|
153
|
-
size={24}
|
|
154
|
-
showBorder={previewSettings.showBorder}
|
|
155
|
-
borderColor={previewSettings.borderColor}
|
|
156
|
-
/>
|
|
157
|
-
<span
|
|
158
|
-
className="flex-1 text-[11px] font-mono truncate text-left"
|
|
159
|
-
style={{
|
|
160
|
-
color: "var(--studio-text)",
|
|
161
|
-
fontWeight: 500,
|
|
162
|
-
textTransform: "none",
|
|
163
|
-
letterSpacing: 0,
|
|
164
|
-
}}
|
|
165
|
-
>
|
|
166
|
-
{shadow.name}
|
|
167
|
-
</span>
|
|
168
|
-
{shadow.source === "framework-preset" && !shadow.isOverridden && (
|
|
169
|
-
<span
|
|
170
|
-
className="text-[9px] px-1.5 py-0.5 rounded"
|
|
171
|
-
style={{
|
|
172
|
-
background: "var(--studio-input-bg)",
|
|
173
|
-
color: "var(--studio-text-dimmed)",
|
|
174
|
-
}}
|
|
175
|
-
>
|
|
176
|
-
preset
|
|
177
|
-
</span>
|
|
178
|
-
)}
|
|
179
|
-
{shadow.source === "design-token" && (
|
|
180
|
-
<span
|
|
181
|
-
className="text-[9px] px-1.5 py-0.5 rounded"
|
|
182
|
-
style={{
|
|
183
|
-
background: "var(--studio-input-bg)",
|
|
184
|
-
color: "var(--studio-text-dimmed)",
|
|
185
|
-
}}
|
|
186
|
-
>
|
|
187
|
-
token
|
|
188
|
-
</span>
|
|
189
|
-
)}
|
|
190
|
-
{saving && (
|
|
191
|
-
<span
|
|
192
|
-
className="flex items-center gap-0.5 text-[10px]"
|
|
193
|
-
style={{ color: "var(--studio-success)" }}
|
|
194
|
-
>
|
|
195
|
-
<CheckIcon style={{ width: 10, height: 10 }} />
|
|
196
|
-
</span>
|
|
197
|
-
)}
|
|
198
|
-
</button>
|
|
199
|
-
|
|
200
|
-
{isExpanded && (
|
|
201
|
-
<div className="px-4 pb-3">
|
|
202
|
-
{/* Edit mode toggle */}
|
|
203
|
-
<div className="studio-segmented mb-3" style={{ width: "100%" }}>
|
|
204
|
-
<button
|
|
205
|
-
onClick={() => onModeChange("custom")}
|
|
206
|
-
className={mode === "custom" ? "active" : ""}
|
|
207
|
-
style={{ flex: 1 }}
|
|
208
|
-
>
|
|
209
|
-
<Pencil1Icon style={{ width: 12, height: 12 }} />
|
|
210
|
-
Custom
|
|
211
|
-
</button>
|
|
212
|
-
<button
|
|
213
|
-
onClick={() => onModeChange("preset")}
|
|
214
|
-
className={mode === "preset" ? "active" : ""}
|
|
215
|
-
style={{ flex: 1 }}
|
|
216
|
-
>
|
|
217
|
-
<BookmarkIcon style={{ width: 12, height: 12 }} />
|
|
218
|
-
Use Preset
|
|
219
|
-
</button>
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
{mode === "custom" ? (
|
|
223
|
-
<ShadowControls
|
|
224
|
-
shadow={{ ...shadow, value: currentValue }}
|
|
225
|
-
onPreview={(value) => {
|
|
226
|
-
const varName = shadow.cssVariable || `--${shadow.name}`;
|
|
227
|
-
onPreviewShadow(varName, value, shadow.name);
|
|
228
|
-
}}
|
|
229
|
-
onSave={handleSave}
|
|
230
|
-
previewSettings={previewSettings}
|
|
231
|
-
/>
|
|
232
|
-
) : (
|
|
233
|
-
<PresetPicker
|
|
234
|
-
currentValue={currentValue}
|
|
235
|
-
onSelect={(value) => {
|
|
236
|
-
const varName = shadow.cssVariable || `--${shadow.name}`;
|
|
237
|
-
onPreviewShadow(varName, value, shadow.name);
|
|
238
|
-
handleSave(value);
|
|
239
|
-
}}
|
|
240
|
-
previewSettings={previewSettings}
|
|
241
|
-
/>
|
|
242
|
-
)}
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
function PresetPicker({
|
|
250
|
-
currentValue,
|
|
251
|
-
onSelect,
|
|
252
|
-
previewSettings,
|
|
253
|
-
}: {
|
|
254
|
-
currentValue: string;
|
|
255
|
-
onSelect: (value: string) => void;
|
|
256
|
-
previewSettings: PreviewSettings;
|
|
257
|
-
}) {
|
|
258
|
-
// Presets from shadow-explorer: multi-layer approaches at different depths
|
|
259
|
-
const presets = [
|
|
260
|
-
// Crisp 2-layer
|
|
261
|
-
{ name: "Crisp 2L", value: "0px 0.3px 0.7px 0px rgb(0 0 0 / 0.1), 0px 0.7px 2.1px 0px rgb(0 0 0 / 0.07)", desc: "Subtle" },
|
|
262
|
-
{ name: "Crisp 2L +", value: "0px 0.5px 1px 0px rgb(0 0 0 / 0.1), 0px 1px 3px 0px rgb(0 0 0 / 0.07), 0px 2px 6px 0px rgb(0 0 0 / 0.07)", desc: "Elevated" },
|
|
263
|
-
// Crisp 3-layer (doubling)
|
|
264
|
-
{ name: "Crisp 3L", value: "0px 1px 1px 0px rgb(0 0 0 / 0.08), 0px 2px 2px 0px rgb(0 0 0 / 0.08), 0px 4px 4px 0px rgb(0 0 0 / 0.08)", desc: "Card" },
|
|
265
|
-
{ name: "Crisp 3L +", value: "0px 1px 1px 0px rgb(0 0 0 / 0.08), 0px 2px 2px 0px rgb(0 0 0 / 0.08), 0px 4px 4px 0px rgb(0 0 0 / 0.08), 0px 8px 8px 0px rgb(0 0 0 / 0.08)", desc: "Elevated" },
|
|
266
|
-
// Crisp 3-layer tight
|
|
267
|
-
{ name: "Crisp Tight", value: "0px 0.5px 0.5px 0px rgb(0 0 0 / 0.09), 0px 1px 1.5px 0px rgb(0 0 0 / 0.07), 0px 3px 3px 0px rgb(0 0 0 / 0.06)", desc: "Card" },
|
|
268
|
-
{ name: "Crisp Tight +", value: "0px 0.5px 0.5px 0px rgb(0 0 0 / 0.09), 0px 1px 1.5px 0px rgb(0 0 0 / 0.07), 0px 3px 3px 0px rgb(0 0 0 / 0.06), 0px 6px 6px 0px rgb(0 0 0 / 0.06)", desc: "Elevated" },
|
|
269
|
-
// Crisp 4-layer
|
|
270
|
-
{ name: "Crisp 4L", value: "0px 0.5px 0.5px 0px rgb(0 0 0 / 0.07), 0px 1px 1px 0px rgb(0 0 0 / 0.06), 0px 2px 2px 0px rgb(0 0 0 / 0.06), 0px 4px 4px 0px rgb(0 0 0 / 0.06)", desc: "Card" },
|
|
271
|
-
{ name: "Crisp 4L +", value: "0px 0.5px 0.5px 0px rgb(0 0 0 / 0.07), 0px 1px 1px 0px rgb(0 0 0 / 0.06), 0px 2px 2px 0px rgb(0 0 0 / 0.06), 0px 4px 4px 0px rgb(0 0 0 / 0.06), 0px 8px 8px 0px rgb(0 0 0 / 0.06)", desc: "Elevated" },
|
|
272
|
-
// Crisp 4-layer with hard edge
|
|
273
|
-
{ name: "Edge 4L", value: "0px 0px 0.5px -0.5px rgb(0 0 0 / 0.12), 0px 1px 1px 0px rgb(0 0 0 / 0.06), 0px 2px 2px 0px rgb(0 0 0 / 0.06), 0px 4px 4px 0px rgb(0 0 0 / 0.05)", desc: "Card" },
|
|
274
|
-
{ name: "Edge 4L +", value: "0px 0px 0.5px -0.5px rgb(0 0 0 / 0.12), 0px 1px 1px 0px rgb(0 0 0 / 0.06), 0px 2px 2px 0px rgb(0 0 0 / 0.06), 0px 4px 4px 0px rgb(0 0 0 / 0.05), 0px 8px 8px 0px rgb(0 0 0 / 0.05)", desc: "Elevated" },
|
|
275
|
-
// Soft / diffuse
|
|
276
|
-
{ name: "Soft", value: "0px 1px 2px 0px rgb(0 0 0 / 0.04), 0px 2px 4px 0px rgb(0 0 0 / 0.04), 0px 4px 8px 0px rgb(0 0 0 / 0.04)", desc: "Diffuse" },
|
|
277
|
-
{ name: "Soft +", value: "0px 1px 2px 0px rgb(0 0 0 / 0.04), 0px 2px 4px 0px rgb(0 0 0 / 0.04), 0px 4px 8px 0px rgb(0 0 0 / 0.04), 0px 8px 16px 0px rgb(0 0 0 / 0.04)", desc: "Elevated" },
|
|
278
|
-
// Chunky — solid, hard shadow with minimal blur
|
|
279
|
-
{ name: "Chunky", value: "0px 2px 0px 0px rgb(0 0 0 / 0.15), 0px 4px 0px 0px rgb(0 0 0 / 0.1)", desc: "Hard" },
|
|
280
|
-
{ name: "Chunky +", value: "0px 2px 0px 0px rgb(0 0 0 / 0.15), 0px 4px 0px 0px rgb(0 0 0 / 0.1), 0px 8px 0px 0px rgb(0 0 0 / 0.07)", desc: "Elevated" },
|
|
281
|
-
// None
|
|
282
|
-
{ name: "None", value: "none", desc: "Remove" },
|
|
283
|
-
];
|
|
284
|
-
|
|
285
|
-
return (
|
|
286
|
-
<div
|
|
287
|
-
className="grid gap-2"
|
|
288
|
-
style={{ gridTemplateColumns: "repeat(2, 1fr)" }}
|
|
289
|
-
>
|
|
290
|
-
{presets.map((preset) => {
|
|
291
|
-
const isActive = currentValue === preset.value;
|
|
292
|
-
return (
|
|
293
|
-
<button
|
|
294
|
-
key={preset.name}
|
|
295
|
-
onClick={() => onSelect(preset.value)}
|
|
296
|
-
className="flex flex-col items-center gap-1.5 p-3 rounded-lg cursor-pointer"
|
|
297
|
-
style={{
|
|
298
|
-
background: isActive
|
|
299
|
-
? "var(--studio-accent-muted)"
|
|
300
|
-
: "transparent",
|
|
301
|
-
border: "1px solid",
|
|
302
|
-
borderColor: isActive
|
|
303
|
-
? "var(--studio-accent)"
|
|
304
|
-
: "var(--studio-border-subtle)",
|
|
305
|
-
}}
|
|
306
|
-
>
|
|
307
|
-
<div
|
|
308
|
-
className="flex items-center justify-center rounded-lg w-full"
|
|
309
|
-
style={{
|
|
310
|
-
background: previewSettings.previewBg,
|
|
311
|
-
height: 72,
|
|
312
|
-
}}
|
|
313
|
-
>
|
|
314
|
-
<ShadowPreview
|
|
315
|
-
value={preset.value}
|
|
316
|
-
size={48}
|
|
317
|
-
background="white"
|
|
318
|
-
showBorder={previewSettings.showBorder}
|
|
319
|
-
borderColor={previewSettings.borderColor}
|
|
320
|
-
/>
|
|
321
|
-
</div>
|
|
322
|
-
<div className="text-center">
|
|
323
|
-
<div
|
|
324
|
-
className="text-[10px] font-medium"
|
|
325
|
-
style={{ color: "var(--studio-text-muted)" }}
|
|
326
|
-
>
|
|
327
|
-
{preset.name}
|
|
328
|
-
</div>
|
|
329
|
-
{preset.desc && (
|
|
330
|
-
<div
|
|
331
|
-
className="text-[9px]"
|
|
332
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
333
|
-
>
|
|
334
|
-
{preset.desc}
|
|
335
|
-
</div>
|
|
336
|
-
)}
|
|
337
|
-
</div>
|
|
338
|
-
</button>
|
|
339
|
-
);
|
|
340
|
-
})}
|
|
341
|
-
</div>
|
|
342
|
-
);
|
|
343
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { ShadowPreview } from "./shadow-preview.js";
|
|
3
|
-
import type { PreviewSettings } from "./shadow-preview-settings.js";
|
|
4
|
-
|
|
5
|
-
interface ShadowOverviewProps {
|
|
6
|
-
shadows: any[];
|
|
7
|
-
previewSettings: PreviewSettings;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
type OverviewSize = "small" | "large";
|
|
11
|
-
|
|
12
|
-
export function ShadowOverview({ shadows, previewSettings }: ShadowOverviewProps) {
|
|
13
|
-
const [size, setSize] = useState<OverviewSize>("small");
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<div>
|
|
17
|
-
{/* Size toggle tabs */}
|
|
18
|
-
<div
|
|
19
|
-
className="px-4 py-2 shrink-0"
|
|
20
|
-
>
|
|
21
|
-
<div className="studio-segmented" style={{ width: "100%" }}>
|
|
22
|
-
<button
|
|
23
|
-
onClick={() => setSize("small")}
|
|
24
|
-
className={size === "small" ? "active" : ""}
|
|
25
|
-
style={{ flex: 1 }}
|
|
26
|
-
>
|
|
27
|
-
Small
|
|
28
|
-
</button>
|
|
29
|
-
<button
|
|
30
|
-
onClick={() => setSize("large")}
|
|
31
|
-
className={size === "large" ? "active" : ""}
|
|
32
|
-
style={{ flex: 1 }}
|
|
33
|
-
>
|
|
34
|
-
Large
|
|
35
|
-
</button>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
|
|
39
|
-
{/* Shadow grid */}
|
|
40
|
-
{size === "small" ? (
|
|
41
|
-
<div
|
|
42
|
-
className="grid gap-3 px-4 py-3"
|
|
43
|
-
style={{ gridTemplateColumns: "repeat(2, 1fr)" }}
|
|
44
|
-
>
|
|
45
|
-
{shadows.map((shadow: any) => (
|
|
46
|
-
<div key={shadow.name} className="flex flex-col items-center gap-2">
|
|
47
|
-
<div
|
|
48
|
-
className="flex items-center justify-center rounded-xl w-full"
|
|
49
|
-
style={{
|
|
50
|
-
background: previewSettings.previewBg,
|
|
51
|
-
height: 100,
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
54
|
-
<ShadowPreview
|
|
55
|
-
value={shadow.value}
|
|
56
|
-
size={56}
|
|
57
|
-
background="white"
|
|
58
|
-
showBorder={previewSettings.showBorder}
|
|
59
|
-
borderColor={previewSettings.borderColor}
|
|
60
|
-
/>
|
|
61
|
-
</div>
|
|
62
|
-
<span
|
|
63
|
-
className="text-[9px] font-mono text-center truncate w-full"
|
|
64
|
-
style={{ color: "var(--studio-text-muted)" }}
|
|
65
|
-
>
|
|
66
|
-
{shadow.name}
|
|
67
|
-
</span>
|
|
68
|
-
</div>
|
|
69
|
-
))}
|
|
70
|
-
</div>
|
|
71
|
-
) : (
|
|
72
|
-
<div className="flex flex-col gap-3 px-4 py-3">
|
|
73
|
-
{shadows.map((shadow: any) => (
|
|
74
|
-
<div key={shadow.name} className="flex flex-col items-center gap-2">
|
|
75
|
-
<div
|
|
76
|
-
className="flex items-center justify-center rounded-xl w-full"
|
|
77
|
-
style={{
|
|
78
|
-
background: previewSettings.previewBg,
|
|
79
|
-
height: 200,
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<ShadowPreview
|
|
83
|
-
value={shadow.value}
|
|
84
|
-
size={96}
|
|
85
|
-
background="white"
|
|
86
|
-
showBorder={previewSettings.showBorder}
|
|
87
|
-
borderColor={previewSettings.borderColor}
|
|
88
|
-
/>
|
|
89
|
-
</div>
|
|
90
|
-
<span
|
|
91
|
-
className="text-[9px] font-mono text-center truncate w-full"
|
|
92
|
-
style={{ color: "var(--studio-text-muted)" }}
|
|
93
|
-
>
|
|
94
|
-
{shadow.name}
|
|
95
|
-
</span>
|
|
96
|
-
</div>
|
|
97
|
-
))}
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
{shadows.length === 0 && (
|
|
102
|
-
<div
|
|
103
|
-
className="px-4 py-6 text-center text-[11px]"
|
|
104
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
105
|
-
>
|
|
106
|
-
No shadows to display
|
|
107
|
-
</div>
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from "react";
|
|
2
|
-
import { Half2Icon } from "@radix-ui/react-icons";
|
|
3
|
-
|
|
4
|
-
export interface PreviewSettings {
|
|
5
|
-
previewBg: string;
|
|
6
|
-
showBorder: boolean;
|
|
7
|
-
borderColor: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export const DEFAULT_PREVIEW_SETTINGS: PreviewSettings = {
|
|
11
|
-
previewBg: "rgb(240, 244, 250)",
|
|
12
|
-
showBorder: false,
|
|
13
|
-
borderColor: "#e8e8e8",
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const BG_SWATCHES = [
|
|
17
|
-
{ name: "Soft Blue", value: "rgb(240, 244, 250)" },
|
|
18
|
-
{ name: "White", value: "white" },
|
|
19
|
-
{ name: "Light Gray", value: "#f0f0f0" },
|
|
20
|
-
{ name: "Gray", value: "#e0e0e0" },
|
|
21
|
-
{ name: "Mid Gray", value: "#d0d0d0" },
|
|
22
|
-
{ name: "Dark Gray", value: "#bbb" },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
const BORDER_COLORS = [
|
|
26
|
-
{ name: "Lightest", value: "#eee" },
|
|
27
|
-
{ name: "Light", value: "#e0e0e0" },
|
|
28
|
-
{ name: "Medium", value: "#d0d0d0" },
|
|
29
|
-
{ name: "Dark", value: "#bbb" },
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
interface ShadowPreviewSettingsProps {
|
|
33
|
-
settings: PreviewSettings;
|
|
34
|
-
onChange: (settings: PreviewSettings) => void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function ShadowPreviewSettingsButton({
|
|
38
|
-
settings,
|
|
39
|
-
onChange,
|
|
40
|
-
}: ShadowPreviewSettingsProps) {
|
|
41
|
-
const [open, setOpen] = useState(false);
|
|
42
|
-
const popoverRef = useRef<HTMLDivElement>(null);
|
|
43
|
-
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (!open) return;
|
|
47
|
-
const handleClick = (e: MouseEvent) => {
|
|
48
|
-
if (
|
|
49
|
-
popoverRef.current &&
|
|
50
|
-
!popoverRef.current.contains(e.target as Node) &&
|
|
51
|
-
buttonRef.current &&
|
|
52
|
-
!buttonRef.current.contains(e.target as Node)
|
|
53
|
-
) {
|
|
54
|
-
setOpen(false);
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
document.addEventListener("mousedown", handleClick);
|
|
58
|
-
return () => document.removeEventListener("mousedown", handleClick);
|
|
59
|
-
}, [open]);
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div style={{ position: "relative" }}>
|
|
63
|
-
<button
|
|
64
|
-
ref={buttonRef}
|
|
65
|
-
onClick={() => setOpen(!open)}
|
|
66
|
-
className="cursor-pointer"
|
|
67
|
-
style={{
|
|
68
|
-
display: "flex",
|
|
69
|
-
alignItems: "center",
|
|
70
|
-
gap: 6,
|
|
71
|
-
padding: "3px 8px 3px 6px",
|
|
72
|
-
borderRadius: 6,
|
|
73
|
-
fontSize: 10,
|
|
74
|
-
fontWeight: 500,
|
|
75
|
-
color: open ? "var(--studio-accent)" : "var(--studio-text-dimmed)",
|
|
76
|
-
background: open ? "var(--studio-accent-muted)" : "var(--studio-input-bg)",
|
|
77
|
-
border: "1px solid",
|
|
78
|
-
borderColor: open ? "var(--studio-accent)" : "var(--studio-border)",
|
|
79
|
-
transition: "all 0.15s",
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<Half2Icon style={{ width: 12, height: 12 }} />
|
|
83
|
-
Settings
|
|
84
|
-
</button>
|
|
85
|
-
|
|
86
|
-
{open && (
|
|
87
|
-
<div
|
|
88
|
-
ref={popoverRef}
|
|
89
|
-
style={{
|
|
90
|
-
position: "absolute",
|
|
91
|
-
top: "100%",
|
|
92
|
-
right: 0,
|
|
93
|
-
marginTop: 4,
|
|
94
|
-
background: "var(--studio-surface)",
|
|
95
|
-
border: "1px solid var(--studio-border)",
|
|
96
|
-
borderRadius: 8,
|
|
97
|
-
padding: "12px 14px",
|
|
98
|
-
zIndex: 100,
|
|
99
|
-
width: 248,
|
|
100
|
-
boxShadow: "0 8px 24px rgb(0 0 0 / 0.15)",
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
103
|
-
<div className="flex flex-col gap-3.5">
|
|
104
|
-
{/* Background */}
|
|
105
|
-
<div>
|
|
106
|
-
<div
|
|
107
|
-
className="text-[9px] font-semibold uppercase tracking-wide mb-2"
|
|
108
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
109
|
-
>
|
|
110
|
-
Preview Background
|
|
111
|
-
</div>
|
|
112
|
-
<div className="flex gap-1.5">
|
|
113
|
-
{BG_SWATCHES.map((bg) => (
|
|
114
|
-
<button
|
|
115
|
-
key={bg.value}
|
|
116
|
-
onClick={() =>
|
|
117
|
-
onChange({ ...settings, previewBg: bg.value })
|
|
118
|
-
}
|
|
119
|
-
className="shrink-0 cursor-pointer"
|
|
120
|
-
title={bg.name}
|
|
121
|
-
style={{
|
|
122
|
-
width: 24,
|
|
123
|
-
height: 24,
|
|
124
|
-
borderRadius: 5,
|
|
125
|
-
background: bg.value,
|
|
126
|
-
border:
|
|
127
|
-
settings.previewBg === bg.value
|
|
128
|
-
? "2px solid var(--studio-accent)"
|
|
129
|
-
: "1px solid var(--studio-border)",
|
|
130
|
-
}}
|
|
131
|
-
/>
|
|
132
|
-
))}
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
|
|
136
|
-
{/* Borders */}
|
|
137
|
-
<div>
|
|
138
|
-
<div className="flex items-center justify-between mb-2">
|
|
139
|
-
<div
|
|
140
|
-
className="text-[9px] font-semibold uppercase tracking-wide"
|
|
141
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
142
|
-
>
|
|
143
|
-
Show card borders
|
|
144
|
-
</div>
|
|
145
|
-
<button
|
|
146
|
-
onClick={() =>
|
|
147
|
-
onChange({
|
|
148
|
-
...settings,
|
|
149
|
-
showBorder: !settings.showBorder,
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
className="shrink-0 cursor-pointer"
|
|
153
|
-
style={{
|
|
154
|
-
width: 34,
|
|
155
|
-
height: 20,
|
|
156
|
-
borderRadius: 10,
|
|
157
|
-
border: "1px solid var(--studio-border)",
|
|
158
|
-
background: settings.showBorder
|
|
159
|
-
? "var(--studio-accent)"
|
|
160
|
-
: "var(--studio-input-bg)",
|
|
161
|
-
position: "relative",
|
|
162
|
-
transition: "background 0.15s",
|
|
163
|
-
}}
|
|
164
|
-
>
|
|
165
|
-
<div
|
|
166
|
-
style={{
|
|
167
|
-
width: 14,
|
|
168
|
-
height: 14,
|
|
169
|
-
borderRadius: "50%",
|
|
170
|
-
background: "white",
|
|
171
|
-
position: "absolute",
|
|
172
|
-
top: 2,
|
|
173
|
-
left: settings.showBorder ? 16 : 2,
|
|
174
|
-
transition: "left 0.15s",
|
|
175
|
-
boxShadow: "0 1px 2px rgb(0 0 0 / 0.15)",
|
|
176
|
-
}}
|
|
177
|
-
/>
|
|
178
|
-
</button>
|
|
179
|
-
</div>
|
|
180
|
-
{settings.showBorder && (
|
|
181
|
-
<div className="flex gap-1.5">
|
|
182
|
-
{BORDER_COLORS.map((bc) => (
|
|
183
|
-
<button
|
|
184
|
-
key={bc.value}
|
|
185
|
-
onClick={() =>
|
|
186
|
-
onChange({ ...settings, borderColor: bc.value })
|
|
187
|
-
}
|
|
188
|
-
className="shrink-0 cursor-pointer"
|
|
189
|
-
title={bc.name}
|
|
190
|
-
style={{
|
|
191
|
-
width: 24,
|
|
192
|
-
height: 24,
|
|
193
|
-
borderRadius: 5,
|
|
194
|
-
background: bc.value,
|
|
195
|
-
border:
|
|
196
|
-
settings.borderColor === bc.value
|
|
197
|
-
? "2px solid var(--studio-accent)"
|
|
198
|
-
: "1px solid var(--studio-border)",
|
|
199
|
-
}}
|
|
200
|
-
/>
|
|
201
|
-
))}
|
|
202
|
-
</div>
|
|
203
|
-
)}
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
)}
|
|
208
|
-
</div>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
interface ShadowPreviewProps {
|
|
2
|
-
value: string;
|
|
3
|
-
size?: number;
|
|
4
|
-
shape?: "square" | "rounded" | "circle";
|
|
5
|
-
background?: string;
|
|
6
|
-
showBorder?: boolean;
|
|
7
|
-
borderColor?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function ShadowPreview({
|
|
11
|
-
value,
|
|
12
|
-
size = 32,
|
|
13
|
-
shape = "rounded",
|
|
14
|
-
background = "white",
|
|
15
|
-
showBorder = false,
|
|
16
|
-
borderColor = "#e0e0e0",
|
|
17
|
-
}: ShadowPreviewProps) {
|
|
18
|
-
const borderRadius =
|
|
19
|
-
shape === "circle" ? "50%" : shape === "rounded" ? "4px" : "0";
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<div
|
|
23
|
-
className="shrink-0"
|
|
24
|
-
style={{
|
|
25
|
-
width: size,
|
|
26
|
-
height: size,
|
|
27
|
-
borderRadius,
|
|
28
|
-
background,
|
|
29
|
-
boxShadow: value,
|
|
30
|
-
border: showBorder ? `1px solid ${borderColor}` : "none",
|
|
31
|
-
}}
|
|
32
|
-
/>
|
|
33
|
-
);
|
|
34
|
-
}
|