@designtools/shadows 0.1.5 → 0.1.6
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 +292 -193
- package/package.json +1 -1
- package/src/client/app.tsx +19 -3
- package/src/client/components/shadow-controls.tsx +9 -3
- package/src/client/components/shadow-editor-panel.tsx +31 -5
- package/src/client/components/shadow-list.tsx +95 -67
- package/src/client/components/shadow-overview.tsx +83 -78
- package/src/client/components/shadow-preview-settings.tsx +210 -0
- package/src/client/components/shadow-preview.tsx +5 -1
- package/src/client/dist/assets/index-CA2rtXvo.js +49 -0
- package/src/client/dist/index.html +1 -1
- package/src/client/dist/assets/index-DuqoHhIQ.js +0 -49
package/package.json
CHANGED
package/src/client/app.tsx
CHANGED
|
@@ -39,6 +39,9 @@ export function App() {
|
|
|
39
39
|
if (msg.type === "tool:elementSelected") {
|
|
40
40
|
setSelectedElement(msg.data);
|
|
41
41
|
}
|
|
42
|
+
if (msg.type === "tool:pathChanged") {
|
|
43
|
+
setIframePath(msg.path);
|
|
44
|
+
}
|
|
42
45
|
if (msg.type === "tool:injectedReady" && iframeRef.current) {
|
|
43
46
|
if (selectionMode) {
|
|
44
47
|
sendToIframe(iframeRef.current, {
|
|
@@ -70,8 +73,20 @@ export function App() {
|
|
|
70
73
|
}, [theme]);
|
|
71
74
|
|
|
72
75
|
const previewShadow = useCallback(
|
|
73
|
-
(variableName: string, value: string) => {
|
|
74
|
-
if (iframeRef.current)
|
|
76
|
+
(variableName: string, value: string, shadowName?: string) => {
|
|
77
|
+
if (!iframeRef.current) return;
|
|
78
|
+
|
|
79
|
+
// Tailwind v4 compiles shadow utilities (.shadow-sm) to use --tw-shadow
|
|
80
|
+
// internally, so setting --shadow-sm on :root has no effect.
|
|
81
|
+
// Instead, inject a <style> that overrides the utility class.
|
|
82
|
+
const isTailwind = scanData?.styling.type === "tailwind-v4";
|
|
83
|
+
if (isTailwind && shadowName) {
|
|
84
|
+
sendToIframe(iframeRef.current, {
|
|
85
|
+
type: "tool:previewShadow",
|
|
86
|
+
className: shadowName,
|
|
87
|
+
value,
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
75
90
|
sendToIframe(iframeRef.current, {
|
|
76
91
|
type: "tool:setProperty",
|
|
77
92
|
token: variableName,
|
|
@@ -79,13 +94,14 @@ export function App() {
|
|
|
79
94
|
});
|
|
80
95
|
}
|
|
81
96
|
},
|
|
82
|
-
[]
|
|
97
|
+
[scanData]
|
|
83
98
|
);
|
|
84
99
|
|
|
85
100
|
return (
|
|
86
101
|
<ToolChrome
|
|
87
102
|
toolName="Shadows"
|
|
88
103
|
toolIcon={<ShadowIcon style={{ width: 15, height: 15 }} />}
|
|
104
|
+
showSelectionMode={false}
|
|
89
105
|
selectionMode={selectionMode}
|
|
90
106
|
onToggleSelectionMode={() => setSelectionMode((s) => !s)}
|
|
91
107
|
theme={theme}
|
|
@@ -14,16 +14,20 @@ interface ShadowLayer {
|
|
|
14
14
|
inset: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
import type { PreviewSettings } from "./shadow-preview-settings.js";
|
|
18
|
+
|
|
17
19
|
interface ShadowControlsProps {
|
|
18
20
|
shadow: { value: string; layers: ShadowLayer[] };
|
|
19
21
|
onPreview: (value: string) => void;
|
|
20
22
|
onSave: (value: string) => void;
|
|
23
|
+
previewSettings: PreviewSettings;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export function ShadowControls({
|
|
24
27
|
shadow,
|
|
25
28
|
onPreview,
|
|
26
29
|
onSave,
|
|
30
|
+
previewSettings,
|
|
27
31
|
}: ShadowControlsProps) {
|
|
28
32
|
const [layers, setLayers] = useState<ShadowLayer[]>(() =>
|
|
29
33
|
shadow.layers.length > 0
|
|
@@ -73,10 +77,9 @@ export function ShadowControls({
|
|
|
73
77
|
<div className="flex flex-col gap-3">
|
|
74
78
|
{/* Preview */}
|
|
75
79
|
<div
|
|
76
|
-
className="flex items-center justify-center p-
|
|
80
|
+
className="flex items-center justify-center p-6 rounded-lg"
|
|
77
81
|
style={{
|
|
78
|
-
background:
|
|
79
|
-
border: "1px solid var(--studio-border-subtle)",
|
|
82
|
+
background: previewSettings.previewBg,
|
|
80
83
|
}}
|
|
81
84
|
>
|
|
82
85
|
<div
|
|
@@ -84,6 +87,9 @@ export function ShadowControls({
|
|
|
84
87
|
style={{
|
|
85
88
|
background: "white",
|
|
86
89
|
boxShadow: formatValue(layers),
|
|
90
|
+
border: previewSettings.showBorder
|
|
91
|
+
? `1px solid ${previewSettings.borderColor}`
|
|
92
|
+
: "none",
|
|
87
93
|
}}
|
|
88
94
|
/>
|
|
89
95
|
</div>
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import {
|
|
3
|
-
Cross2Icon,
|
|
4
3
|
ShadowIcon,
|
|
5
4
|
MixerHorizontalIcon,
|
|
6
5
|
GridIcon,
|
|
7
|
-
ChevronRightIcon,
|
|
8
|
-
ChevronDownIcon,
|
|
9
6
|
} from "@radix-ui/react-icons";
|
|
10
7
|
import type { ShadowsScanData } from "../app.js";
|
|
11
8
|
import type { ElementData } from "@designtools/core/client/lib/iframe-bridge";
|
|
12
9
|
import { ShadowList } from "./shadow-list.js";
|
|
13
10
|
import { ShadowOverview } from "./shadow-overview.js";
|
|
11
|
+
import {
|
|
12
|
+
ShadowPreviewSettingsButton,
|
|
13
|
+
DEFAULT_PREVIEW_SETTINGS,
|
|
14
|
+
type PreviewSettings,
|
|
15
|
+
} from "./shadow-preview-settings.js";
|
|
14
16
|
|
|
15
17
|
type ViewMode = "list" | "overview";
|
|
16
18
|
|
|
17
19
|
interface ShadowEditorPanelProps {
|
|
18
20
|
scanData: ShadowsScanData | null;
|
|
19
21
|
selectedElement: ElementData | null;
|
|
20
|
-
onPreviewShadow: (variableName: string, value: string) => void;
|
|
22
|
+
onPreviewShadow: (variableName: string, value: string, shadowName?: string) => void;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export function ShadowEditorPanel({
|
|
@@ -26,6 +28,9 @@ export function ShadowEditorPanel({
|
|
|
26
28
|
onPreviewShadow,
|
|
27
29
|
}: ShadowEditorPanelProps) {
|
|
28
30
|
const [viewMode, setViewMode] = useState<ViewMode>("list");
|
|
31
|
+
const [previewSettings, setPreviewSettings] = useState<PreviewSettings>(
|
|
32
|
+
DEFAULT_PREVIEW_SETTINGS
|
|
33
|
+
);
|
|
29
34
|
|
|
30
35
|
if (!scanData) {
|
|
31
36
|
return (
|
|
@@ -50,7 +55,7 @@ export function ShadowEditorPanel({
|
|
|
50
55
|
|
|
51
56
|
const modeConfig: Record<ViewMode, { icon: any; label: string }> = {
|
|
52
57
|
list: { icon: MixerHorizontalIcon, label: "Edit" },
|
|
53
|
-
overview: { icon: GridIcon, label: "
|
|
58
|
+
overview: { icon: GridIcon, label: "Preview" },
|
|
54
59
|
};
|
|
55
60
|
|
|
56
61
|
return (
|
|
@@ -91,6 +96,10 @@ export function ShadowEditorPanel({
|
|
|
91
96
|
{scanData.shadows.shadows.length !== 1 ? "s" : ""} found
|
|
92
97
|
</div>
|
|
93
98
|
</div>
|
|
99
|
+
<ShadowPreviewSettingsButton
|
|
100
|
+
settings={previewSettings}
|
|
101
|
+
onChange={setPreviewSettings}
|
|
102
|
+
/>
|
|
94
103
|
</div>
|
|
95
104
|
|
|
96
105
|
{/* View mode switcher */}
|
|
@@ -116,6 +125,21 @@ export function ShadowEditorPanel({
|
|
|
116
125
|
</div>
|
|
117
126
|
</div>
|
|
118
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
|
+
|
|
119
143
|
{/* Content */}
|
|
120
144
|
<div className="flex-1 overflow-y-auto studio-scrollbar">
|
|
121
145
|
{viewMode === "list" && (
|
|
@@ -124,11 +148,13 @@ export function ShadowEditorPanel({
|
|
|
124
148
|
cssFilePath={scanData.shadows.cssFilePath}
|
|
125
149
|
stylingType={scanData.shadows.stylingType}
|
|
126
150
|
onPreviewShadow={onPreviewShadow}
|
|
151
|
+
previewSettings={previewSettings}
|
|
127
152
|
/>
|
|
128
153
|
)}
|
|
129
154
|
{viewMode === "overview" && (
|
|
130
155
|
<ShadowOverview
|
|
131
156
|
shadows={scanData.shadows.shadows}
|
|
157
|
+
previewSettings={previewSettings}
|
|
132
158
|
/>
|
|
133
159
|
)}
|
|
134
160
|
</div>
|
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
} from "@radix-ui/react-icons";
|
|
9
9
|
import { ShadowControls } from "./shadow-controls.js";
|
|
10
10
|
import { ShadowPreview } from "./shadow-preview.js";
|
|
11
|
+
import type { PreviewSettings } from "./shadow-preview-settings.js";
|
|
11
12
|
|
|
12
13
|
interface ShadowListProps {
|
|
13
14
|
shadows: any[];
|
|
14
15
|
cssFilePath: string;
|
|
15
16
|
stylingType: string;
|
|
16
|
-
onPreviewShadow: (variableName: string, value: string) => void;
|
|
17
|
+
onPreviewShadow: (variableName: string, value: string, shadowName?: string) => void;
|
|
18
|
+
previewSettings: PreviewSettings;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export function ShadowList({
|
|
@@ -21,6 +23,7 @@ export function ShadowList({
|
|
|
21
23
|
cssFilePath,
|
|
22
24
|
stylingType,
|
|
23
25
|
onPreviewShadow,
|
|
26
|
+
previewSettings,
|
|
24
27
|
}: ShadowListProps) {
|
|
25
28
|
const [expandedShadow, setExpandedShadow] = useState<string | null>(null);
|
|
26
29
|
const [editMode, setEditMode] = useState<Record<string, "custom" | "preset">>({});
|
|
@@ -44,6 +47,7 @@ export function ShadowList({
|
|
|
44
47
|
cssFilePath={cssFilePath}
|
|
45
48
|
stylingType={stylingType}
|
|
46
49
|
onPreviewShadow={onPreviewShadow}
|
|
50
|
+
previewSettings={previewSettings}
|
|
47
51
|
/>
|
|
48
52
|
))}
|
|
49
53
|
|
|
@@ -68,6 +72,7 @@ function ShadowRow({
|
|
|
68
72
|
cssFilePath,
|
|
69
73
|
stylingType,
|
|
70
74
|
onPreviewShadow,
|
|
75
|
+
previewSettings,
|
|
71
76
|
}: {
|
|
72
77
|
shadow: any;
|
|
73
78
|
isExpanded: boolean;
|
|
@@ -76,11 +81,14 @@ function ShadowRow({
|
|
|
76
81
|
onModeChange: (mode: "custom" | "preset") => void;
|
|
77
82
|
cssFilePath: string;
|
|
78
83
|
stylingType: string;
|
|
79
|
-
onPreviewShadow: (variableName: string, value: string) => void;
|
|
84
|
+
onPreviewShadow: (variableName: string, value: string, shadowName?: string) => void;
|
|
85
|
+
previewSettings: PreviewSettings;
|
|
80
86
|
}) {
|
|
81
87
|
const [saving, setSaving] = useState(false);
|
|
88
|
+
const [currentValue, setCurrentValue] = useState(shadow.value);
|
|
82
89
|
|
|
83
90
|
const handleSave = async (newValue: string) => {
|
|
91
|
+
setCurrentValue(newValue);
|
|
84
92
|
setSaving(true);
|
|
85
93
|
try {
|
|
86
94
|
// Design token shadows write to their own endpoint
|
|
@@ -140,7 +148,12 @@ function ShadowRow({
|
|
|
140
148
|
style={{ gap: 8 }}
|
|
141
149
|
>
|
|
142
150
|
{isExpanded ? <ChevronDownIcon /> : <ChevronRightIcon />}
|
|
143
|
-
<ShadowPreview
|
|
151
|
+
<ShadowPreview
|
|
152
|
+
value={currentValue}
|
|
153
|
+
size={24}
|
|
154
|
+
showBorder={previewSettings.showBorder}
|
|
155
|
+
borderColor={previewSettings.borderColor}
|
|
156
|
+
/>
|
|
144
157
|
<span
|
|
145
158
|
className="flex-1 text-[11px] font-mono truncate text-left"
|
|
146
159
|
style={{
|
|
@@ -208,21 +221,23 @@ function ShadowRow({
|
|
|
208
221
|
|
|
209
222
|
{mode === "custom" ? (
|
|
210
223
|
<ShadowControls
|
|
211
|
-
shadow={shadow}
|
|
224
|
+
shadow={{ ...shadow, value: currentValue }}
|
|
212
225
|
onPreview={(value) => {
|
|
213
226
|
const varName = shadow.cssVariable || `--${shadow.name}`;
|
|
214
|
-
onPreviewShadow(varName, value);
|
|
227
|
+
onPreviewShadow(varName, value, shadow.name);
|
|
215
228
|
}}
|
|
216
229
|
onSave={handleSave}
|
|
230
|
+
previewSettings={previewSettings}
|
|
217
231
|
/>
|
|
218
232
|
) : (
|
|
219
233
|
<PresetPicker
|
|
220
|
-
currentValue={
|
|
234
|
+
currentValue={currentValue}
|
|
221
235
|
onSelect={(value) => {
|
|
222
236
|
const varName = shadow.cssVariable || `--${shadow.name}`;
|
|
223
|
-
onPreviewShadow(varName, value);
|
|
237
|
+
onPreviewShadow(varName, value, shadow.name);
|
|
224
238
|
handleSave(value);
|
|
225
239
|
}}
|
|
240
|
+
previewSettings={previewSettings}
|
|
226
241
|
/>
|
|
227
242
|
)}
|
|
228
243
|
</div>
|
|
@@ -234,80 +249,93 @@ function ShadowRow({
|
|
|
234
249
|
function PresetPicker({
|
|
235
250
|
currentValue,
|
|
236
251
|
onSelect,
|
|
252
|
+
previewSettings,
|
|
237
253
|
}: {
|
|
238
254
|
currentValue: string;
|
|
239
255
|
onSelect: (value: string) => void;
|
|
256
|
+
previewSettings: PreviewSettings;
|
|
240
257
|
}) {
|
|
241
|
-
//
|
|
258
|
+
// Presets from shadow-explorer: multi-layer approaches at different depths
|
|
242
259
|
const presets = [
|
|
243
|
-
|
|
244
|
-
{ name: "
|
|
245
|
-
{ name: "
|
|
246
|
-
|
|
247
|
-
{ name: "
|
|
248
|
-
{ name: "
|
|
249
|
-
|
|
250
|
-
{ name: "
|
|
251
|
-
{ name: "
|
|
252
|
-
|
|
253
|
-
{ name: "
|
|
254
|
-
{ name: "
|
|
255
|
-
|
|
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" },
|
|
256
283
|
];
|
|
257
284
|
|
|
258
|
-
const categories = ["subtle", "medium", "dramatic", "layered", "colored", "reset"];
|
|
259
|
-
const categoryLabels: Record<string, string> = {
|
|
260
|
-
subtle: "Subtle",
|
|
261
|
-
medium: "Medium",
|
|
262
|
-
dramatic: "Dramatic",
|
|
263
|
-
layered: "Layered",
|
|
264
|
-
colored: "Colored",
|
|
265
|
-
reset: "Reset",
|
|
266
|
-
};
|
|
267
|
-
|
|
268
285
|
return (
|
|
269
|
-
<div
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
286
|
+
<div
|
|
287
|
+
className="grid gap-2"
|
|
288
|
+
style={{ gridTemplateColumns: "repeat(2, 1fr)" }}
|
|
289
|
+
>
|
|
290
|
+
{presets.map((preset) => {
|
|
291
|
+
const isActive = currentValue === preset.value;
|
|
273
292
|
return (
|
|
274
|
-
<
|
|
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
|
+
>
|
|
275
307
|
<div
|
|
276
|
-
className="
|
|
277
|
-
style={{
|
|
308
|
+
className="flex items-center justify-center rounded-lg w-full"
|
|
309
|
+
style={{
|
|
310
|
+
background: previewSettings.previewBg,
|
|
311
|
+
height: 72,
|
|
312
|
+
}}
|
|
278
313
|
>
|
|
279
|
-
|
|
314
|
+
<ShadowPreview
|
|
315
|
+
value={preset.value}
|
|
316
|
+
size={48}
|
|
317
|
+
background="white"
|
|
318
|
+
showBorder={previewSettings.showBorder}
|
|
319
|
+
borderColor={previewSettings.borderColor}
|
|
320
|
+
/>
|
|
280
321
|
</div>
|
|
281
|
-
<div className="
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
border: "1px solid",
|
|
293
|
-
borderColor:
|
|
294
|
-
currentValue === preset.value
|
|
295
|
-
? "var(--studio-accent)"
|
|
296
|
-
: "var(--studio-border-subtle)",
|
|
297
|
-
cursor: "pointer",
|
|
298
|
-
}}
|
|
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)" }}
|
|
299
333
|
>
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
style={{ color: "var(--studio-text)" }}
|
|
304
|
-
>
|
|
305
|
-
{preset.name}
|
|
306
|
-
</span>
|
|
307
|
-
</button>
|
|
308
|
-
))}
|
|
334
|
+
{preset.desc}
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
309
337
|
</div>
|
|
310
|
-
</
|
|
338
|
+
</button>
|
|
311
339
|
);
|
|
312
340
|
})}
|
|
313
341
|
</div>
|
|
@@ -1,97 +1,102 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
2
|
import { ShadowPreview } from "./shadow-preview.js";
|
|
3
|
+
import type { PreviewSettings } from "./shadow-preview-settings.js";
|
|
3
4
|
|
|
4
5
|
interface ShadowOverviewProps {
|
|
5
6
|
shadows: any[];
|
|
7
|
+
previewSettings: PreviewSettings;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
{ name: "White", value: "white" },
|
|
10
|
-
{ name: "Light", value: "#f5f5f5" },
|
|
11
|
-
{ name: "Dark", value: "#1a1a2e" },
|
|
12
|
-
{ name: "Blue", value: "#e0f2fe" },
|
|
13
|
-
{ name: "Warm", value: "#fef3c7" },
|
|
14
|
-
];
|
|
10
|
+
type OverviewSize = "small" | "large";
|
|
15
11
|
|
|
16
|
-
export function ShadowOverview({ shadows }: ShadowOverviewProps) {
|
|
17
|
-
const [
|
|
18
|
-
const [previewSize, setPreviewSize] = useState(64);
|
|
12
|
+
export function ShadowOverview({ shadows, previewSettings }: ShadowOverviewProps) {
|
|
13
|
+
const [size, setSize] = useState<OverviewSize>("small");
|
|
19
14
|
|
|
20
15
|
return (
|
|
21
16
|
<div>
|
|
22
|
-
{/*
|
|
23
|
-
<div
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
background: bg.value,
|
|
42
|
-
border:
|
|
43
|
-
activeBg === bg.value
|
|
44
|
-
? "2px solid var(--studio-accent)"
|
|
45
|
-
: "1px solid var(--studio-border)",
|
|
46
|
-
}}
|
|
47
|
-
/>
|
|
48
|
-
))}
|
|
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>
|
|
49
36
|
</div>
|
|
50
|
-
<div className="flex-1" />
|
|
51
|
-
<select
|
|
52
|
-
value={previewSize}
|
|
53
|
-
onChange={(e) => setPreviewSize(parseInt(e.target.value))}
|
|
54
|
-
className="studio-select"
|
|
55
|
-
style={{ fontSize: 10, width: 50 }}
|
|
56
|
-
>
|
|
57
|
-
<option value={48}>S</option>
|
|
58
|
-
<option value={64}>M</option>
|
|
59
|
-
<option value={96}>L</option>
|
|
60
|
-
</select>
|
|
61
37
|
</div>
|
|
62
38
|
|
|
63
39
|
{/* Shadow grid */}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
gridTemplateColumns:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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>
|
|
85
68
|
</div>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
)}
|
|
95
100
|
|
|
96
101
|
{shadows.length === 0 && (
|
|
97
102
|
<div
|