@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,287 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useCallback } from "react";
|
|
2
|
-
import {
|
|
3
|
-
PlusIcon,
|
|
4
|
-
TrashIcon,
|
|
5
|
-
MoveIcon,
|
|
6
|
-
} from "@radix-ui/react-icons";
|
|
7
|
-
|
|
8
|
-
interface ShadowLayer {
|
|
9
|
-
offsetX: string;
|
|
10
|
-
offsetY: string;
|
|
11
|
-
blur: string;
|
|
12
|
-
spread: string;
|
|
13
|
-
color: string;
|
|
14
|
-
inset: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
import type { PreviewSettings } from "./shadow-preview-settings.js";
|
|
18
|
-
|
|
19
|
-
interface ShadowControlsProps {
|
|
20
|
-
shadow: { value: string; layers: ShadowLayer[] };
|
|
21
|
-
onPreview: (value: string) => void;
|
|
22
|
-
onSave: (value: string) => void;
|
|
23
|
-
previewSettings: PreviewSettings;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function ShadowControls({
|
|
27
|
-
shadow,
|
|
28
|
-
onPreview,
|
|
29
|
-
onSave,
|
|
30
|
-
previewSettings,
|
|
31
|
-
}: ShadowControlsProps) {
|
|
32
|
-
const [layers, setLayers] = useState<ShadowLayer[]>(() =>
|
|
33
|
-
shadow.layers.length > 0
|
|
34
|
-
? shadow.layers
|
|
35
|
-
: [{ offsetX: "0", offsetY: "4px", blur: "6px", spread: "-1px", color: "rgb(0 0 0 / 0.1)", inset: false }]
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const formatValue = useCallback((layers: ShadowLayer[]) => {
|
|
39
|
-
if (layers.length === 0) return "none";
|
|
40
|
-
return layers
|
|
41
|
-
.map((l) => {
|
|
42
|
-
const parts: string[] = [];
|
|
43
|
-
if (l.inset) parts.push("inset");
|
|
44
|
-
parts.push(l.offsetX, l.offsetY, l.blur, l.spread, l.color);
|
|
45
|
-
return parts.join(" ");
|
|
46
|
-
})
|
|
47
|
-
.join(", ");
|
|
48
|
-
}, []);
|
|
49
|
-
|
|
50
|
-
const updateLayer = useCallback(
|
|
51
|
-
(index: number, update: Partial<ShadowLayer>) => {
|
|
52
|
-
const newLayers = layers.map((l, i) =>
|
|
53
|
-
i === index ? { ...l, ...update } : l
|
|
54
|
-
);
|
|
55
|
-
setLayers(newLayers);
|
|
56
|
-
onPreview(formatValue(newLayers));
|
|
57
|
-
},
|
|
58
|
-
[layers, onPreview, formatValue]
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
const addLayer = () => {
|
|
62
|
-
const newLayers = [
|
|
63
|
-
...layers,
|
|
64
|
-
{ offsetX: "0", offsetY: "4px", blur: "8px", spread: "0", color: "rgb(0 0 0 / 0.1)", inset: false },
|
|
65
|
-
];
|
|
66
|
-
setLayers(newLayers);
|
|
67
|
-
onPreview(formatValue(newLayers));
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const removeLayer = (index: number) => {
|
|
71
|
-
const newLayers = layers.filter((_, i) => i !== index);
|
|
72
|
-
setLayers(newLayers);
|
|
73
|
-
onPreview(formatValue(newLayers));
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<div className="flex flex-col gap-3">
|
|
78
|
-
{/* Preview */}
|
|
79
|
-
<div
|
|
80
|
-
className="flex items-center justify-center p-6 rounded-lg"
|
|
81
|
-
style={{
|
|
82
|
-
background: previewSettings.previewBg,
|
|
83
|
-
}}
|
|
84
|
-
>
|
|
85
|
-
<div
|
|
86
|
-
className="w-20 h-20 rounded-lg"
|
|
87
|
-
style={{
|
|
88
|
-
background: "white",
|
|
89
|
-
boxShadow: formatValue(layers),
|
|
90
|
-
border: previewSettings.showBorder
|
|
91
|
-
? `1px solid ${previewSettings.borderColor}`
|
|
92
|
-
: "none",
|
|
93
|
-
}}
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
96
|
-
|
|
97
|
-
{/* Layers */}
|
|
98
|
-
{layers.map((layer, i) => (
|
|
99
|
-
<LayerEditor
|
|
100
|
-
key={i}
|
|
101
|
-
layer={layer}
|
|
102
|
-
index={i}
|
|
103
|
-
total={layers.length}
|
|
104
|
-
onChange={(update) => updateLayer(i, update)}
|
|
105
|
-
onRemove={() => removeLayer(i)}
|
|
106
|
-
/>
|
|
107
|
-
))}
|
|
108
|
-
|
|
109
|
-
{/* Add layer button */}
|
|
110
|
-
<button
|
|
111
|
-
onClick={addLayer}
|
|
112
|
-
className="flex items-center justify-center gap-1 py-1.5 rounded text-[11px] cursor-pointer"
|
|
113
|
-
style={{
|
|
114
|
-
background: "var(--studio-input-bg)",
|
|
115
|
-
border: "1px solid var(--studio-border-subtle)",
|
|
116
|
-
color: "var(--studio-text-muted)",
|
|
117
|
-
}}
|
|
118
|
-
>
|
|
119
|
-
<PlusIcon style={{ width: 12, height: 12 }} />
|
|
120
|
-
Add Layer
|
|
121
|
-
</button>
|
|
122
|
-
|
|
123
|
-
{/* Save */}
|
|
124
|
-
<button
|
|
125
|
-
onClick={() => onSave(formatValue(layers))}
|
|
126
|
-
className="w-full py-1.5 rounded text-[11px] font-medium cursor-pointer"
|
|
127
|
-
style={{
|
|
128
|
-
background: "var(--studio-accent)",
|
|
129
|
-
color: "white",
|
|
130
|
-
border: "none",
|
|
131
|
-
}}
|
|
132
|
-
>
|
|
133
|
-
Save to File
|
|
134
|
-
</button>
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function LayerEditor({
|
|
140
|
-
layer,
|
|
141
|
-
index,
|
|
142
|
-
total,
|
|
143
|
-
onChange,
|
|
144
|
-
onRemove,
|
|
145
|
-
}: {
|
|
146
|
-
layer: ShadowLayer;
|
|
147
|
-
index: number;
|
|
148
|
-
total: number;
|
|
149
|
-
onChange: (update: Partial<ShadowLayer>) => void;
|
|
150
|
-
onRemove: () => void;
|
|
151
|
-
}) {
|
|
152
|
-
const parseNum = (v: string): number => parseFloat(v) || 0;
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<div
|
|
156
|
-
className="rounded-lg p-2.5"
|
|
157
|
-
style={{
|
|
158
|
-
background: "var(--studio-input-bg)",
|
|
159
|
-
border: "1px solid var(--studio-border-subtle)",
|
|
160
|
-
}}
|
|
161
|
-
>
|
|
162
|
-
{/* Layer header */}
|
|
163
|
-
<div className="flex items-center justify-between mb-2">
|
|
164
|
-
<span
|
|
165
|
-
className="text-[9px] font-semibold uppercase tracking-wide"
|
|
166
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
167
|
-
>
|
|
168
|
-
Layer {index + 1}
|
|
169
|
-
</span>
|
|
170
|
-
<div className="flex items-center gap-1">
|
|
171
|
-
<button
|
|
172
|
-
onClick={() => onChange({ inset: !layer.inset })}
|
|
173
|
-
className={`studio-bp-btn ${layer.inset ? "active" : ""}`}
|
|
174
|
-
style={{ fontSize: 9, padding: "1px 5px" }}
|
|
175
|
-
>
|
|
176
|
-
inset
|
|
177
|
-
</button>
|
|
178
|
-
{total > 1 && (
|
|
179
|
-
<button
|
|
180
|
-
onClick={onRemove}
|
|
181
|
-
className="studio-icon-btn"
|
|
182
|
-
style={{ width: 20, height: 20 }}
|
|
183
|
-
>
|
|
184
|
-
<TrashIcon style={{ width: 10, height: 10 }} />
|
|
185
|
-
</button>
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
{/* Controls grid */}
|
|
191
|
-
<div className="grid grid-cols-2 gap-x-2 gap-y-1.5">
|
|
192
|
-
<SliderField
|
|
193
|
-
label="X Offset"
|
|
194
|
-
value={parseNum(layer.offsetX)}
|
|
195
|
-
min={-50}
|
|
196
|
-
max={50}
|
|
197
|
-
unit="px"
|
|
198
|
-
onChange={(v) => onChange({ offsetX: `${v}px` })}
|
|
199
|
-
/>
|
|
200
|
-
<SliderField
|
|
201
|
-
label="Y Offset"
|
|
202
|
-
value={parseNum(layer.offsetY)}
|
|
203
|
-
min={-50}
|
|
204
|
-
max={50}
|
|
205
|
-
unit="px"
|
|
206
|
-
onChange={(v) => onChange({ offsetY: `${v}px` })}
|
|
207
|
-
/>
|
|
208
|
-
<SliderField
|
|
209
|
-
label="Blur"
|
|
210
|
-
value={parseNum(layer.blur)}
|
|
211
|
-
min={0}
|
|
212
|
-
max={100}
|
|
213
|
-
unit="px"
|
|
214
|
-
onChange={(v) => onChange({ blur: `${v}px` })}
|
|
215
|
-
/>
|
|
216
|
-
<SliderField
|
|
217
|
-
label="Spread"
|
|
218
|
-
value={parseNum(layer.spread)}
|
|
219
|
-
min={-50}
|
|
220
|
-
max={50}
|
|
221
|
-
unit="px"
|
|
222
|
-
onChange={(v) => onChange({ spread: `${v}px` })}
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
{/* Color */}
|
|
227
|
-
<div className="mt-1.5">
|
|
228
|
-
<div
|
|
229
|
-
className="text-[9px] font-medium mb-0.5"
|
|
230
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
231
|
-
>
|
|
232
|
-
Color
|
|
233
|
-
</div>
|
|
234
|
-
<input
|
|
235
|
-
type="text"
|
|
236
|
-
value={layer.color}
|
|
237
|
-
onChange={(e) => onChange({ color: e.target.value })}
|
|
238
|
-
className="studio-input w-full"
|
|
239
|
-
style={{ fontSize: 10 }}
|
|
240
|
-
/>
|
|
241
|
-
</div>
|
|
242
|
-
</div>
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function SliderField({
|
|
247
|
-
label,
|
|
248
|
-
value,
|
|
249
|
-
min,
|
|
250
|
-
max,
|
|
251
|
-
unit,
|
|
252
|
-
onChange,
|
|
253
|
-
}: {
|
|
254
|
-
label: string;
|
|
255
|
-
value: number;
|
|
256
|
-
min: number;
|
|
257
|
-
max: number;
|
|
258
|
-
unit: string;
|
|
259
|
-
onChange: (v: number) => void;
|
|
260
|
-
}) {
|
|
261
|
-
return (
|
|
262
|
-
<div>
|
|
263
|
-
<div className="flex items-center justify-between">
|
|
264
|
-
<span
|
|
265
|
-
className="text-[9px] font-medium"
|
|
266
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
267
|
-
>
|
|
268
|
-
{label}
|
|
269
|
-
</span>
|
|
270
|
-
<span
|
|
271
|
-
className="text-[9px] font-mono"
|
|
272
|
-
style={{ color: "var(--studio-text-muted)" }}
|
|
273
|
-
>
|
|
274
|
-
{value}{unit}
|
|
275
|
-
</span>
|
|
276
|
-
</div>
|
|
277
|
-
<input
|
|
278
|
-
type="range"
|
|
279
|
-
min={min}
|
|
280
|
-
max={max}
|
|
281
|
-
value={value}
|
|
282
|
-
onChange={(e) => onChange(parseInt(e.target.value, 10))}
|
|
283
|
-
className="w-full"
|
|
284
|
-
/>
|
|
285
|
-
</div>
|
|
286
|
-
);
|
|
287
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import {
|
|
3
|
-
ShadowIcon,
|
|
4
|
-
MixerHorizontalIcon,
|
|
5
|
-
GridIcon,
|
|
6
|
-
} from "@radix-ui/react-icons";
|
|
7
|
-
import type { ShadowsScanData } from "../app.js";
|
|
8
|
-
import type { ElementData } from "@designtools/core/client/lib/iframe-bridge";
|
|
9
|
-
import { ShadowList } from "./shadow-list.js";
|
|
10
|
-
import { ShadowOverview } from "./shadow-overview.js";
|
|
11
|
-
import {
|
|
12
|
-
ShadowPreviewSettingsButton,
|
|
13
|
-
DEFAULT_PREVIEW_SETTINGS,
|
|
14
|
-
type PreviewSettings,
|
|
15
|
-
} from "./shadow-preview-settings.js";
|
|
16
|
-
|
|
17
|
-
type ViewMode = "list" | "overview";
|
|
18
|
-
|
|
19
|
-
interface ShadowEditorPanelProps {
|
|
20
|
-
scanData: ShadowsScanData | null;
|
|
21
|
-
selectedElement: ElementData | null;
|
|
22
|
-
onPreviewShadow: (variableName: string, value: string, shadowName?: string) => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function ShadowEditorPanel({
|
|
26
|
-
scanData,
|
|
27
|
-
selectedElement,
|
|
28
|
-
onPreviewShadow,
|
|
29
|
-
}: ShadowEditorPanelProps) {
|
|
30
|
-
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
|
31
|
-
const [previewSettings, setPreviewSettings] = useState<PreviewSettings>(
|
|
32
|
-
DEFAULT_PREVIEW_SETTINGS
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
if (!scanData) {
|
|
36
|
-
return (
|
|
37
|
-
<div
|
|
38
|
-
className="flex flex-col border-l studio-scrollbar"
|
|
39
|
-
style={{
|
|
40
|
-
width: 340,
|
|
41
|
-
minWidth: 340,
|
|
42
|
-
background: "var(--studio-surface)",
|
|
43
|
-
borderColor: "var(--studio-border)",
|
|
44
|
-
}}
|
|
45
|
-
>
|
|
46
|
-
<div
|
|
47
|
-
className="px-4 py-3 text-[11px]"
|
|
48
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
49
|
-
>
|
|
50
|
-
Scanning project...
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const modeConfig: Record<ViewMode, { icon: any; label: string }> = {
|
|
57
|
-
list: { icon: MixerHorizontalIcon, label: "Edit" },
|
|
58
|
-
overview: { icon: GridIcon, label: "Preview" },
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div
|
|
63
|
-
className="flex flex-col border-l studio-scrollbar overflow-y-auto"
|
|
64
|
-
style={{
|
|
65
|
-
width: 340,
|
|
66
|
-
minWidth: 340,
|
|
67
|
-
background: "var(--studio-surface)",
|
|
68
|
-
borderColor: "var(--studio-border)",
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
71
|
-
{/* Header */}
|
|
72
|
-
<div
|
|
73
|
-
className="flex items-center gap-2 px-4 py-3 border-b shrink-0"
|
|
74
|
-
style={{ borderColor: "var(--studio-border)" }}
|
|
75
|
-
>
|
|
76
|
-
<div
|
|
77
|
-
className="w-5 h-5 rounded flex items-center justify-center shrink-0"
|
|
78
|
-
style={{ background: "var(--studio-accent-muted)" }}
|
|
79
|
-
>
|
|
80
|
-
<ShadowIcon
|
|
81
|
-
style={{ width: 12, height: 12, color: "var(--studio-accent)" }}
|
|
82
|
-
/>
|
|
83
|
-
</div>
|
|
84
|
-
<div className="flex-1 min-w-0">
|
|
85
|
-
<span
|
|
86
|
-
className="text-[12px] font-semibold"
|
|
87
|
-
style={{ color: "var(--studio-text)" }}
|
|
88
|
-
>
|
|
89
|
-
Shadows
|
|
90
|
-
</span>
|
|
91
|
-
<div
|
|
92
|
-
className="text-[10px]"
|
|
93
|
-
style={{ color: "var(--studio-text-dimmed)" }}
|
|
94
|
-
>
|
|
95
|
-
{scanData.shadows.shadows.length} shadow
|
|
96
|
-
{scanData.shadows.shadows.length !== 1 ? "s" : ""} found
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
<ShadowPreviewSettingsButton
|
|
100
|
-
settings={previewSettings}
|
|
101
|
-
onChange={setPreviewSettings}
|
|
102
|
-
/>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
{/* View mode switcher */}
|
|
106
|
-
<div
|
|
107
|
-
className="px-4 py-2.5 border-b shrink-0"
|
|
108
|
-
style={{ borderColor: "var(--studio-border)" }}
|
|
109
|
-
>
|
|
110
|
-
<div className="studio-segmented" style={{ width: "100%" }}>
|
|
111
|
-
{(Object.keys(modeConfig) as ViewMode[]).map((mode) => {
|
|
112
|
-
const cfg = modeConfig[mode];
|
|
113
|
-
return (
|
|
114
|
-
<button
|
|
115
|
-
key={mode}
|
|
116
|
-
onClick={() => setViewMode(mode)}
|
|
117
|
-
className={viewMode === mode ? "active" : ""}
|
|
118
|
-
style={{ flex: 1 }}
|
|
119
|
-
>
|
|
120
|
-
<cfg.icon style={{ width: 12, height: 12 }} />
|
|
121
|
-
{cfg.label}
|
|
122
|
-
</button>
|
|
123
|
-
);
|
|
124
|
-
})}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{/* No CSS file warning */}
|
|
129
|
-
{!scanData.shadows.cssFilePath && (
|
|
130
|
-
<div
|
|
131
|
-
className="mx-4 mt-2.5 mb-0 px-3 py-2 rounded-md text-[10px] shrink-0"
|
|
132
|
-
style={{
|
|
133
|
-
background: "rgb(255 243 205)",
|
|
134
|
-
color: "rgb(133 100 4)",
|
|
135
|
-
border: "1px solid rgb(255 224 130)",
|
|
136
|
-
lineHeight: 1.4,
|
|
137
|
-
}}
|
|
138
|
-
>
|
|
139
|
-
No CSS file found. Run this tool from your app directory to enable editing.
|
|
140
|
-
</div>
|
|
141
|
-
)}
|
|
142
|
-
|
|
143
|
-
{/* Content */}
|
|
144
|
-
<div className="flex-1 overflow-y-auto studio-scrollbar">
|
|
145
|
-
{viewMode === "list" && (
|
|
146
|
-
<ShadowList
|
|
147
|
-
shadows={scanData.shadows.shadows}
|
|
148
|
-
cssFilePath={scanData.shadows.cssFilePath}
|
|
149
|
-
stylingType={scanData.shadows.stylingType}
|
|
150
|
-
onPreviewShadow={onPreviewShadow}
|
|
151
|
-
previewSettings={previewSettings}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
154
|
-
{viewMode === "overview" && (
|
|
155
|
-
<ShadowOverview
|
|
156
|
-
shadows={scanData.shadows.shadows}
|
|
157
|
-
previewSettings={previewSettings}
|
|
158
|
-
/>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
);
|
|
163
|
-
}
|