@newtonedev/editor 0.1.12 → 0.2.1
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/Editor.d.ts.map +1 -1
- package/dist/components/CodeBlock.d.ts.map +1 -1
- package/dist/components/ConfiguratorPanel.d.ts +6 -3
- package/dist/components/ConfiguratorPanel.d.ts.map +1 -1
- package/dist/components/EditorHeader.d.ts +3 -2
- package/dist/components/EditorHeader.d.ts.map +1 -1
- package/dist/components/EditorShell.d.ts.map +1 -1
- package/dist/components/PresetSelector.d.ts +3 -2
- package/dist/components/PresetSelector.d.ts.map +1 -1
- package/dist/components/PreviewWindow.d.ts.map +1 -1
- package/dist/components/RightSidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts +8 -1
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/TableOfContents.d.ts.map +1 -1
- package/dist/components/sections/ColorsSection.d.ts +6 -3
- package/dist/components/sections/ColorsSection.d.ts.map +1 -1
- package/dist/components/sections/DynamicRangeSection.d.ts +2 -2
- package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
- package/dist/components/sections/FontsSection.d.ts +2 -2
- package/dist/components/sections/FontsSection.d.ts.map +1 -1
- package/dist/components/sections/IconsSection.d.ts +2 -2
- package/dist/components/sections/IconsSection.d.ts.map +1 -1
- package/dist/components/sections/OthersSection.d.ts +2 -2
- package/dist/components/sections/OthersSection.d.ts.map +1 -1
- package/dist/components/sections/ScalePlots.d.ts +11 -0
- package/dist/components/sections/ScalePlots.d.ts.map +1 -0
- package/dist/components/sections/index.d.ts +1 -0
- package/dist/components/sections/index.d.ts.map +1 -1
- package/dist/configurator/bridge/toCSS.d.ts +7 -0
- package/dist/configurator/bridge/toCSS.d.ts.map +1 -0
- package/dist/configurator/bridge/toJSON.d.ts +15 -0
- package/dist/configurator/bridge/toJSON.d.ts.map +1 -0
- package/dist/configurator/bridge/toThemeConfig.d.ts +8 -0
- package/dist/configurator/bridge/toThemeConfig.d.ts.map +1 -0
- package/dist/configurator/constants.d.ts +13 -0
- package/dist/configurator/constants.d.ts.map +1 -0
- package/dist/configurator/hex-conversion.d.ts +21 -0
- package/dist/configurator/hex-conversion.d.ts.map +1 -0
- package/dist/configurator/hooks/useConfigurator.d.ts +11 -0
- package/dist/configurator/hooks/useConfigurator.d.ts.map +1 -0
- package/dist/configurator/hooks/usePreviewColors.d.ts +8 -0
- package/dist/configurator/hooks/usePreviewColors.d.ts.map +1 -0
- package/dist/configurator/hooks/useWcagValidation.d.ts +20 -0
- package/dist/configurator/hooks/useWcagValidation.d.ts.map +1 -0
- package/dist/configurator/hue-conversion.d.ts +10 -0
- package/dist/configurator/hue-conversion.d.ts.map +1 -0
- package/dist/configurator/state/actions.d.ts +107 -0
- package/dist/configurator/state/actions.d.ts.map +1 -0
- package/dist/configurator/state/defaults.d.ts +7 -0
- package/dist/configurator/state/defaults.d.ts.map +1 -0
- package/dist/configurator/state/reducer.d.ts +19 -0
- package/dist/configurator/state/reducer.d.ts.map +1 -0
- package/dist/configurator/types.d.ts +60 -0
- package/dist/configurator/types.d.ts.map +1 -0
- package/dist/hooks/useEditorState.d.ts +8 -6
- package/dist/hooks/useEditorState.d.ts.map +1 -1
- package/dist/hooks/usePresets.d.ts +7 -6
- package/dist/hooks/usePresets.d.ts.map +1 -1
- package/dist/index.cjs +30372 -808
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30351 -799
- package/dist/index.js.map +1 -1
- package/dist/preview/CategoryView.d.ts.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts.map +1 -1
- package/dist/preview/ComponentRenderer.d.ts.map +1 -1
- package/dist/preview/IconBrowserView.d.ts.map +1 -1
- package/dist/preview/OverviewView.d.ts.map +1 -1
- package/dist/preview/PaletteScaleView.d.ts +11 -0
- package/dist/preview/PaletteScaleView.d.ts.map +1 -0
- package/dist/types.d.ts +4 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -4
- package/src/Editor.tsx +43 -19
- package/src/components/CodeBlock.tsx +7 -11
- package/src/components/ConfiguratorPanel.tsx +25 -18
- package/src/components/EditorHeader.tsx +29 -39
- package/src/components/EditorShell.tsx +17 -29
- package/src/components/FontPicker.tsx +7 -7
- package/src/components/PresetSelector.tsx +211 -129
- package/src/components/PreviewWindow.tsx +5 -12
- package/src/components/PrimaryNav.tsx +6 -6
- package/src/components/RightSidebar.tsx +24 -25
- package/src/components/Sidebar.tsx +54 -60
- package/src/components/TableOfContents.tsx +4 -5
- package/src/components/sections/ColorsSection.tsx +109 -121
- package/src/components/sections/DynamicRangeSection.tsx +61 -75
- package/src/components/sections/FontsSection.tsx +17 -28
- package/src/components/sections/IconsSection.tsx +2 -2
- package/src/components/sections/OthersSection.tsx +4 -5
- package/src/components/sections/ScalePlots.tsx +221 -0
- package/src/components/sections/index.ts +1 -0
- package/src/configurator/bridge/toCSS.ts +44 -0
- package/src/configurator/bridge/toJSON.ts +24 -0
- package/src/configurator/bridge/toThemeConfig.ts +114 -0
- package/src/configurator/constants.ts +13 -0
- package/src/configurator/hex-conversion.ts +67 -0
- package/src/configurator/hooks/useConfigurator.ts +33 -0
- package/src/configurator/hooks/usePreviewColors.ts +47 -0
- package/src/configurator/hooks/useWcagValidation.ts +133 -0
- package/src/configurator/hue-conversion.ts +25 -0
- package/src/configurator/state/actions.ts +43 -0
- package/src/configurator/state/defaults.ts +107 -0
- package/src/configurator/state/reducer.ts +399 -0
- package/src/configurator/types.ts +65 -0
- package/src/hooks/useEditorState.ts +25 -11
- package/src/hooks/usePresets.ts +54 -33
- package/src/index.ts +33 -0
- package/src/preview/CategoryView.tsx +8 -11
- package/src/preview/ComponentDetailView.tsx +24 -54
- package/src/preview/ComponentRenderer.tsx +2 -4
- package/src/preview/IconBrowserView.tsx +9 -10
- package/src/preview/OverviewView.tsx +9 -12
- package/src/preview/PaletteScaleView.tsx +122 -0
- package/src/types.ts +4 -3
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
2
3
|
import { useTokens, Icon } from "@newtonedev/components";
|
|
3
|
-
import { srgbToHex } from "newtone";
|
|
4
4
|
import type { Preset } from "../types";
|
|
5
5
|
import { presetHasUnpublishedChanges } from "../utils/presets";
|
|
6
6
|
|
|
7
7
|
interface PresetSelectorProps {
|
|
8
8
|
readonly presets: readonly Preset[];
|
|
9
9
|
readonly activePresetId: string;
|
|
10
|
-
readonly
|
|
10
|
+
readonly defaultVariantId: string | null;
|
|
11
11
|
readonly onSwitchPreset: (presetId: string) => void;
|
|
12
12
|
readonly onCreatePreset: (name: string) => Promise<string>;
|
|
13
13
|
readonly onRenamePreset: (presetId: string, name: string) => void;
|
|
@@ -16,49 +16,64 @@ interface PresetSelectorProps {
|
|
|
16
16
|
presetId: string,
|
|
17
17
|
name: string,
|
|
18
18
|
) => Promise<string>;
|
|
19
|
+
readonly onSetDefaultVariant: (presetId: string) => void;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function PresetSelector({
|
|
22
23
|
presets,
|
|
23
24
|
activePresetId,
|
|
24
|
-
|
|
25
|
+
defaultVariantId,
|
|
25
26
|
onSwitchPreset,
|
|
26
27
|
onCreatePreset,
|
|
27
28
|
onRenamePreset,
|
|
28
29
|
onDeletePreset,
|
|
29
30
|
onDuplicatePreset,
|
|
31
|
+
onSetDefaultVariant,
|
|
30
32
|
}: PresetSelectorProps) {
|
|
31
33
|
const tokens = useTokens();
|
|
32
34
|
const [isOpen, setIsOpen] = useState(false);
|
|
33
35
|
const [renamingId, setRenamingId] = useState<string | null>(null);
|
|
34
36
|
const [renameValue, setRenameValue] = useState("");
|
|
35
37
|
const [menuOpenId, setMenuOpenId] = useState<string | null>(null);
|
|
38
|
+
const [menuPos, setMenuPos] = useState<{ top: number; left: number } | null>(
|
|
39
|
+
null,
|
|
40
|
+
);
|
|
41
|
+
const [dropdownPos, setDropdownPos] = useState<{ top: number; left: number } | null>(null);
|
|
36
42
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
37
43
|
const [hoveredAction, setHoveredAction] = useState<string | null>(null);
|
|
44
|
+
const triggerAreaRef = useRef<HTMLDivElement>(null);
|
|
38
45
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
46
|
+
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
47
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
39
48
|
const renameInputRef = useRef<HTMLInputElement>(null);
|
|
40
49
|
|
|
41
50
|
const activePreset = presets.find((p) => p.id === activePresetId);
|
|
42
51
|
|
|
43
|
-
const borderColor =
|
|
44
|
-
const bgColor =
|
|
45
|
-
const textPrimary =
|
|
46
|
-
const textSecondary =
|
|
47
|
-
const interactiveColor =
|
|
48
|
-
const warningColor =
|
|
49
|
-
const errorColor =
|
|
52
|
+
const borderColor = tokens.colors.primary.main.fontDisabled;
|
|
53
|
+
const bgColor = tokens.colors.primary.main.background;
|
|
54
|
+
const textPrimary = tokens.colors.primary.main.fontPrimary;
|
|
55
|
+
const textSecondary = tokens.colors.primary.main.fontTertiary;
|
|
56
|
+
const interactiveColor = tokens.colors.secondary.emphasis.fontPrimary;
|
|
57
|
+
const warningColor = tokens.colors.warning.emphasis.fontPrimary;
|
|
58
|
+
const errorColor = tokens.colors.error.emphasis.fontPrimary;
|
|
50
59
|
const hoverBg = `${borderColor}18`;
|
|
51
60
|
const activeBg = `${interactiveColor}14`;
|
|
52
61
|
|
|
62
|
+
// Close dropdown + context menu on outside click
|
|
53
63
|
useEffect(() => {
|
|
54
64
|
if (!isOpen) return;
|
|
55
65
|
const handleClickOutside = (e: MouseEvent) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
const target = e.target as Node;
|
|
67
|
+
const inTrigger =
|
|
68
|
+
triggerAreaRef.current && triggerAreaRef.current.contains(target);
|
|
69
|
+
const inDropdown =
|
|
70
|
+
dropdownRef.current && dropdownRef.current.contains(target);
|
|
71
|
+
const inMenu = menuRef.current && menuRef.current.contains(target);
|
|
72
|
+
if (!inTrigger && !inDropdown && !inMenu) {
|
|
60
73
|
setIsOpen(false);
|
|
74
|
+
setDropdownPos(null);
|
|
61
75
|
setMenuOpenId(null);
|
|
76
|
+
setMenuPos(null);
|
|
62
77
|
setRenamingId(null);
|
|
63
78
|
}
|
|
64
79
|
};
|
|
@@ -73,20 +88,26 @@ export function PresetSelector({
|
|
|
73
88
|
}
|
|
74
89
|
}, [renamingId]);
|
|
75
90
|
|
|
91
|
+
const closeMenu = useCallback(() => {
|
|
92
|
+
setMenuOpenId(null);
|
|
93
|
+
setMenuPos(null);
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
76
96
|
const handleCreate = useCallback(async () => {
|
|
77
|
-
const name = `
|
|
97
|
+
const name = `Variant ${presets.length + 1}`;
|
|
78
98
|
const newId = await onCreatePreset(name);
|
|
79
99
|
onSwitchPreset(newId);
|
|
80
100
|
setIsOpen(false);
|
|
101
|
+
setDropdownPos(null);
|
|
81
102
|
}, [presets.length, onCreatePreset, onSwitchPreset]);
|
|
82
103
|
|
|
83
104
|
const handleStartRename = useCallback(
|
|
84
105
|
(presetId: string, currentName: string) => {
|
|
85
106
|
setRenamingId(presetId);
|
|
86
107
|
setRenameValue(currentName);
|
|
87
|
-
|
|
108
|
+
closeMenu();
|
|
88
109
|
},
|
|
89
|
-
[],
|
|
110
|
+
[closeMenu],
|
|
90
111
|
);
|
|
91
112
|
|
|
92
113
|
const handleCommitRename = useCallback(() => {
|
|
@@ -102,25 +123,59 @@ export function PresetSelector({
|
|
|
102
123
|
if (window.confirm("Delete this preset? This cannot be undone.")) {
|
|
103
124
|
await onDeletePreset(presetId);
|
|
104
125
|
}
|
|
105
|
-
|
|
126
|
+
closeMenu();
|
|
106
127
|
},
|
|
107
|
-
[presets.length, onDeletePreset],
|
|
128
|
+
[presets.length, onDeletePreset, closeMenu],
|
|
108
129
|
);
|
|
109
130
|
|
|
110
131
|
const handleDuplicate = useCallback(
|
|
111
132
|
async (presetId: string, sourceName: string) => {
|
|
112
133
|
const newId = await onDuplicatePreset(presetId, `${sourceName} (copy)`);
|
|
113
134
|
onSwitchPreset(newId);
|
|
114
|
-
|
|
135
|
+
closeMenu();
|
|
115
136
|
setIsOpen(false);
|
|
137
|
+
setDropdownPos(null);
|
|
116
138
|
},
|
|
117
|
-
[onDuplicatePreset, onSwitchPreset],
|
|
139
|
+
[onDuplicatePreset, onSwitchPreset, closeMenu],
|
|
118
140
|
);
|
|
119
141
|
|
|
142
|
+
const openMenu = useCallback(
|
|
143
|
+
(presetId: string, button: HTMLElement) => {
|
|
144
|
+
if (menuOpenId === presetId) {
|
|
145
|
+
closeMenu();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const rect = button.getBoundingClientRect();
|
|
149
|
+
setMenuPos({ top: rect.bottom + 4, left: rect.right + 4 });
|
|
150
|
+
setMenuOpenId(presetId);
|
|
151
|
+
setHoveredAction(null);
|
|
152
|
+
},
|
|
153
|
+
[menuOpenId, closeMenu],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const menuPreset = menuOpenId
|
|
157
|
+
? presets.find((p) => p.id === menuOpenId)
|
|
158
|
+
: null;
|
|
159
|
+
const menuIsDefault = menuOpenId === defaultVariantId;
|
|
160
|
+
|
|
161
|
+
const toggleDropdown = useCallback(() => {
|
|
162
|
+
if (isOpen) {
|
|
163
|
+
setIsOpen(false);
|
|
164
|
+
setDropdownPos(null);
|
|
165
|
+
} else {
|
|
166
|
+
if (triggerRef.current) {
|
|
167
|
+
const rect = triggerRef.current.getBoundingClientRect();
|
|
168
|
+
setDropdownPos({ top: rect.bottom + 4, left: rect.left });
|
|
169
|
+
}
|
|
170
|
+
setIsOpen(true);
|
|
171
|
+
}
|
|
172
|
+
}, [isOpen]);
|
|
173
|
+
|
|
120
174
|
return (
|
|
121
|
-
<div ref={
|
|
175
|
+
<div ref={triggerAreaRef} style={{ position: "relative" }}>
|
|
122
176
|
<button
|
|
123
|
-
|
|
177
|
+
ref={triggerRef}
|
|
178
|
+
onClick={toggleDropdown}
|
|
124
179
|
style={{
|
|
125
180
|
display: "flex",
|
|
126
181
|
alignItems: "center",
|
|
@@ -156,25 +211,26 @@ export function PresetSelector({
|
|
|
156
211
|
/>
|
|
157
212
|
</button>
|
|
158
213
|
|
|
159
|
-
{isOpen && (
|
|
214
|
+
{isOpen && dropdownPos && createPortal(
|
|
160
215
|
<div
|
|
216
|
+
ref={dropdownRef}
|
|
161
217
|
style={{
|
|
162
|
-
position: "
|
|
163
|
-
top:
|
|
164
|
-
left:
|
|
218
|
+
position: "fixed",
|
|
219
|
+
top: dropdownPos.top,
|
|
220
|
+
left: dropdownPos.left,
|
|
165
221
|
width: 260,
|
|
166
222
|
backgroundColor: bgColor,
|
|
167
223
|
border: `1px solid ${borderColor}`,
|
|
168
224
|
borderRadius: 8,
|
|
169
225
|
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
170
|
-
zIndex:
|
|
226
|
+
zIndex: 10000,
|
|
171
227
|
overflow: "hidden",
|
|
172
228
|
}}
|
|
173
229
|
>
|
|
174
230
|
<div style={{ maxHeight: 240, overflowY: "auto", padding: "4px 0" }}>
|
|
175
231
|
{presets.map((preset) => {
|
|
176
232
|
const isActive = preset.id === activePresetId;
|
|
177
|
-
const
|
|
233
|
+
const isDefaultVariant = preset.id === defaultVariantId;
|
|
178
234
|
const hasChanges = presetHasUnpublishedChanges(preset);
|
|
179
235
|
const isHovered = hoveredId === preset.id;
|
|
180
236
|
const isRenaming = renamingId === preset.id;
|
|
@@ -226,6 +282,7 @@ export function PresetSelector({
|
|
|
226
282
|
onClick={() => {
|
|
227
283
|
onSwitchPreset(preset.id);
|
|
228
284
|
setIsOpen(false);
|
|
285
|
+
setDropdownPos(null);
|
|
229
286
|
}}
|
|
230
287
|
style={{
|
|
231
288
|
flex: 1,
|
|
@@ -259,7 +316,7 @@ export function PresetSelector({
|
|
|
259
316
|
}}
|
|
260
317
|
/>
|
|
261
318
|
)}
|
|
262
|
-
{
|
|
319
|
+
{isDefaultVariant && (
|
|
263
320
|
<span
|
|
264
321
|
style={{
|
|
265
322
|
fontSize: 10,
|
|
@@ -272,7 +329,7 @@ export function PresetSelector({
|
|
|
272
329
|
lineHeight: "14px",
|
|
273
330
|
}}
|
|
274
331
|
>
|
|
275
|
-
|
|
332
|
+
Default
|
|
276
333
|
</span>
|
|
277
334
|
)}
|
|
278
335
|
</div>
|
|
@@ -281,7 +338,7 @@ export function PresetSelector({
|
|
|
281
338
|
<button
|
|
282
339
|
onClick={(e) => {
|
|
283
340
|
e.stopPropagation();
|
|
284
|
-
|
|
341
|
+
openMenu(preset.id, e.currentTarget);
|
|
285
342
|
}}
|
|
286
343
|
style={{
|
|
287
344
|
display: "flex",
|
|
@@ -297,105 +354,15 @@ export function PresetSelector({
|
|
|
297
354
|
flexShrink: 0,
|
|
298
355
|
}}
|
|
299
356
|
>
|
|
300
|
-
<Icon
|
|
357
|
+
<Icon
|
|
358
|
+
name="more_vert"
|
|
359
|
+
size={14}
|
|
360
|
+
color={textSecondary}
|
|
361
|
+
/>
|
|
301
362
|
</button>
|
|
302
363
|
)}
|
|
303
364
|
</>
|
|
304
365
|
)}
|
|
305
|
-
|
|
306
|
-
{isMenuShown && !isRenaming && (
|
|
307
|
-
<div
|
|
308
|
-
style={{
|
|
309
|
-
position: "absolute",
|
|
310
|
-
top: 0,
|
|
311
|
-
right: -140,
|
|
312
|
-
width: 130,
|
|
313
|
-
backgroundColor: bgColor,
|
|
314
|
-
border: `1px solid ${borderColor}`,
|
|
315
|
-
borderRadius: 6,
|
|
316
|
-
boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
|
|
317
|
-
zIndex: 101,
|
|
318
|
-
overflow: "hidden",
|
|
319
|
-
}}
|
|
320
|
-
>
|
|
321
|
-
<button
|
|
322
|
-
onClick={(e) => {
|
|
323
|
-
e.stopPropagation();
|
|
324
|
-
handleStartRename(preset.id, preset.name);
|
|
325
|
-
}}
|
|
326
|
-
onMouseEnter={() => setHoveredAction("rename")}
|
|
327
|
-
onMouseLeave={() => setHoveredAction(null)}
|
|
328
|
-
style={{
|
|
329
|
-
display: "block",
|
|
330
|
-
width: "100%",
|
|
331
|
-
padding: "8px 12px",
|
|
332
|
-
border: "none",
|
|
333
|
-
backgroundColor:
|
|
334
|
-
hoveredAction === "rename"
|
|
335
|
-
? hoverBg
|
|
336
|
-
: "transparent",
|
|
337
|
-
color: textPrimary,
|
|
338
|
-
fontSize: 12,
|
|
339
|
-
textAlign: "left",
|
|
340
|
-
cursor: "pointer",
|
|
341
|
-
}}
|
|
342
|
-
>
|
|
343
|
-
Rename
|
|
344
|
-
</button>
|
|
345
|
-
<button
|
|
346
|
-
onClick={(e) => {
|
|
347
|
-
e.stopPropagation();
|
|
348
|
-
handleDuplicate(preset.id, preset.name);
|
|
349
|
-
}}
|
|
350
|
-
onMouseEnter={() => setHoveredAction("duplicate")}
|
|
351
|
-
onMouseLeave={() => setHoveredAction(null)}
|
|
352
|
-
style={{
|
|
353
|
-
display: "block",
|
|
354
|
-
width: "100%",
|
|
355
|
-
padding: "8px 12px",
|
|
356
|
-
border: "none",
|
|
357
|
-
backgroundColor:
|
|
358
|
-
hoveredAction === "duplicate"
|
|
359
|
-
? hoverBg
|
|
360
|
-
: "transparent",
|
|
361
|
-
color: textPrimary,
|
|
362
|
-
fontSize: 12,
|
|
363
|
-
textAlign: "left",
|
|
364
|
-
cursor: "pointer",
|
|
365
|
-
}}
|
|
366
|
-
>
|
|
367
|
-
Duplicate
|
|
368
|
-
</button>
|
|
369
|
-
<button
|
|
370
|
-
onClick={(e) => {
|
|
371
|
-
e.stopPropagation();
|
|
372
|
-
handleDelete(preset.id);
|
|
373
|
-
}}
|
|
374
|
-
onMouseEnter={() => setHoveredAction("delete")}
|
|
375
|
-
onMouseLeave={() => setHoveredAction(null)}
|
|
376
|
-
disabled={presets.length <= 1}
|
|
377
|
-
style={{
|
|
378
|
-
display: "block",
|
|
379
|
-
width: "100%",
|
|
380
|
-
padding: "8px 12px",
|
|
381
|
-
border: "none",
|
|
382
|
-
backgroundColor:
|
|
383
|
-
hoveredAction === "delete"
|
|
384
|
-
? hoverBg
|
|
385
|
-
: "transparent",
|
|
386
|
-
color:
|
|
387
|
-
presets.length <= 1 ? textSecondary : errorColor,
|
|
388
|
-
fontSize: 12,
|
|
389
|
-
textAlign: "left",
|
|
390
|
-
cursor:
|
|
391
|
-
presets.length <= 1 ? "not-allowed" : "pointer",
|
|
392
|
-
opacity: presets.length <= 1 ? 0.5 : 1,
|
|
393
|
-
}}
|
|
394
|
-
>
|
|
395
|
-
Delete
|
|
396
|
-
</button>
|
|
397
|
-
</div>
|
|
398
|
-
)}
|
|
399
366
|
</div>
|
|
400
367
|
);
|
|
401
368
|
})}
|
|
@@ -421,9 +388,124 @@ export function PresetSelector({
|
|
|
421
388
|
}}
|
|
422
389
|
>
|
|
423
390
|
<Icon name="add" size={14} color={textSecondary} />
|
|
424
|
-
New
|
|
391
|
+
New variant
|
|
392
|
+
</button>
|
|
393
|
+
</div>,
|
|
394
|
+
document.body,
|
|
395
|
+
)}
|
|
396
|
+
|
|
397
|
+
{/* Context menu — rendered outside the overflow:hidden dropdown panel */}
|
|
398
|
+
{menuOpenId && menuPreset && menuPos && createPortal(
|
|
399
|
+
<div
|
|
400
|
+
ref={menuRef}
|
|
401
|
+
style={{
|
|
402
|
+
position: "fixed",
|
|
403
|
+
top: menuPos.top,
|
|
404
|
+
left: menuPos.left,
|
|
405
|
+
width: 140,
|
|
406
|
+
backgroundColor: bgColor,
|
|
407
|
+
border: `1px solid ${borderColor}`,
|
|
408
|
+
borderRadius: 6,
|
|
409
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.12)",
|
|
410
|
+
zIndex: 10000,
|
|
411
|
+
overflow: "hidden",
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
{!menuIsDefault && (
|
|
415
|
+
<button
|
|
416
|
+
onClick={(e) => {
|
|
417
|
+
e.stopPropagation();
|
|
418
|
+
onSetDefaultVariant(menuOpenId);
|
|
419
|
+
closeMenu();
|
|
420
|
+
}}
|
|
421
|
+
onMouseEnter={() => setHoveredAction("default")}
|
|
422
|
+
onMouseLeave={() => setHoveredAction(null)}
|
|
423
|
+
style={{
|
|
424
|
+
display: "block",
|
|
425
|
+
width: "100%",
|
|
426
|
+
padding: "8px 12px",
|
|
427
|
+
border: "none",
|
|
428
|
+
backgroundColor:
|
|
429
|
+
hoveredAction === "default" ? hoverBg : "transparent",
|
|
430
|
+
color: textPrimary,
|
|
431
|
+
fontSize: 12,
|
|
432
|
+
textAlign: "left",
|
|
433
|
+
cursor: "pointer",
|
|
434
|
+
}}
|
|
435
|
+
>
|
|
436
|
+
Make default
|
|
437
|
+
</button>
|
|
438
|
+
)}
|
|
439
|
+
<button
|
|
440
|
+
onClick={(e) => {
|
|
441
|
+
e.stopPropagation();
|
|
442
|
+
handleStartRename(menuPreset.id, menuPreset.name);
|
|
443
|
+
}}
|
|
444
|
+
onMouseEnter={() => setHoveredAction("rename")}
|
|
445
|
+
onMouseLeave={() => setHoveredAction(null)}
|
|
446
|
+
style={{
|
|
447
|
+
display: "block",
|
|
448
|
+
width: "100%",
|
|
449
|
+
padding: "8px 12px",
|
|
450
|
+
border: "none",
|
|
451
|
+
backgroundColor:
|
|
452
|
+
hoveredAction === "rename" ? hoverBg : "transparent",
|
|
453
|
+
color: textPrimary,
|
|
454
|
+
fontSize: 12,
|
|
455
|
+
textAlign: "left",
|
|
456
|
+
cursor: "pointer",
|
|
457
|
+
}}
|
|
458
|
+
>
|
|
459
|
+
Rename
|
|
460
|
+
</button>
|
|
461
|
+
<button
|
|
462
|
+
onClick={(e) => {
|
|
463
|
+
e.stopPropagation();
|
|
464
|
+
handleDuplicate(menuPreset.id, menuPreset.name);
|
|
465
|
+
}}
|
|
466
|
+
onMouseEnter={() => setHoveredAction("duplicate")}
|
|
467
|
+
onMouseLeave={() => setHoveredAction(null)}
|
|
468
|
+
style={{
|
|
469
|
+
display: "block",
|
|
470
|
+
width: "100%",
|
|
471
|
+
padding: "8px 12px",
|
|
472
|
+
border: "none",
|
|
473
|
+
backgroundColor:
|
|
474
|
+
hoveredAction === "duplicate" ? hoverBg : "transparent",
|
|
475
|
+
color: textPrimary,
|
|
476
|
+
fontSize: 12,
|
|
477
|
+
textAlign: "left",
|
|
478
|
+
cursor: "pointer",
|
|
479
|
+
}}
|
|
480
|
+
>
|
|
481
|
+
Duplicate
|
|
482
|
+
</button>
|
|
483
|
+
<button
|
|
484
|
+
onClick={(e) => {
|
|
485
|
+
e.stopPropagation();
|
|
486
|
+
handleDelete(menuPreset.id);
|
|
487
|
+
}}
|
|
488
|
+
onMouseEnter={() => setHoveredAction("delete")}
|
|
489
|
+
onMouseLeave={() => setHoveredAction(null)}
|
|
490
|
+
disabled={presets.length <= 1}
|
|
491
|
+
style={{
|
|
492
|
+
display: "block",
|
|
493
|
+
width: "100%",
|
|
494
|
+
padding: "8px 12px",
|
|
495
|
+
border: "none",
|
|
496
|
+
backgroundColor:
|
|
497
|
+
hoveredAction === "delete" ? hoverBg : "transparent",
|
|
498
|
+
color: presets.length <= 1 ? textSecondary : errorColor,
|
|
499
|
+
fontSize: 12,
|
|
500
|
+
textAlign: "left",
|
|
501
|
+
cursor: presets.length <= 1 ? "not-allowed" : "pointer",
|
|
502
|
+
opacity: presets.length <= 1 ? 0.5 : 1,
|
|
503
|
+
}}
|
|
504
|
+
>
|
|
505
|
+
Delete
|
|
425
506
|
</button>
|
|
426
|
-
</div
|
|
507
|
+
</div>,
|
|
508
|
+
document.body,
|
|
427
509
|
)}
|
|
428
510
|
</div>
|
|
429
511
|
);
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
-
import {
|
|
3
|
-
import { srgbToHex } from "newtone";
|
|
2
|
+
import { Frame } from "@newtonedev/components";
|
|
4
3
|
import type { TextRole } from "@newtonedev/fonts";
|
|
5
4
|
import { OverviewView } from "../preview/OverviewView";
|
|
6
5
|
import { CategoryView } from "../preview/CategoryView";
|
|
@@ -32,8 +31,6 @@ export function PreviewWindow({
|
|
|
32
31
|
fontCatalog,
|
|
33
32
|
scopeFontMap,
|
|
34
33
|
}: PreviewWindowProps) {
|
|
35
|
-
const tokens = useTokens();
|
|
36
|
-
|
|
37
34
|
const handleNavigateToCategory = useCallback(
|
|
38
35
|
(categoryId: string) => onNavigate({ kind: "category", categoryId }),
|
|
39
36
|
[onNavigate],
|
|
@@ -45,13 +42,9 @@ export function PreviewWindow({
|
|
|
45
42
|
);
|
|
46
43
|
|
|
47
44
|
return (
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
flexDirection: "column",
|
|
52
|
-
height: "100%",
|
|
53
|
-
backgroundColor: srgbToHex(tokens.background.srgb),
|
|
54
|
-
}}
|
|
45
|
+
<Frame
|
|
46
|
+
direction="vertical"
|
|
47
|
+
height="fill"
|
|
55
48
|
>
|
|
56
49
|
<div style={{ flex: 1, overflowY: "auto" }}>
|
|
57
50
|
{view.kind === "overview" && (
|
|
@@ -80,6 +73,6 @@ export function PreviewWindow({
|
|
|
80
73
|
/>
|
|
81
74
|
)}
|
|
82
75
|
</div>
|
|
83
|
-
</
|
|
76
|
+
</Frame>
|
|
84
77
|
);
|
|
85
78
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { useTokens, Icon, CATEGORIES } from "@newtonedev/components";
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
|
|
6
6
|
interface PrimaryNavProps {
|
|
7
7
|
readonly activeSectionId: string | null;
|
|
@@ -15,11 +15,11 @@ export function PrimaryNav({ activeSectionId, onSelectSection, footer }: Primary
|
|
|
15
15
|
const tokens = useTokens();
|
|
16
16
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
17
17
|
|
|
18
|
-
const bg =
|
|
19
|
-
const borderColor =
|
|
20
|
-
const activeBg =
|
|
21
|
-
const iconColor =
|
|
22
|
-
const activeIconColor =
|
|
18
|
+
const bg = tokens.colors.primary.main.background;
|
|
19
|
+
const borderColor = tokens.colors.primary.main.fontDisabled;
|
|
20
|
+
const activeBg = tokens.colors.primary.main.divider;
|
|
21
|
+
const iconColor = tokens.colors.primary.main.fontTertiary;
|
|
22
|
+
const activeIconColor = tokens.colors.primary.main.fontPrimary;
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
25
|
<nav
|