@newtonedev/editor 0.1.5 → 0.1.7
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 +1 -1
- package/dist/Editor.d.ts.map +1 -1
- package/dist/components/CodeBlock.d.ts.map +1 -1
- package/dist/components/ConfiguratorPanel.d.ts +17 -0
- package/dist/components/ConfiguratorPanel.d.ts.map +1 -0
- package/dist/components/FontPicker.d.ts +4 -2
- package/dist/components/FontPicker.d.ts.map +1 -1
- package/dist/components/PresetSelector.d.ts.map +1 -1
- package/dist/components/PreviewWindow.d.ts +9 -3
- package/dist/components/PreviewWindow.d.ts.map +1 -1
- package/dist/components/PrimaryNav.d.ts +7 -0
- package/dist/components/PrimaryNav.d.ts.map +1 -0
- package/dist/components/RightSidebar.d.ts +4 -1
- package/dist/components/RightSidebar.d.ts.map +1 -1
- package/dist/components/Sidebar.d.ts +1 -10
- package/dist/components/Sidebar.d.ts.map +1 -1
- package/dist/components/TableOfContents.d.ts +2 -1
- package/dist/components/TableOfContents.d.ts.map +1 -1
- package/dist/components/sections/DynamicRangeSection.d.ts.map +1 -1
- package/dist/components/sections/FontsSection.d.ts +3 -1
- package/dist/components/sections/FontsSection.d.ts.map +1 -1
- package/dist/hooks/useEditorState.d.ts +4 -1
- package/dist/hooks/useEditorState.d.ts.map +1 -1
- package/dist/index.cjs +2893 -2248
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2895 -2251
- package/dist/index.js.map +1 -1
- package/dist/preview/ComponentDetailView.d.ts +9 -2
- package/dist/preview/ComponentDetailView.d.ts.map +1 -1
- package/dist/preview/ComponentRenderer.d.ts +2 -1
- package/dist/preview/ComponentRenderer.d.ts.map +1 -1
- package/dist/preview/IconBrowserView.d.ts +7 -0
- package/dist/preview/IconBrowserView.d.ts.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/lookupFontMetrics.d.ts +19 -0
- package/dist/utils/lookupFontMetrics.d.ts.map +1 -0
- package/dist/utils/measureFonts.d.ts +18 -0
- package/dist/utils/measureFonts.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/Editor.tsx +57 -11
- package/src/components/CodeBlock.tsx +42 -14
- package/src/components/ConfiguratorPanel.tsx +77 -0
- package/src/components/FontPicker.tsx +38 -29
- package/src/components/PresetSelector.tsx +8 -33
- package/src/components/PreviewWindow.tsx +20 -4
- package/src/components/PrimaryNav.tsx +76 -0
- package/src/components/RightSidebar.tsx +103 -40
- package/src/components/Sidebar.tsx +4 -211
- package/src/components/TableOfContents.tsx +41 -78
- package/src/components/sections/DynamicRangeSection.tsx +2 -225
- package/src/components/sections/FontsSection.tsx +61 -93
- package/src/hooks/useEditorState.ts +68 -9
- package/src/index.ts +2 -0
- package/src/preview/ComponentDetailView.tsx +576 -73
- package/src/preview/ComponentRenderer.tsx +6 -4
- package/src/preview/IconBrowserView.tsx +187 -0
- package/src/types.ts +15 -0
- package/src/utils/lookupFontMetrics.ts +52 -0
- package/src/utils/measureFonts.ts +41 -0
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
2
|
import { useTokens } from "@newtonedev/components";
|
|
3
3
|
import { srgbToHex } from "newtone";
|
|
4
|
+
import type { TextRole } from "@newtonedev/fonts";
|
|
4
5
|
import { OverviewView } from "../preview/OverviewView";
|
|
5
6
|
import { CategoryView } from "../preview/CategoryView";
|
|
6
7
|
import { ComponentDetailView } from "../preview/ComponentDetailView";
|
|
7
|
-
import type { PreviewView } from "../types";
|
|
8
|
+
import type { PreviewView, EditorFontEntry } from "../types";
|
|
8
9
|
|
|
9
10
|
interface PreviewWindowProps {
|
|
10
11
|
readonly view: PreviewView;
|
|
11
12
|
readonly selectedVariantId: string | null;
|
|
12
|
-
readonly propOverrides?: Record<string, unknown>;
|
|
13
13
|
readonly onNavigate: (view: PreviewView) => void;
|
|
14
14
|
readonly onSelectVariant: (variantId: string) => void;
|
|
15
|
+
readonly propOverrides?: Record<string, unknown>;
|
|
16
|
+
readonly onPropOverride?: (name: string, value: unknown) => void;
|
|
17
|
+
readonly roleWeights?: Partial<Record<TextRole, number>>;
|
|
18
|
+
readonly onRoleWeightChange?: (role: TextRole, weight: number) => void;
|
|
19
|
+
readonly fontCatalog?: readonly EditorFontEntry[];
|
|
20
|
+
readonly scopeFontMap?: Record<string, string>;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
export function PreviewWindow({
|
|
18
24
|
view,
|
|
19
25
|
selectedVariantId,
|
|
20
|
-
propOverrides,
|
|
21
26
|
onNavigate,
|
|
22
27
|
onSelectVariant,
|
|
28
|
+
propOverrides,
|
|
29
|
+
onPropOverride,
|
|
30
|
+
roleWeights,
|
|
31
|
+
onRoleWeightChange,
|
|
32
|
+
fontCatalog,
|
|
33
|
+
scopeFontMap,
|
|
23
34
|
}: PreviewWindowProps) {
|
|
24
35
|
const tokens = useTokens();
|
|
25
36
|
|
|
@@ -59,8 +70,13 @@ export function PreviewWindow({
|
|
|
59
70
|
<ComponentDetailView
|
|
60
71
|
componentId={view.componentId}
|
|
61
72
|
selectedVariantId={selectedVariantId}
|
|
62
|
-
propOverrides={propOverrides}
|
|
63
73
|
onSelectVariant={onSelectVariant}
|
|
74
|
+
propOverrides={propOverrides}
|
|
75
|
+
onPropOverride={onPropOverride}
|
|
76
|
+
roleWeights={roleWeights}
|
|
77
|
+
onRoleWeightChange={onRoleWeightChange}
|
|
78
|
+
fontCatalog={fontCatalog}
|
|
79
|
+
scopeFontMap={scopeFontMap}
|
|
64
80
|
/>
|
|
65
81
|
)}
|
|
66
82
|
</div>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useTokens, Icon, CATEGORIES } from "@newtonedev/components";
|
|
3
|
+
import { srgbToHex } from "newtone";
|
|
4
|
+
|
|
5
|
+
interface PrimaryNavProps {
|
|
6
|
+
readonly activeSectionId: string | null;
|
|
7
|
+
readonly onSelectSection: (sectionId: string) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const NAV_WIDTH = 60;
|
|
11
|
+
|
|
12
|
+
export function PrimaryNav({ activeSectionId, onSelectSection }: PrimaryNavProps) {
|
|
13
|
+
const tokens = useTokens();
|
|
14
|
+
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
15
|
+
|
|
16
|
+
const bg = srgbToHex(tokens.background.srgb);
|
|
17
|
+
const borderColor = srgbToHex(tokens.border.srgb);
|
|
18
|
+
const activeBg = srgbToHex(tokens.backgroundInteractive.srgb);
|
|
19
|
+
const iconColor = srgbToHex(tokens.textSecondary.srgb);
|
|
20
|
+
const activeIconColor = srgbToHex(tokens.textPrimary.srgb);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<nav
|
|
24
|
+
aria-label="Section navigation"
|
|
25
|
+
style={{
|
|
26
|
+
width: NAV_WIDTH,
|
|
27
|
+
flexShrink: 0,
|
|
28
|
+
display: "flex",
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
alignItems: "center",
|
|
31
|
+
paddingTop: 12,
|
|
32
|
+
gap: 4,
|
|
33
|
+
backgroundColor: bg,
|
|
34
|
+
borderRight: `1px solid ${borderColor}`,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{CATEGORIES.map((category) => {
|
|
38
|
+
const isActive = activeSectionId === category.id;
|
|
39
|
+
const isHovered = hoveredId === category.id;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
key={category.id}
|
|
44
|
+
onClick={() => onSelectSection(category.id)}
|
|
45
|
+
onMouseEnter={() => setHoveredId(category.id)}
|
|
46
|
+
onMouseLeave={() => setHoveredId(null)}
|
|
47
|
+
title={category.name}
|
|
48
|
+
aria-current={isActive ? "page" : undefined}
|
|
49
|
+
style={{
|
|
50
|
+
display: "flex",
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
justifyContent: "center",
|
|
53
|
+
width: 44,
|
|
54
|
+
height: 44,
|
|
55
|
+
borderRadius: 12,
|
|
56
|
+
border: "none",
|
|
57
|
+
backgroundColor: isActive
|
|
58
|
+
? activeBg
|
|
59
|
+
: isHovered
|
|
60
|
+
? `${borderColor}20`
|
|
61
|
+
: "transparent",
|
|
62
|
+
cursor: "pointer",
|
|
63
|
+
transition: "background-color 150ms",
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<Icon
|
|
67
|
+
name={category.icon ?? "circle"}
|
|
68
|
+
size={24}
|
|
69
|
+
color={isActive ? activeIconColor : iconColor}
|
|
70
|
+
/>
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
})}
|
|
74
|
+
</nav>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { useCallback, type KeyboardEvent } from "react";
|
|
2
|
-
import { useTokens, getComponent, generateComponentCode, Select } from "@newtonedev/components";
|
|
3
|
-
import type { EditableProp } from "@newtonedev/components";
|
|
2
|
+
import { useTokens, getComponent, generateComponentCode, Select, NewtoneProvider, Icon } from "@newtonedev/components";
|
|
3
|
+
import type { EditableProp, NewtoneThemeConfig, ColorMode } from "@newtonedev/components";
|
|
4
4
|
import { srgbToHex } from "newtone";
|
|
5
5
|
import { CodeBlock } from "./CodeBlock";
|
|
6
|
+
import { ComponentRenderer } from "../preview/ComponentRenderer";
|
|
6
7
|
import type { SidebarSelection } from "../types";
|
|
7
8
|
|
|
8
9
|
interface RightSidebarProps {
|
|
@@ -12,6 +13,8 @@ interface RightSidebarProps {
|
|
|
12
13
|
readonly onResetOverrides: () => void;
|
|
13
14
|
readonly onClose: () => void;
|
|
14
15
|
readonly onScopeToComponent: () => void;
|
|
16
|
+
readonly previewConfig: NewtoneThemeConfig;
|
|
17
|
+
readonly colorMode: ColorMode;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
export function RightSidebar({
|
|
@@ -21,6 +24,8 @@ export function RightSidebar({
|
|
|
21
24
|
onResetOverrides,
|
|
22
25
|
onClose,
|
|
23
26
|
onScopeToComponent,
|
|
27
|
+
previewConfig,
|
|
28
|
+
colorMode,
|
|
24
29
|
}: RightSidebarProps) {
|
|
25
30
|
const tokens = useTokens();
|
|
26
31
|
const visible = selection !== null;
|
|
@@ -79,19 +84,7 @@ export function RightSidebar({
|
|
|
79
84
|
alignItems: "center",
|
|
80
85
|
}}
|
|
81
86
|
>
|
|
82
|
-
<
|
|
83
|
-
width={16}
|
|
84
|
-
height={16}
|
|
85
|
-
viewBox="0 0 24 24"
|
|
86
|
-
fill="none"
|
|
87
|
-
stroke="currentColor"
|
|
88
|
-
strokeWidth={2}
|
|
89
|
-
strokeLinecap="round"
|
|
90
|
-
strokeLinejoin="round"
|
|
91
|
-
>
|
|
92
|
-
<line x1="19" y1="12" x2="5" y2="12" />
|
|
93
|
-
<polyline points="12 19 5 12 12 5" />
|
|
94
|
-
</svg>
|
|
87
|
+
<Icon name="arrow_back" size={16} color={srgbToHex(tokens.textSecondary.srgb)} />
|
|
95
88
|
</button>
|
|
96
89
|
{selection.scope === "variant" && variant ? (
|
|
97
90
|
<>
|
|
@@ -154,6 +147,27 @@ export function RightSidebar({
|
|
|
154
147
|
padding: 16,
|
|
155
148
|
}}
|
|
156
149
|
>
|
|
150
|
+
{/* Live Preview */}
|
|
151
|
+
<div
|
|
152
|
+
style={{
|
|
153
|
+
marginBottom: 20,
|
|
154
|
+
borderRadius: 8,
|
|
155
|
+
border: `1px solid ${srgbToHex(tokens.border.srgb)}`,
|
|
156
|
+
overflow: "hidden",
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
<NewtoneProvider
|
|
160
|
+
config={previewConfig}
|
|
161
|
+
initialMode={colorMode}
|
|
162
|
+
key={colorMode}
|
|
163
|
+
>
|
|
164
|
+
<PreviewSurface
|
|
165
|
+
componentId={selection.componentId}
|
|
166
|
+
propOverrides={propOverrides}
|
|
167
|
+
/>
|
|
168
|
+
</NewtoneProvider>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
157
171
|
<h3
|
|
158
172
|
style={{
|
|
159
173
|
fontSize: 13,
|
|
@@ -222,6 +236,31 @@ export function RightSidebar({
|
|
|
222
236
|
);
|
|
223
237
|
}
|
|
224
238
|
|
|
239
|
+
function PreviewSurface({
|
|
240
|
+
componentId,
|
|
241
|
+
propOverrides,
|
|
242
|
+
}: {
|
|
243
|
+
readonly componentId: string;
|
|
244
|
+
readonly propOverrides: Record<string, unknown>;
|
|
245
|
+
}) {
|
|
246
|
+
const previewTokens = useTokens();
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div
|
|
250
|
+
style={{
|
|
251
|
+
display: "flex",
|
|
252
|
+
alignItems: "center",
|
|
253
|
+
justifyContent: "center",
|
|
254
|
+
padding: 24,
|
|
255
|
+
height: 120,
|
|
256
|
+
backgroundColor: srgbToHex(previewTokens.backgroundElevated.srgb),
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<ComponentRenderer componentId={componentId} props={propOverrides} />
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
225
264
|
function PropControl({
|
|
226
265
|
prop,
|
|
227
266
|
value,
|
|
@@ -257,14 +296,7 @@ function PropControl({
|
|
|
257
296
|
|
|
258
297
|
return (
|
|
259
298
|
<div>
|
|
260
|
-
<div
|
|
261
|
-
style={{
|
|
262
|
-
display: "flex",
|
|
263
|
-
alignItems: "center",
|
|
264
|
-
justifyContent: "space-between",
|
|
265
|
-
marginBottom: 4,
|
|
266
|
-
}}
|
|
267
|
-
>
|
|
299
|
+
<div style={{ marginBottom: 4 }}>
|
|
268
300
|
<span
|
|
269
301
|
style={{
|
|
270
302
|
fontSize: 12,
|
|
@@ -274,15 +306,6 @@ function PropControl({
|
|
|
274
306
|
>
|
|
275
307
|
{prop.label}
|
|
276
308
|
</span>
|
|
277
|
-
<span
|
|
278
|
-
style={{
|
|
279
|
-
fontSize: 11,
|
|
280
|
-
color: srgbToHex(tokens.textSecondary.srgb),
|
|
281
|
-
fontFamily: "'SF Mono', 'Fira Code', Menlo, monospace",
|
|
282
|
-
}}
|
|
283
|
-
>
|
|
284
|
-
{prop.control}
|
|
285
|
-
</span>
|
|
286
309
|
</div>
|
|
287
310
|
|
|
288
311
|
{prop.control === "select" && prop.options && (
|
|
@@ -318,6 +341,54 @@ function PropControl({
|
|
|
318
341
|
/>
|
|
319
342
|
)}
|
|
320
343
|
|
|
344
|
+
{prop.control === "discrete-slider" && prop.options && (() => {
|
|
345
|
+
const options = prop.options!;
|
|
346
|
+
const currentIndex = options.findIndex((o) => o.value === value);
|
|
347
|
+
const idx = currentIndex >= 0 ? currentIndex : 0;
|
|
348
|
+
|
|
349
|
+
return (
|
|
350
|
+
<div>
|
|
351
|
+
<input
|
|
352
|
+
type="range"
|
|
353
|
+
min={0}
|
|
354
|
+
max={options.length - 1}
|
|
355
|
+
step={1}
|
|
356
|
+
value={idx}
|
|
357
|
+
onChange={(e) => onChange(options[Number(e.target.value)].value)}
|
|
358
|
+
aria-label={prop.label}
|
|
359
|
+
style={{
|
|
360
|
+
width: "100%",
|
|
361
|
+
accentColor: srgbToHex(tokens.accent.fill.srgb),
|
|
362
|
+
cursor: "pointer",
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
<div
|
|
366
|
+
style={{
|
|
367
|
+
display: "flex",
|
|
368
|
+
justifyContent: "space-between",
|
|
369
|
+
marginTop: 2,
|
|
370
|
+
}}
|
|
371
|
+
>
|
|
372
|
+
{options.map((o) => (
|
|
373
|
+
<span
|
|
374
|
+
key={String(o.value)}
|
|
375
|
+
style={{
|
|
376
|
+
fontSize: 11,
|
|
377
|
+
fontFamily: "'SF Mono', 'Fira Code', Menlo, monospace",
|
|
378
|
+
color: o.value === value
|
|
379
|
+
? srgbToHex(tokens.textPrimary.srgb)
|
|
380
|
+
: srgbToHex(tokens.textTertiary.srgb),
|
|
381
|
+
fontWeight: o.value === value ? 600 : 400,
|
|
382
|
+
}}
|
|
383
|
+
>
|
|
384
|
+
{o.label}
|
|
385
|
+
</span>
|
|
386
|
+
))}
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
);
|
|
390
|
+
})()}
|
|
391
|
+
|
|
321
392
|
{prop.control === "toggle" && (
|
|
322
393
|
<div
|
|
323
394
|
role="switch"
|
|
@@ -359,14 +430,6 @@ function PropControl({
|
|
|
359
430
|
}}
|
|
360
431
|
/>
|
|
361
432
|
</div>
|
|
362
|
-
<span
|
|
363
|
-
style={{
|
|
364
|
-
fontSize: 12,
|
|
365
|
-
color: srgbToHex(tokens.textSecondary.srgb),
|
|
366
|
-
}}
|
|
367
|
-
>
|
|
368
|
-
{value ? "true" : "false"}
|
|
369
|
-
</span>
|
|
370
433
|
</div>
|
|
371
434
|
)}
|
|
372
435
|
</div>
|
|
@@ -1,108 +1,11 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
1
|
import { useTokens } from "@newtonedev/components";
|
|
3
|
-
import type { ColorMode } from "@newtonedev/components";
|
|
4
2
|
import { srgbToHex } from "newtone";
|
|
5
|
-
import type { ColorResult } from "newtone";
|
|
6
|
-
import type { ConfiguratorState } from "@newtonedev/configurator";
|
|
7
|
-
import type { ConfiguratorAction } from "@newtonedev/configurator";
|
|
8
|
-
import {
|
|
9
|
-
ColorsSection,
|
|
10
|
-
DynamicRangeSection,
|
|
11
|
-
IconsSection,
|
|
12
|
-
FontsSection,
|
|
13
|
-
OthersSection,
|
|
14
|
-
} from "./sections";
|
|
15
3
|
import { PresetSelector } from "./PresetSelector";
|
|
16
4
|
import type { Preset } from "../types";
|
|
17
5
|
|
|
18
6
|
const SIDEBAR_WIDTH = 360;
|
|
19
7
|
|
|
20
|
-
const ACCORDION_SECTIONS = [
|
|
21
|
-
{ id: "dynamic-range", label: "Dynamic Range" },
|
|
22
|
-
{ id: "colors", label: "Colors" },
|
|
23
|
-
{ id: "fonts", label: "Fonts" },
|
|
24
|
-
{ id: "icons", label: "Icons" },
|
|
25
|
-
{ id: "others", label: "Others" },
|
|
26
|
-
] as const;
|
|
27
|
-
|
|
28
|
-
function SectionIcon({ id }: { readonly id: string }) {
|
|
29
|
-
const props = {
|
|
30
|
-
width: 16,
|
|
31
|
-
height: 16,
|
|
32
|
-
viewBox: "0 0 24 24",
|
|
33
|
-
fill: "none",
|
|
34
|
-
stroke: "currentColor",
|
|
35
|
-
strokeWidth: 2,
|
|
36
|
-
strokeLinecap: "round" as const,
|
|
37
|
-
strokeLinejoin: "round" as const,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
switch (id) {
|
|
41
|
-
case "dynamic-range":
|
|
42
|
-
// Sun/contrast icon
|
|
43
|
-
return (
|
|
44
|
-
<svg {...props}>
|
|
45
|
-
<circle cx="12" cy="12" r="5" />
|
|
46
|
-
<line x1="12" y1="1" x2="12" y2="3" />
|
|
47
|
-
<line x1="12" y1="21" x2="12" y2="23" />
|
|
48
|
-
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" />
|
|
49
|
-
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
|
|
50
|
-
<line x1="1" y1="12" x2="3" y2="12" />
|
|
51
|
-
<line x1="21" y1="12" x2="23" y2="12" />
|
|
52
|
-
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" />
|
|
53
|
-
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
|
|
54
|
-
</svg>
|
|
55
|
-
);
|
|
56
|
-
case "colors":
|
|
57
|
-
// Palette/droplet icon
|
|
58
|
-
return (
|
|
59
|
-
<svg {...props}>
|
|
60
|
-
<path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z" />
|
|
61
|
-
</svg>
|
|
62
|
-
);
|
|
63
|
-
case "fonts":
|
|
64
|
-
// Type/text icon
|
|
65
|
-
return (
|
|
66
|
-
<svg {...props}>
|
|
67
|
-
<polyline points="4 7 4 4 20 4 20 7" />
|
|
68
|
-
<line x1="9" y1="20" x2="15" y2="20" />
|
|
69
|
-
<line x1="12" y1="4" x2="12" y2="20" />
|
|
70
|
-
</svg>
|
|
71
|
-
);
|
|
72
|
-
case "icons":
|
|
73
|
-
// Grid icon
|
|
74
|
-
return (
|
|
75
|
-
<svg {...props}>
|
|
76
|
-
<rect x="3" y="3" width="7" height="7" />
|
|
77
|
-
<rect x="14" y="3" width="7" height="7" />
|
|
78
|
-
<rect x="3" y="14" width="7" height="7" />
|
|
79
|
-
<rect x="14" y="14" width="7" height="7" />
|
|
80
|
-
</svg>
|
|
81
|
-
);
|
|
82
|
-
case "others":
|
|
83
|
-
// Sliders icon
|
|
84
|
-
return (
|
|
85
|
-
<svg {...props}>
|
|
86
|
-
<line x1="4" y1="21" x2="4" y2="14" />
|
|
87
|
-
<line x1="4" y1="10" x2="4" y2="3" />
|
|
88
|
-
<line x1="12" y1="21" x2="12" y2="12" />
|
|
89
|
-
<line x1="12" y1="8" x2="12" y2="3" />
|
|
90
|
-
<line x1="20" y1="21" x2="20" y2="16" />
|
|
91
|
-
<line x1="20" y1="12" x2="20" y2="3" />
|
|
92
|
-
<line x1="1" y1="14" x2="7" y2="14" />
|
|
93
|
-
<line x1="9" y1="8" x2="15" y2="8" />
|
|
94
|
-
<line x1="17" y1="16" x2="23" y2="16" />
|
|
95
|
-
</svg>
|
|
96
|
-
);
|
|
97
|
-
default:
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
8
|
interface SidebarProps {
|
|
103
|
-
readonly state: ConfiguratorState;
|
|
104
|
-
readonly dispatch: (action: ConfiguratorAction) => void;
|
|
105
|
-
readonly previewColors: readonly (readonly ColorResult[])[];
|
|
106
9
|
readonly isDirty: boolean;
|
|
107
10
|
readonly onRevert: () => void;
|
|
108
11
|
readonly presets: readonly Preset[];
|
|
@@ -116,14 +19,9 @@ interface SidebarProps {
|
|
|
116
19
|
presetId: string,
|
|
117
20
|
name: string,
|
|
118
21
|
) => Promise<string>;
|
|
119
|
-
readonly colorMode: ColorMode;
|
|
120
|
-
readonly onColorModeChange: (mode: ColorMode) => void;
|
|
121
22
|
}
|
|
122
23
|
|
|
123
24
|
export function Sidebar({
|
|
124
|
-
state,
|
|
125
|
-
dispatch,
|
|
126
|
-
previewColors,
|
|
127
25
|
isDirty,
|
|
128
26
|
onRevert,
|
|
129
27
|
presets,
|
|
@@ -134,52 +32,11 @@ export function Sidebar({
|
|
|
134
32
|
onRenamePreset,
|
|
135
33
|
onDeletePreset,
|
|
136
34
|
onDuplicatePreset,
|
|
137
|
-
colorMode,
|
|
138
|
-
onColorModeChange,
|
|
139
35
|
}: SidebarProps) {
|
|
140
36
|
const tokens = useTokens();
|
|
141
|
-
const [openSections, setOpenSections] = useState<Set<string>>(
|
|
142
|
-
new Set(["dynamic-range", "colors"]),
|
|
143
|
-
);
|
|
144
|
-
const [hoveredSectionId, setHoveredSectionId] = useState<string | null>(null);
|
|
145
37
|
|
|
146
38
|
const borderColor = srgbToHex(tokens.border.srgb);
|
|
147
39
|
const bgColor = srgbToHex(tokens.background.srgb);
|
|
148
|
-
const hoverBg = `${borderColor}10`;
|
|
149
|
-
|
|
150
|
-
const toggleSection = (id: string) => {
|
|
151
|
-
setOpenSections((prev) => {
|
|
152
|
-
const next = new Set(prev);
|
|
153
|
-
if (next.has(id)) next.delete(id);
|
|
154
|
-
else next.add(id);
|
|
155
|
-
return next;
|
|
156
|
-
});
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
const renderSectionContent = (sectionId: string) => {
|
|
160
|
-
switch (sectionId) {
|
|
161
|
-
case "dynamic-range":
|
|
162
|
-
return <DynamicRangeSection state={state} dispatch={dispatch} />;
|
|
163
|
-
case "colors":
|
|
164
|
-
return (
|
|
165
|
-
<ColorsSection
|
|
166
|
-
state={state}
|
|
167
|
-
dispatch={dispatch}
|
|
168
|
-
previewColors={previewColors}
|
|
169
|
-
colorMode={colorMode}
|
|
170
|
-
onColorModeChange={onColorModeChange}
|
|
171
|
-
/>
|
|
172
|
-
);
|
|
173
|
-
case "icons":
|
|
174
|
-
return <IconsSection state={state} dispatch={dispatch} />;
|
|
175
|
-
case "fonts":
|
|
176
|
-
return <FontsSection state={state} dispatch={dispatch} />;
|
|
177
|
-
case "others":
|
|
178
|
-
return <OthersSection state={state} dispatch={dispatch} />;
|
|
179
|
-
default:
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
40
|
|
|
184
41
|
return (
|
|
185
42
|
<div
|
|
@@ -193,7 +50,7 @@ export function Sidebar({
|
|
|
193
50
|
backgroundColor: bgColor,
|
|
194
51
|
}}
|
|
195
52
|
>
|
|
196
|
-
{/*
|
|
53
|
+
{/* Header */}
|
|
197
54
|
<div
|
|
198
55
|
style={{
|
|
199
56
|
flexShrink: 0,
|
|
@@ -225,80 +82,16 @@ export function Sidebar({
|
|
|
225
82
|
/>
|
|
226
83
|
</div>
|
|
227
84
|
|
|
228
|
-
{/*
|
|
85
|
+
{/* Content area (empty for now) */}
|
|
229
86
|
<div
|
|
230
87
|
style={{
|
|
231
88
|
flex: 1,
|
|
232
89
|
overflowY: "auto",
|
|
233
90
|
overflowX: "hidden",
|
|
234
91
|
}}
|
|
235
|
-
|
|
236
|
-
{ACCORDION_SECTIONS.map((section) => {
|
|
237
|
-
const isOpen = openSections.has(section.id);
|
|
238
|
-
const isHovered = hoveredSectionId === section.id;
|
|
239
|
-
|
|
240
|
-
return (
|
|
241
|
-
<div key={section.id}>
|
|
242
|
-
<button
|
|
243
|
-
onClick={() => toggleSection(section.id)}
|
|
244
|
-
onMouseEnter={() => setHoveredSectionId(section.id)}
|
|
245
|
-
onMouseLeave={() => setHoveredSectionId(null)}
|
|
246
|
-
aria-expanded={isOpen}
|
|
247
|
-
aria-controls={`section-${section.id}`}
|
|
248
|
-
style={{
|
|
249
|
-
display: "flex",
|
|
250
|
-
alignItems: "center",
|
|
251
|
-
justifyContent: "space-between",
|
|
252
|
-
width: "100%",
|
|
253
|
-
padding: "12px 20px",
|
|
254
|
-
border: "none",
|
|
255
|
-
borderBottom: `1px solid ${borderColor}`,
|
|
256
|
-
background: isHovered ? hoverBg : "none",
|
|
257
|
-
cursor: "pointer",
|
|
258
|
-
fontSize: 14,
|
|
259
|
-
fontWeight: 500,
|
|
260
|
-
color: srgbToHex(tokens.textPrimary.srgb),
|
|
261
|
-
transition: "background-color 100ms ease",
|
|
262
|
-
}}
|
|
263
|
-
>
|
|
264
|
-
<span style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
265
|
-
<SectionIcon id={section.id} />
|
|
266
|
-
{section.label}
|
|
267
|
-
</span>
|
|
268
|
-
<svg
|
|
269
|
-
width={12}
|
|
270
|
-
height={12}
|
|
271
|
-
viewBox="0 0 24 24"
|
|
272
|
-
fill="none"
|
|
273
|
-
stroke="currentColor"
|
|
274
|
-
strokeWidth={2}
|
|
275
|
-
style={{
|
|
276
|
-
transform: isOpen ? "rotate(180deg)" : "none",
|
|
277
|
-
transition: "transform 150ms ease",
|
|
278
|
-
}}
|
|
279
|
-
>
|
|
280
|
-
<polyline points="6 9 12 15 18 9" />
|
|
281
|
-
</svg>
|
|
282
|
-
</button>
|
|
283
|
-
{isOpen && (
|
|
284
|
-
<div
|
|
285
|
-
id={`section-${section.id}`}
|
|
286
|
-
role="region"
|
|
287
|
-
aria-label={section.label}
|
|
288
|
-
style={{
|
|
289
|
-
padding: "16px 20px",
|
|
290
|
-
borderBottom: `1px solid ${borderColor}`,
|
|
291
|
-
}}
|
|
292
|
-
>
|
|
293
|
-
{renderSectionContent(section.id)}
|
|
294
|
-
</div>
|
|
295
|
-
)}
|
|
296
|
-
</div>
|
|
297
|
-
);
|
|
298
|
-
})}
|
|
299
|
-
</div>
|
|
92
|
+
/>
|
|
300
93
|
|
|
301
|
-
{/*
|
|
94
|
+
{/* Footer */}
|
|
302
95
|
<div
|
|
303
96
|
style={{
|
|
304
97
|
flexShrink: 0,
|