@newtonedev/editor 0.1.3 → 0.1.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ComponentRenderer.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentRenderer.tsx"],"names":[],"mappings":"AAaA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AAyDD,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,sBAAsB,kDAiC/E"}
1
+ {"version":3,"file":"ComponentRenderer.d.ts","sourceRoot":"","sources":["../../src/preview/ComponentRenderer.tsx"],"names":[],"mappings":"AAiBA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC;AA2ED,wBAAgB,iBAAiB,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,sBAAsB,kDA8D/E"}
package/dist/types.d.ts CHANGED
@@ -8,7 +8,6 @@ export interface Preset {
8
8
  readonly published_state: ConfiguratorState | null;
9
9
  }
10
10
  export type SaveStatus = "saved" | "saving" | "unsaved" | "error";
11
- export type ThemeName = "neutral" | "primary" | "secondary" | "strong";
12
11
  export type PreviewView = {
13
12
  readonly kind: "overview";
14
13
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newtonedev/editor",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Shared color system editor for Newtone applications",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/Editor.tsx CHANGED
@@ -1,9 +1,10 @@
1
+ import { useMemo } from "react";
1
2
  import { NewtoneProvider } from "@newtonedev/components";
3
+ import type { NewtoneThemeConfig } from "@newtonedev/components";
2
4
  import { useEditorState } from "./hooks/useEditorState";
3
5
  import { EditorShell } from "./components/EditorShell";
4
6
  import { Sidebar } from "./components/Sidebar";
5
7
  import { EditorHeader } from "./components/EditorHeader";
6
- import { ThemeBar } from "./components/ThemeBar";
7
8
  import { TableOfContents } from "./components/TableOfContents";
8
9
  import { PreviewWindow } from "./components/PreviewWindow";
9
10
  import { RightSidebar } from "./components/RightSidebar";
@@ -34,6 +35,17 @@ export function Editor({
34
35
  initialPreviewView,
35
36
  });
36
37
 
38
+ // Merge token overrides from chrome config into the preview config.
39
+ // Token overrides (from Token Tuner) are stored separately from configurator
40
+ // state, but should apply to the preview so components render accurately.
41
+ const previewConfig: NewtoneThemeConfig = useMemo(
42
+ () =>
43
+ chromeThemeConfig.tokenOverrides
44
+ ? { ...editor.themeConfig, tokenOverrides: chromeThemeConfig.tokenOverrides }
45
+ : editor.themeConfig,
46
+ [editor.themeConfig, chromeThemeConfig.tokenOverrides],
47
+ );
48
+
37
49
  return (
38
50
  <NewtoneProvider config={chromeThemeConfig}>
39
51
  <EditorShell
@@ -90,15 +102,10 @@ export function Editor({
90
102
  }}
91
103
  >
92
104
  <NewtoneProvider
93
- config={editor.themeConfig}
105
+ config={previewConfig}
94
106
  initialMode={editor.colorMode}
95
- initialTheme={editor.activeTheme}
96
- key={`${editor.colorMode}-${editor.activeTheme}`}
107
+ key={editor.colorMode}
97
108
  >
98
- <ThemeBar
99
- activeTheme={editor.activeTheme}
100
- onThemeChange={editor.handleThemeChange}
101
- />
102
109
  <div style={{ flex: 1, overflowY: "auto", minWidth: 0 }}>
103
110
  <PreviewWindow
104
111
  view={editor.previewView}
@@ -12,7 +12,7 @@ export function CopyButton({ text }: { readonly text: string }) {
12
12
  }, [text]);
13
13
 
14
14
  return (
15
- <Button variant="ghost" size="sm" icon={copied ? "check" : "content_copy"} onPress={handleCopy}>
15
+ <Button variant="tertiary" semantic="neutral" size="sm" icon={copied ? "check" : "content_copy"} onPress={handleCopy}>
16
16
  {copied ? "Copied!" : "Copy"}
17
17
  </Button>
18
18
  );
@@ -34,10 +34,10 @@ export function EditorHeader({
34
34
  const borderColor = srgbToHex(tokens.border.srgb);
35
35
 
36
36
  const statusColor: Record<SaveStatus, string> = {
37
- saved: srgbToHex(tokens.success.srgb),
38
- saving: srgbToHex(tokens.warning.srgb),
37
+ saved: srgbToHex(tokens.success.fill.srgb),
38
+ saving: srgbToHex(tokens.warning.fill.srgb),
39
39
  unsaved: srgbToHex(tokens.textSecondary.srgb),
40
- error: srgbToHex(tokens.error.srgb),
40
+ error: srgbToHex(tokens.error.fill.srgb),
41
41
  };
42
42
 
43
43
  return (
@@ -66,7 +66,7 @@ export function EditorHeader({
66
66
  {STATUS_LABEL[saveStatus]}
67
67
  </span>
68
68
  {saveStatus === "error" && (
69
- <Button variant="ghost" size="sm" icon="refresh" onPress={onRetry}>
69
+ <Button variant="tertiary" semantic="neutral" size="sm" icon="refresh" onPress={onRetry}>
70
70
  Retry
71
71
  </Button>
72
72
  )}
@@ -23,7 +23,7 @@ export function EditorShell({
23
23
  <div
24
24
  style={{
25
25
  display: "flex",
26
- height: "100vh",
26
+ height: "100%",
27
27
  overflow: "hidden",
28
28
  backgroundColor: srgbToHex(tokens.background.srgb),
29
29
  }}
@@ -33,7 +33,7 @@ export function EditorShell({
33
33
  flex: 1,
34
34
  display: "flex",
35
35
  flexDirection: "column",
36
- height: "100vh",
36
+ height: "100%",
37
37
  overflow: "hidden",
38
38
  minWidth: 0,
39
39
  }}
@@ -84,7 +84,7 @@ export function FontPicker({
84
84
  const bgColor = srgbToHex(tokens.backgroundElevated.srgb);
85
85
  const borderColor = srgbToHex(tokens.border.srgb);
86
86
  const hoverColor = srgbToHex(tokens.backgroundSunken.srgb);
87
- const interactiveColor = srgbToHex(tokens.interactive.srgb);
87
+ const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
88
88
 
89
89
  useEffect(() => {
90
90
  if (!isOpen) return;
@@ -44,9 +44,9 @@ export function PresetSelector({
44
44
  const bgColor = srgbToHex(tokens.background.srgb);
45
45
  const textPrimary = srgbToHex(tokens.textPrimary.srgb);
46
46
  const textSecondary = srgbToHex(tokens.textSecondary.srgb);
47
- const interactiveColor = srgbToHex(tokens.interactive.srgb);
48
- const warningColor = srgbToHex(tokens.warning.srgb);
49
- const errorColor = srgbToHex(tokens.error.srgb);
47
+ const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
48
+ const warningColor = srgbToHex(tokens.warning.fill.srgb);
49
+ const errorColor = srgbToHex(tokens.error.fill.srgb);
50
50
  const hoverBg = `${borderColor}18`;
51
51
  const activeBg = `${interactiveColor}14`;
52
52
 
@@ -105,7 +105,7 @@ export function RightSidebar({
105
105
  padding: 0,
106
106
  fontSize: 14,
107
107
  fontWeight: 500,
108
- color: srgbToHex(tokens.interactive.srgb),
108
+ color: srgbToHex(tokens.accent.fill.srgb),
109
109
  whiteSpace: "nowrap",
110
110
  }}
111
111
  >
@@ -339,7 +339,7 @@ function PropControl({
339
339
  height: 20,
340
340
  borderRadius: 10,
341
341
  backgroundColor: value
342
- ? srgbToHex(tokens.interactive.srgb)
342
+ ? srgbToHex(tokens.accent.fill.srgb)
343
343
  : srgbToHex(tokens.border.srgb),
344
344
  position: "relative",
345
345
  transition: "background-color 150ms ease",
@@ -24,7 +24,7 @@ export function TableOfContents({
24
24
  const [hoveredId, setHoveredId] = useState<string | null>(null);
25
25
 
26
26
  const borderColor = srgbToHex(tokens.border.srgb);
27
- const activeColor = srgbToHex(tokens.interactive.srgb);
27
+ const activeColor = srgbToHex(tokens.accent.fill.srgb);
28
28
  const textPrimary = srgbToHex(tokens.textPrimary.srgb);
29
29
  const textSecondary = srgbToHex(tokens.textSecondary.srgb);
30
30
  const hoverBg = `${borderColor}20`;
@@ -61,7 +61,7 @@ export function ColorsSection({
61
61
  const hueRange = SEMANTIC_HUE_RANGES[activePaletteIndex];
62
62
  const isNeutral = activePaletteIndex === 0;
63
63
 
64
- const activeColor = srgbToHex(tokens.interactive.srgb);
64
+ const activeColor = srgbToHex(tokens.accent.fill.srgb);
65
65
  const borderColor = srgbToHex(tokens.border.srgb);
66
66
 
67
67
  // Resolve effective key color for current mode
@@ -350,7 +350,7 @@ export function ColorsSection({
350
350
  style={{
351
351
  fontSize: 12,
352
352
  fontWeight: 500,
353
- color: srgbToHex(tokens.error.srgb),
353
+ color: srgbToHex(tokens.error.fill.srgb),
354
354
  }}
355
355
  >
356
356
  {hexError}
@@ -1,9 +1,20 @@
1
- import { useState, useRef, useCallback } from "react";
1
+ import { useState, useRef, useCallback, useMemo, useEffect } from "react";
2
2
  import { HueSlider, Select, useTokens } from "@newtonedev/components";
3
- import { srgbToHex } from "newtone";
3
+ import {
4
+ srgbToHex,
5
+ resolveLightness,
6
+ findMaxChromaInGamut,
7
+ oklchToSrgb,
8
+ clampSrgb,
9
+ HUE_GRADING_STRENGTH_LOW,
10
+ HUE_GRADING_STRENGTH_MEDIUM,
11
+ HUE_GRADING_STRENGTH_HARD,
12
+ HUE_GRADING_EASING_POWER,
13
+ } from "newtone";
4
14
  import type { HueGradingStrength } from "newtone";
5
15
  import type { ConfiguratorState } from "@newtonedev/configurator";
6
16
  import type { ConfiguratorAction } from "@newtonedev/configurator";
17
+ import { traditionalHueToOklch } from "@newtonedev/configurator";
7
18
 
8
19
  const STRENGTH_OPTIONS = [
9
20
  { label: "None", value: "none" },
@@ -76,7 +87,7 @@ function DualRangeSlider({
76
87
  "whites" | "blacks" | null
77
88
  >(null);
78
89
 
79
- const interactiveColor = srgbToHex(tokens.interactive.srgb);
90
+ const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
80
91
  const borderColor = srgbToHex(tokens.border.srgb);
81
92
 
82
93
  const wDisplay = internalToDisplay(whitesValue);
@@ -264,6 +275,215 @@ function RangeInput({ display, onCommit, toInternal }: RangeInputProps) {
264
275
  );
265
276
  }
266
277
 
278
+ // --- Dynamic Range Graph ---
279
+
280
+ const GRAPH_HEIGHT = 80;
281
+ const GRAPH_COLS = 256;
282
+ const GRAPH_ROWS = 64;
283
+
284
+ function strengthToFactor(strength: HueGradingStrength): number {
285
+ switch (strength) {
286
+ case "none":
287
+ return 0;
288
+ case "low":
289
+ return HUE_GRADING_STRENGTH_LOW;
290
+ case "medium":
291
+ return HUE_GRADING_STRENGTH_MEDIUM;
292
+ case "hard":
293
+ return HUE_GRADING_STRENGTH_HARD;
294
+ }
295
+ }
296
+
297
+ function blendHues(
298
+ lightHue: number,
299
+ darkHue: number,
300
+ wLight: number,
301
+ wDark: number,
302
+ ): number {
303
+ const totalW = wLight + wDark;
304
+ if (totalW === 0) return 0;
305
+ const delta = (((darkHue - lightHue + 180) % 360) + 360) % 360 - 180;
306
+ const t = wDark / totalW;
307
+ const result = lightHue + delta * t;
308
+ return ((result % 360) + 360) % 360;
309
+ }
310
+
311
+ interface GraphData {
312
+ readonly buffer: Uint8ClampedArray;
313
+ readonly curvePoints: readonly { readonly x: number; readonly y: number }[];
314
+ }
315
+
316
+ function computeGraphData(state: ConfiguratorState): GraphData {
317
+ const { dynamicRange, globalHueGrading } = state;
318
+
319
+ const lightActive = globalHueGrading.light.strength !== "none";
320
+ const darkActive = globalHueGrading.dark.strength !== "none";
321
+ const lightOklchHue = traditionalHueToOklch(globalHueGrading.light.hue);
322
+ const darkOklchHue = traditionalHueToOklch(globalHueGrading.dark.hue);
323
+ const lightFactor = strengthToFactor(globalHueGrading.light.strength);
324
+ const darkFactor = strengthToFactor(globalHueGrading.dark.strength);
325
+
326
+ const buffer = new Uint8ClampedArray(GRAPH_COLS * GRAPH_ROWS * 4);
327
+
328
+ for (let col = 0; col < GRAPH_COLS; col++) {
329
+ const nv = 1 - col / (GRAPH_COLS - 1);
330
+ const L = resolveLightness(dynamicRange, nv);
331
+
332
+ // Easing weights for hue blend at top row (assumes hard strength)
333
+ const wLight = lightActive ? Math.pow(nv, HUE_GRADING_EASING_POWER) : 0;
334
+ const wDark = darkActive
335
+ ? Math.pow(1 - nv, HUE_GRADING_EASING_POWER)
336
+ : 0;
337
+ const totalW = wLight + wDark;
338
+
339
+ let topHue: number;
340
+ let topChroma: number;
341
+
342
+ if (totalW === 0) {
343
+ topHue = 0;
344
+ topChroma = 0;
345
+ } else {
346
+ if (!lightActive) {
347
+ topHue = darkOklchHue;
348
+ } else if (!darkActive) {
349
+ topHue = lightOklchHue;
350
+ } else {
351
+ topHue = blendHues(lightOklchHue, darkOklchHue, wLight, wDark);
352
+ }
353
+ topChroma =
354
+ findMaxChromaInGamut(L, topHue) * Math.min(totalW, 1);
355
+ }
356
+
357
+ for (let row = 0; row < GRAPH_ROWS; row++) {
358
+ const gradingIntensity = row / (GRAPH_ROWS - 1);
359
+ const C = topChroma * gradingIntensity;
360
+ const srgb = clampSrgb(oklchToSrgb({ L, C, h: topHue }));
361
+
362
+ // Canvas Y=0 is top; row=0 is bottom of our graph
363
+ const canvasY = GRAPH_ROWS - 1 - row;
364
+ const idx = (canvasY * GRAPH_COLS + col) * 4;
365
+ buffer[idx] = Math.round(srgb.r * 255);
366
+ buffer[idx + 1] = Math.round(srgb.g * 255);
367
+ buffer[idx + 2] = Math.round(srgb.b * 255);
368
+ buffer[idx + 3] = 255;
369
+ }
370
+ }
371
+
372
+ // 26-step curve points
373
+ const curvePoints: { x: number; y: number }[] = [];
374
+ for (let i = 0; i < 26; i++) {
375
+ const nv = 1 - i / 25;
376
+ const x = (i / 25) * (GRAPH_COLS - 1);
377
+
378
+ const lightContrib =
379
+ Math.pow(nv, HUE_GRADING_EASING_POWER) *
380
+ (lightFactor / HUE_GRADING_STRENGTH_HARD);
381
+ const darkContrib =
382
+ Math.pow(1 - nv, HUE_GRADING_EASING_POWER) *
383
+ (darkFactor / HUE_GRADING_STRENGTH_HARD);
384
+ const y = clamp(lightContrib + darkContrib, 0, 1);
385
+
386
+ curvePoints.push({ x, y });
387
+ }
388
+
389
+ return { buffer, curvePoints };
390
+ }
391
+
392
+ interface DynamicRangeGraphProps {
393
+ readonly state: ConfiguratorState;
394
+ }
395
+
396
+ function DynamicRangeGraph({ state }: DynamicRangeGraphProps) {
397
+ const tokens = useTokens();
398
+ const canvasRef = useRef<HTMLCanvasElement>(null);
399
+
400
+ const graphData = useMemo(
401
+ () => computeGraphData(state),
402
+ [
403
+ state.dynamicRange.lightest,
404
+ state.dynamicRange.darkest,
405
+ state.globalHueGrading.light.strength,
406
+ state.globalHueGrading.light.hue,
407
+ state.globalHueGrading.dark.strength,
408
+ state.globalHueGrading.dark.hue,
409
+ ],
410
+ );
411
+
412
+ useEffect(() => {
413
+ const canvas = canvasRef.current;
414
+ if (!canvas) return;
415
+
416
+ canvas.width = GRAPH_COLS;
417
+ canvas.height = GRAPH_ROWS;
418
+
419
+ const ctx = canvas.getContext("2d");
420
+ if (!ctx) return;
421
+
422
+ // Draw gradient from pre-computed buffer
423
+ const imageData = ctx.createImageData(GRAPH_COLS, GRAPH_ROWS);
424
+ imageData.data.set(graphData.buffer);
425
+ ctx.putImageData(imageData, 0, 0);
426
+
427
+ // Draw 26-step curve overlay
428
+ const curveColor = srgbToHex(tokens.accent.fill.srgb);
429
+ const { curvePoints } = graphData;
430
+
431
+ if (curvePoints.length < 2) return;
432
+
433
+ const mapped = curvePoints.map((p) => ({
434
+ cx: p.x,
435
+ cy: (1 - p.y) * (GRAPH_ROWS - 1),
436
+ }));
437
+
438
+ // Smooth Catmull-Rom spline
439
+ ctx.beginPath();
440
+ ctx.strokeStyle = curveColor;
441
+ ctx.lineWidth = 1.5;
442
+ ctx.lineJoin = "round";
443
+ ctx.lineCap = "round";
444
+
445
+ ctx.moveTo(mapped[0].cx, mapped[0].cy);
446
+ for (let i = 0; i < mapped.length - 1; i++) {
447
+ const p0 = mapped[Math.max(0, i - 1)];
448
+ const p1 = mapped[i];
449
+ const p2 = mapped[i + 1];
450
+ const p3 = mapped[Math.min(mapped.length - 1, i + 2)];
451
+
452
+ const cp1x = p1.cx + (p2.cx - p0.cx) / 6;
453
+ const cp1y = p1.cy + (p2.cy - p0.cy) / 6;
454
+ const cp2x = p2.cx - (p3.cx - p1.cx) / 6;
455
+ const cp2y = p2.cy - (p3.cy - p1.cy) / 6;
456
+
457
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.cx, p2.cy);
458
+ }
459
+ ctx.stroke();
460
+
461
+ // Draw dots at each of the 26 steps
462
+ ctx.fillStyle = curveColor;
463
+ for (const p of mapped) {
464
+ ctx.beginPath();
465
+ ctx.arc(p.cx, p.cy, 2, 0, Math.PI * 2);
466
+ ctx.fill();
467
+ }
468
+ }, [graphData, tokens]);
469
+
470
+ const borderColor = srgbToHex(tokens.border.srgb);
471
+
472
+ return (
473
+ <canvas
474
+ ref={canvasRef}
475
+ style={{
476
+ width: "100%",
477
+ height: GRAPH_HEIGHT,
478
+ borderRadius: 6,
479
+ border: `1px solid ${borderColor}`,
480
+ display: "block",
481
+ overflow: "hidden",
482
+ }}
483
+ />
484
+ );
485
+ }
486
+
267
487
  // --- Section ---
268
488
 
269
489
  interface DynamicRangeSectionProps {
@@ -291,6 +511,9 @@ export function DynamicRangeSection({
291
511
 
292
512
  return (
293
513
  <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
514
+ {/* Dynamic range graph */}
515
+ <DynamicRangeGraph state={state} />
516
+
294
517
  {/* Labels above slider */}
295
518
  <div
296
519
  style={{
@@ -7,7 +7,6 @@ import { usePresets } from "./usePresets";
7
7
  import type {
8
8
  Preset,
9
9
  SaveStatus,
10
- ThemeName,
11
10
  PreviewView,
12
11
  SidebarSelection,
13
12
  EditorPersistence,
@@ -50,9 +49,6 @@ export function useEditorState({
50
49
  const [colorMode, setColorMode] = useState<ColorMode>(
51
50
  initialState.preview.mode,
52
51
  );
53
- const [activeTheme, setActiveTheme] = useState<ThemeName>(
54
- (initialState.preview.theme as ThemeName) || "neutral",
55
- );
56
52
  const [previewView, setPreviewView] = useState<PreviewView>(
57
53
  initialPreviewView ?? { kind: "overview" },
58
54
  );
@@ -253,16 +249,6 @@ export function useEditorState({
253
249
  scheduleSave();
254
250
  }, [configuratorState, scheduleSave]);
255
251
 
256
- // --- ThemeBar dispatch handlers ---
257
-
258
- const handleThemeChange = useCallback(
259
- (theme: ThemeName) => {
260
- setActiveTheme(theme);
261
- dispatch({ type: "SET_PREVIEW_THEME", theme });
262
- },
263
- [dispatch],
264
- );
265
-
266
252
  const handleColorModeChange = useCallback(
267
253
  (mode: ColorMode) => {
268
254
  setColorMode(mode);
@@ -345,10 +331,8 @@ export function useEditorState({
345
331
  // Preview
346
332
  previewView,
347
333
  colorMode,
348
- activeTheme,
349
334
  handlePreviewNavigate,
350
335
  handleSelectVariant,
351
- handleThemeChange,
352
336
  handleColorModeChange,
353
337
 
354
338
  // Sidebar
package/src/index.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  export type {
3
3
  Preset,
4
4
  SaveStatus,
5
- ThemeName,
6
5
  PreviewView,
7
6
  SidebarSelection,
8
7
  EditorPersistence,
@@ -35,7 +34,6 @@ export { PreviewWindow } from "./components/PreviewWindow";
35
34
  export { RightSidebar } from "./components/RightSidebar";
36
35
  export { Sidebar } from "./components/Sidebar";
37
36
  export { TableOfContents } from "./components/TableOfContents";
38
- export { ThemeBar } from "./components/ThemeBar";
39
37
 
40
38
  // Sections
41
39
  export {
@@ -66,7 +66,7 @@ export function CategoryView({
66
66
  padding: 24,
67
67
  borderRadius: 12,
68
68
  border: `1px solid ${srgbToHex(
69
- isHovered ? tokens.interactive.srgb : tokens.border.srgb,
69
+ isHovered ? tokens.accent.fill.srgb : tokens.border.srgb,
70
70
  )}`,
71
71
  backgroundColor: srgbToHex(tokens.backgroundElevated.srgb),
72
72
  cursor: "pointer",
@@ -23,7 +23,7 @@ export function ComponentDetailView({
23
23
 
24
24
  if (!component) return null;
25
25
 
26
- const interactiveColor = srgbToHex(tokens.interactive.srgb);
26
+ const interactiveColor = srgbToHex(tokens.accent.fill.srgb);
27
27
 
28
28
  return (
29
29
  <div style={{ padding: 32 }}>
@@ -7,6 +7,10 @@ import {
7
7
  Toggle,
8
8
  Slider,
9
9
  HueSlider,
10
+ Frame,
11
+ Text,
12
+ Icon,
13
+ Wrapper,
10
14
  useTokens,
11
15
  } from "@newtonedev/components";
12
16
  import { srgbToHex } from "newtone";
@@ -71,6 +75,24 @@ function CardPreview(props: AnyProps) {
71
75
  );
72
76
  }
73
77
 
78
+ function FramePreview(props: AnyProps) {
79
+ return (
80
+ <Frame {...props} style={{ minWidth: 200, minHeight: 60 }}>
81
+ <Text size="sm">Frame content</Text>
82
+ </Frame>
83
+ );
84
+ }
85
+
86
+ function WrapperPreview(props: AnyProps) {
87
+ return (
88
+ <Wrapper {...props} style={{ minWidth: 200 }}>
89
+ <Text size="sm">Item 1</Text>
90
+ <Text size="sm">Item 2</Text>
91
+ <Text size="sm">Item 3</Text>
92
+ </Wrapper>
93
+ );
94
+ }
95
+
74
96
  export function ComponentRenderer({ componentId, props }: ComponentRendererProps) {
75
97
  const noop = useCallback(() => {}, []);
76
98
 
@@ -80,9 +102,11 @@ export function ComponentRenderer({ componentId, props }: ComponentRendererProps
80
102
  return (
81
103
  <Button
82
104
  variant={props.variant as AnyProps}
105
+ semantic={props.semantic as AnyProps}
83
106
  size={props.size as AnyProps}
84
107
  icon={icon}
85
108
  iconPosition={props.iconPosition as AnyProps}
109
+ disabled={props.disabled as AnyProps}
86
110
  onPress={noop}
87
111
  >
88
112
  Button
@@ -101,6 +125,33 @@ export function ComponentRenderer({ componentId, props }: ComponentRendererProps
101
125
  return <StatefulHueSlider {...props} />;
102
126
  case "card":
103
127
  return <CardPreview {...props} />;
128
+ case "text":
129
+ return (
130
+ <Text
131
+ size={props.size as AnyProps}
132
+ weight={props.weight as AnyProps}
133
+ color={props.color as AnyProps}
134
+ font={props.font as AnyProps}
135
+ >
136
+ The quick brown fox
137
+ </Text>
138
+ );
139
+ case "icon":
140
+ return (
141
+ <Icon
142
+ name={(props.name as string) ?? "home"}
143
+ size={props.size as AnyProps}
144
+ fill={props.fill as AnyProps}
145
+ />
146
+ );
147
+ case "frame":
148
+ return (
149
+ <FramePreview {...props} />
150
+ );
151
+ case "wrapper":
152
+ return (
153
+ <WrapperPreview {...props} />
154
+ );
104
155
  default:
105
156
  return null;
106
157
  }
@@ -128,7 +128,7 @@ function ComponentCard({
128
128
  padding: 20,
129
129
  borderRadius: 12,
130
130
  border: `1px solid ${srgbToHex(
131
- isHovered ? tokens.interactive.srgb : tokens.border.srgb,
131
+ isHovered ? tokens.accent.fill.srgb : tokens.border.srgb,
132
132
  )}`,
133
133
  backgroundColor: srgbToHex(tokens.backgroundElevated.srgb),
134
134
  cursor: "pointer",
package/src/types.ts CHANGED
@@ -15,8 +15,6 @@ export interface Preset {
15
15
 
16
16
  export type SaveStatus = "saved" | "saving" | "unsaved" | "error";
17
17
 
18
- export type ThemeName = "neutral" | "primary" | "secondary" | "strong";
19
-
20
18
  export type PreviewView =
21
19
  | { readonly kind: "overview" }
22
20
  | { readonly kind: "category"; readonly categoryId: string }
@@ -1,8 +0,0 @@
1
- import type { ThemeName } from "../types";
2
- interface ThemeBarProps {
3
- readonly activeTheme: ThemeName;
4
- readonly onThemeChange: (theme: ThemeName) => void;
5
- }
6
- export declare function ThemeBar({ activeTheme, onThemeChange }: ThemeBarProps): import("react/jsx-runtime").JSX.Element;
7
- export {};
8
- //# sourceMappingURL=ThemeBar.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ThemeBar.d.ts","sourceRoot":"","sources":["../../src/components/ThemeBar.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,UAAU,aAAa;IACrB,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACpD;AASD,wBAAgB,QAAQ,CAAC,EAAE,WAAW,EAAE,aAAa,EAAE,EAAE,aAAa,2CA0DrE"}