@designtools/shadows 0.1.4 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@designtools/shadows",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Visual shadow editing CLI — scan, preview, and edit box-shadow values in your project",
5
5
  "type": "module",
6
6
  "license": "CC-BY-NC-4.0",
@@ -16,7 +16,6 @@ export interface ShadowsScanData {
16
16
  cssFilePath: string;
17
17
  stylingType: string;
18
18
  };
19
- routes: { routes: { urlPath: string; filePath: string }[] };
20
19
  }
21
20
 
22
21
  export function App() {
@@ -40,6 +39,9 @@ export function App() {
40
39
  if (msg.type === "tool:elementSelected") {
41
40
  setSelectedElement(msg.data);
42
41
  }
42
+ if (msg.type === "tool:pathChanged") {
43
+ setIframePath(msg.path);
44
+ }
43
45
  if (msg.type === "tool:injectedReady" && iframeRef.current) {
44
46
  if (selectionMode) {
45
47
  sendToIframe(iframeRef.current, {
@@ -71,8 +73,20 @@ export function App() {
71
73
  }, [theme]);
72
74
 
73
75
  const previewShadow = useCallback(
74
- (variableName: string, value: string) => {
75
- 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 {
76
90
  sendToIframe(iframeRef.current, {
77
91
  type: "tool:setProperty",
78
92
  token: variableName,
@@ -80,14 +94,14 @@ export function App() {
80
94
  });
81
95
  }
82
96
  },
83
- []
97
+ [scanData]
84
98
  );
85
99
 
86
100
  return (
87
101
  <ToolChrome
88
102
  toolName="Shadows"
89
103
  toolIcon={<ShadowIcon style={{ width: 15, height: 15 }} />}
90
- routes={scanData?.routes.routes || []}
104
+ showSelectionMode={false}
91
105
  selectionMode={selectionMode}
92
106
  onToggleSelectionMode={() => setSelectionMode((s) => !s)}
93
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-4 rounded-lg"
80
+ className="flex items-center justify-center p-6 rounded-lg"
77
81
  style={{
78
- background: "var(--studio-bg)",
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: "Overview" },
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 value={shadow.value} size={24} />
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={shadow.value}
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
- // Import presets inline to avoid circular deps
258
+ // Presets from shadow-explorer: multi-layer approaches at different depths
242
259
  const presets = [
243
- { name: "Soft Small", value: "0 1px 2px 0 rgb(0 0 0 / 0.03), 0 1px 3px 0 rgb(0 0 0 / 0.06)", category: "subtle" },
244
- { name: "Soft Medium", value: "0 2px 8px -2px rgb(0 0 0 / 0.05), 0 4px 12px -2px rgb(0 0 0 / 0.08)", category: "subtle" },
245
- { name: "Soft Large", value: "0 4px 16px -4px rgb(0 0 0 / 0.08), 0 8px 24px -4px rgb(0 0 0 / 0.1)", category: "subtle" },
246
- { name: "Card", value: "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)", category: "medium" },
247
- { name: "Dropdown", value: "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", category: "medium" },
248
- { name: "Modal", value: "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)", category: "medium" },
249
- { name: "Elevated", value: "0 25px 50px -12px rgb(0 0 0 / 0.25)", category: "dramatic" },
250
- { name: "Floating", value: "0 20px 60px -15px rgb(0 0 0 / 0.3)", category: "dramatic" },
251
- { name: "Layered Small", value: "0 1px 1px rgb(0 0 0 / 0.04), 0 2px 2px rgb(0 0 0 / 0.04), 0 4px 4px rgb(0 0 0 / 0.04)", category: "layered" },
252
- { name: "Layered Medium", value: "0 1px 1px rgb(0 0 0 / 0.03), 0 2px 2px rgb(0 0 0 / 0.03), 0 4px 4px rgb(0 0 0 / 0.03), 0 8px 8px rgb(0 0 0 / 0.03), 0 16px 16px rgb(0 0 0 / 0.03)", category: "layered" },
253
- { name: "Blue Glow", value: "0 4px 14px 0 rgb(59 130 246 / 0.3)", category: "colored" },
254
- { name: "Purple Glow", value: "0 4px 14px 0 rgb(147 51 234 / 0.3)", category: "colored" },
255
- { name: "None", value: "none", category: "reset" },
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 className="flex flex-col gap-2">
270
- {categories.map((cat) => {
271
- const catPresets = presets.filter((p) => p.category === cat);
272
- if (catPresets.length === 0) return null;
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
- <div key={cat}>
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="text-[9px] font-semibold uppercase tracking-wide mb-1"
277
- style={{ color: "var(--studio-text-dimmed)" }}
308
+ className="flex items-center justify-center rounded-lg w-full"
309
+ style={{
310
+ background: previewSettings.previewBg,
311
+ height: 72,
312
+ }}
278
313
  >
279
- {categoryLabels[cat]}
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="flex flex-col gap-1">
282
- {catPresets.map((preset) => (
283
- <button
284
- key={preset.name}
285
- onClick={() => onSelect(preset.value)}
286
- className="flex items-center gap-2 p-1.5 rounded text-left w-full"
287
- style={{
288
- background:
289
- currentValue === preset.value
290
- ? "var(--studio-accent-muted)"
291
- : "transparent",
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
- <ShadowPreview value={preset.value} size={32} />
301
- <span
302
- className="text-[11px]"
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
- </div>
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
- const BACKGROUNDS = [
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 [activeBg, setActiveBg] = useState("white");
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
- {/* Background selector */}
23
- <div className="px-4 py-2 flex items-center gap-2">
24
- <span
25
- className="text-[9px] font-semibold uppercase tracking-wide shrink-0"
26
- style={{ color: "var(--studio-text-dimmed)" }}
27
- >
28
- BG
29
- </span>
30
- <div className="flex gap-1">
31
- {BACKGROUNDS.map((bg) => (
32
- <button
33
- key={bg.value}
34
- onClick={() => setActiveBg(bg.value)}
35
- className="shrink-0 cursor-pointer"
36
- title={bg.name}
37
- style={{
38
- width: 18,
39
- height: 18,
40
- borderRadius: 3,
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
- <div
65
- className="grid gap-4 px-4 py-3"
66
- style={{
67
- gridTemplateColumns: `repeat(auto-fill, minmax(${previewSize + 24}px, 1fr))`,
68
- }}
69
- >
70
- {shadows.map((shadow: any) => (
71
- <div key={shadow.name} className="flex flex-col items-center gap-1.5">
72
- <div
73
- className="flex items-center justify-center rounded-lg p-3"
74
- style={{
75
- background: activeBg,
76
- width: previewSize + 24,
77
- height: previewSize + 24,
78
- }}
79
- >
80
- <ShadowPreview
81
- value={shadow.value}
82
- size={previewSize}
83
- background={activeBg}
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
- <span
87
- className="text-[9px] font-mono text-center truncate w-full"
88
- style={{ color: "var(--studio-text-muted)" }}
89
- >
90
- {shadow.name}
91
- </span>
92
- </div>
93
- ))}
94
- </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
+ )}
95
100
 
96
101
  {shadows.length === 0 && (
97
102
  <div