@newtonedev/configurator 0.1.0

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.
Files changed (62) hide show
  1. package/dist/Configurator.d.ts +13 -0
  2. package/dist/Configurator.d.ts.map +1 -0
  3. package/dist/Configurator.types.d.ts +13 -0
  4. package/dist/Configurator.types.d.ts.map +1 -0
  5. package/dist/bridge/toCSS.d.ts +7 -0
  6. package/dist/bridge/toCSS.d.ts.map +1 -0
  7. package/dist/bridge/toJSON.d.ts +15 -0
  8. package/dist/bridge/toJSON.d.ts.map +1 -0
  9. package/dist/bridge/toThemeConfig.d.ts +8 -0
  10. package/dist/bridge/toThemeConfig.d.ts.map +1 -0
  11. package/dist/constants.d.ts +16 -0
  12. package/dist/constants.d.ts.map +1 -0
  13. package/dist/hooks/useConfigurator.d.ts +11 -0
  14. package/dist/hooks/useConfigurator.d.ts.map +1 -0
  15. package/dist/hooks/usePreviewColors.d.ts +8 -0
  16. package/dist/hooks/usePreviewColors.d.ts.map +1 -0
  17. package/dist/hue-conversion.d.ts +10 -0
  18. package/dist/hue-conversion.d.ts.map +1 -0
  19. package/dist/index.cjs +827 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.ts +14 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +814 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/panels/CanvasPanel.d.ts +8 -0
  26. package/dist/panels/CanvasPanel.d.ts.map +1 -0
  27. package/dist/panels/ExportPanel.d.ts +8 -0
  28. package/dist/panels/ExportPanel.d.ts.map +1 -0
  29. package/dist/panels/GlobalPanel.d.ts +10 -0
  30. package/dist/panels/GlobalPanel.d.ts.map +1 -0
  31. package/dist/panels/PalettePanel.d.ts +13 -0
  32. package/dist/panels/PalettePanel.d.ts.map +1 -0
  33. package/dist/panels/PreviewPanel.d.ts +12 -0
  34. package/dist/panels/PreviewPanel.d.ts.map +1 -0
  35. package/dist/state/actions.d.ts +62 -0
  36. package/dist/state/actions.d.ts.map +1 -0
  37. package/dist/state/defaults.d.ts +9 -0
  38. package/dist/state/defaults.d.ts.map +1 -0
  39. package/dist/state/reducer.d.ts +4 -0
  40. package/dist/state/reducer.d.ts.map +1 -0
  41. package/dist/types.d.ts +35 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/package.json +56 -0
  44. package/src/Configurator.tsx +177 -0
  45. package/src/Configurator.types.ts +13 -0
  46. package/src/bridge/toCSS.ts +43 -0
  47. package/src/bridge/toJSON.ts +24 -0
  48. package/src/bridge/toThemeConfig.ts +58 -0
  49. package/src/constants.ts +16 -0
  50. package/src/hooks/useConfigurator.ts +30 -0
  51. package/src/hooks/usePreviewColors.ts +40 -0
  52. package/src/hue-conversion.ts +25 -0
  53. package/src/index.ts +24 -0
  54. package/src/panels/CanvasPanel.tsx +90 -0
  55. package/src/panels/ExportPanel.tsx +95 -0
  56. package/src/panels/GlobalPanel.tsx +126 -0
  57. package/src/panels/PalettePanel.tsx +145 -0
  58. package/src/panels/PreviewPanel.tsx +124 -0
  59. package/src/state/actions.ts +27 -0
  60. package/src/state/defaults.ts +74 -0
  61. package/src/state/reducer.ts +163 -0
  62. package/src/types.ts +37 -0
package/dist/index.js ADDED
@@ -0,0 +1,814 @@
1
+ import React3, { useReducer, useMemo, useCallback, useRef, useState, useEffect } from 'react';
2
+ import { StyleSheet, View, Pressable, Text } from 'react-native';
3
+ import { computeTokens, useTokens, useNewtoneTheme, Toggle, Card, Slider, Select, HueSlider, NewtoneProvider, Button, TextInput } from '@newtonedev/components';
4
+ import { srgbToOklch, generatePreview, srgbToHex } from 'newtone';
5
+
6
+ // src/Configurator.tsx
7
+
8
+ // src/state/defaults.ts
9
+ var DEFAULT_CONFIGURATOR_STATE = {
10
+ palettes: [
11
+ {
12
+ name: "Neutral",
13
+ hue: 220,
14
+ saturation: 24,
15
+ desaturationStrength: "none",
16
+ desaturationDirection: "light",
17
+ hueGradeStrength: "none",
18
+ hueGradeHue: 0,
19
+ hueGradeDirection: "light"
20
+ },
21
+ {
22
+ name: "Accent",
23
+ hue: 24,
24
+ saturation: 80,
25
+ desaturationStrength: "none",
26
+ desaturationDirection: "light",
27
+ hueGradeStrength: "none",
28
+ hueGradeHue: 0,
29
+ hueGradeDirection: "light"
30
+ },
31
+ {
32
+ name: "Success",
33
+ hue: 145,
34
+ saturation: 64,
35
+ desaturationStrength: "none",
36
+ desaturationDirection: "light",
37
+ hueGradeStrength: "none",
38
+ hueGradeHue: 0,
39
+ hueGradeDirection: "light"
40
+ },
41
+ {
42
+ name: "Warning",
43
+ hue: 40,
44
+ saturation: 80,
45
+ desaturationStrength: "none",
46
+ desaturationDirection: "light",
47
+ hueGradeStrength: "none",
48
+ hueGradeHue: 0,
49
+ hueGradeDirection: "light"
50
+ },
51
+ {
52
+ name: "Error",
53
+ hue: 0,
54
+ saturation: 80,
55
+ desaturationStrength: "none",
56
+ desaturationDirection: "light",
57
+ hueGradeStrength: "none",
58
+ hueGradeHue: 0,
59
+ hueGradeDirection: "light"
60
+ }
61
+ ],
62
+ dynamicRange: {
63
+ lightest: 1,
64
+ darkest: 1
65
+ },
66
+ globalHueGrading: {
67
+ light: { strength: "none", hue: 30 },
68
+ dark: { strength: "none", hue: 220 }
69
+ },
70
+ preview: {
71
+ mode: "light",
72
+ theme: "neutral"
73
+ }
74
+ };
75
+
76
+ // src/state/reducer.ts
77
+ function updatePalette(palettes, index, update) {
78
+ if (index < 0 || index >= palettes.length) return palettes;
79
+ return palettes.map((p, i) => i === index ? { ...p, ...update } : p);
80
+ }
81
+ function clamp(value, min, max) {
82
+ return Math.max(min, Math.min(max, value));
83
+ }
84
+ function wrapHue(hue) {
85
+ return (hue % 360 + 360) % 360;
86
+ }
87
+ function configuratorReducer(state, action) {
88
+ switch (action.type) {
89
+ // Palette actions
90
+ case "SET_PALETTE_HUE":
91
+ return {
92
+ ...state,
93
+ palettes: updatePalette(state.palettes, action.index, {
94
+ hue: wrapHue(action.hue)
95
+ })
96
+ };
97
+ case "SET_PALETTE_SATURATION":
98
+ return {
99
+ ...state,
100
+ palettes: updatePalette(state.palettes, action.index, {
101
+ saturation: clamp(action.saturation, 0, 100)
102
+ })
103
+ };
104
+ case "SET_PALETTE_DESAT_STRENGTH":
105
+ return {
106
+ ...state,
107
+ palettes: updatePalette(state.palettes, action.index, {
108
+ desaturationStrength: action.strength
109
+ })
110
+ };
111
+ case "SET_PALETTE_DESAT_DIRECTION":
112
+ return {
113
+ ...state,
114
+ palettes: updatePalette(state.palettes, action.index, {
115
+ desaturationDirection: action.direction
116
+ })
117
+ };
118
+ case "SET_PALETTE_HUE_GRADE_STRENGTH":
119
+ return {
120
+ ...state,
121
+ palettes: updatePalette(state.palettes, action.index, {
122
+ hueGradeStrength: action.strength
123
+ })
124
+ };
125
+ case "SET_PALETTE_HUE_GRADE_HUE":
126
+ return {
127
+ ...state,
128
+ palettes: updatePalette(state.palettes, action.index, {
129
+ hueGradeHue: wrapHue(action.hue)
130
+ })
131
+ };
132
+ case "SET_PALETTE_HUE_GRADE_DIRECTION":
133
+ return {
134
+ ...state,
135
+ palettes: updatePalette(state.palettes, action.index, {
136
+ hueGradeDirection: action.direction
137
+ })
138
+ };
139
+ // Dynamic range actions
140
+ case "SET_LIGHTEST":
141
+ return {
142
+ ...state,
143
+ dynamicRange: {
144
+ ...state.dynamicRange,
145
+ lightest: clamp(action.value, 0, 1)
146
+ }
147
+ };
148
+ case "SET_DARKEST":
149
+ return {
150
+ ...state,
151
+ dynamicRange: {
152
+ ...state.dynamicRange,
153
+ darkest: clamp(action.value, 0, 1)
154
+ }
155
+ };
156
+ // Global hue grading actions
157
+ case "SET_GLOBAL_GRADE_LIGHT_STRENGTH":
158
+ return {
159
+ ...state,
160
+ globalHueGrading: {
161
+ ...state.globalHueGrading,
162
+ light: { ...state.globalHueGrading.light, strength: action.strength }
163
+ }
164
+ };
165
+ case "SET_GLOBAL_GRADE_LIGHT_HUE":
166
+ return {
167
+ ...state,
168
+ globalHueGrading: {
169
+ ...state.globalHueGrading,
170
+ light: { ...state.globalHueGrading.light, hue: wrapHue(action.hue) }
171
+ }
172
+ };
173
+ case "SET_GLOBAL_GRADE_DARK_STRENGTH":
174
+ return {
175
+ ...state,
176
+ globalHueGrading: {
177
+ ...state.globalHueGrading,
178
+ dark: { ...state.globalHueGrading.dark, strength: action.strength }
179
+ }
180
+ };
181
+ case "SET_GLOBAL_GRADE_DARK_HUE":
182
+ return {
183
+ ...state,
184
+ globalHueGrading: {
185
+ ...state.globalHueGrading,
186
+ dark: { ...state.globalHueGrading.dark, hue: wrapHue(action.hue) }
187
+ }
188
+ };
189
+ // Preview actions
190
+ case "SET_PREVIEW_MODE":
191
+ return {
192
+ ...state,
193
+ preview: { ...state.preview, mode: action.mode }
194
+ };
195
+ case "SET_PREVIEW_THEME":
196
+ return {
197
+ ...state,
198
+ preview: { ...state.preview, theme: action.theme }
199
+ };
200
+ // Control actions
201
+ case "RESET":
202
+ return DEFAULT_CONFIGURATOR_STATE;
203
+ case "LOAD_STATE":
204
+ return action.state;
205
+ default:
206
+ return state;
207
+ }
208
+ }
209
+ function traditionalHueToOklch(hue) {
210
+ const h = (hue % 360 + 360) % 360;
211
+ const x = 1 - Math.abs(h / 60 % 2 - 1);
212
+ let r, g, b;
213
+ if (h < 60) {
214
+ r = 1;
215
+ g = x;
216
+ b = 0;
217
+ } else if (h < 120) {
218
+ r = x;
219
+ g = 1;
220
+ b = 0;
221
+ } else if (h < 180) {
222
+ r = 0;
223
+ g = 1;
224
+ b = x;
225
+ } else if (h < 240) {
226
+ r = 0;
227
+ g = x;
228
+ b = 1;
229
+ } else if (h < 300) {
230
+ r = x;
231
+ g = 0;
232
+ b = 1;
233
+ } else {
234
+ r = 1;
235
+ g = 0;
236
+ b = x;
237
+ }
238
+ return srgbToOklch({ r, g, b }).h;
239
+ }
240
+
241
+ // src/bridge/toThemeConfig.ts
242
+ function toThemeConfig(state) {
243
+ const palettes = state.palettes.map((p) => {
244
+ const oklchHue = traditionalHueToOklch(p.hue);
245
+ const desaturation = p.desaturationStrength !== "none" ? { direction: p.desaturationDirection, strength: p.desaturationStrength } : void 0;
246
+ const paletteHueGrading = p.hueGradeStrength !== "none" ? {
247
+ hue: traditionalHueToOklch(p.hueGradeHue),
248
+ strength: p.hueGradeStrength,
249
+ direction: p.hueGradeDirection
250
+ } : void 0;
251
+ return { hue: oklchHue, saturation: p.saturation, desaturation, paletteHueGrading };
252
+ });
253
+ const light = state.globalHueGrading.light.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.light.hue), strength: state.globalHueGrading.light.strength } : void 0;
254
+ const dark = state.globalHueGrading.dark.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.dark.hue), strength: state.globalHueGrading.dark.strength } : void 0;
255
+ const hueGrading = light || dark ? { light, dark } : void 0;
256
+ const dynamicRange = {
257
+ lightest: state.dynamicRange.lightest,
258
+ darkest: state.dynamicRange.darkest,
259
+ ...hueGrading ? { hueGrading } : {}
260
+ };
261
+ return {
262
+ colorSystem: { dynamicRange, palettes },
263
+ themes: {
264
+ neutral: { paletteIndex: 0, lightModeNv: 0.95, darkModeNv: 0.1 },
265
+ primary: { paletteIndex: 1, lightModeNv: 0.95, darkModeNv: 0.1 },
266
+ secondary: { paletteIndex: 1, lightModeNv: 0.85, darkModeNv: 0.15 },
267
+ strong: { paletteIndex: 0, lightModeNv: 0.1, darkModeNv: 0.95 }
268
+ },
269
+ elevation: {
270
+ offsets: [-0.02, 0, 0.04]
271
+ }
272
+ };
273
+ }
274
+
275
+ // src/hooks/useConfigurator.ts
276
+ function useConfigurator(initialState) {
277
+ const mergedInitial = initialState ? { ...DEFAULT_CONFIGURATOR_STATE, ...initialState } : DEFAULT_CONFIGURATOR_STATE;
278
+ const [state, dispatch] = useReducer(configuratorReducer, mergedInitial);
279
+ const themeConfig = useMemo(() => toThemeConfig(state), [state]);
280
+ const reset = useCallback(() => dispatch({ type: "RESET" }), []);
281
+ return { state, dispatch, themeConfig, reset };
282
+ }
283
+ var PREVIEW_STEPS = 26;
284
+ function usePreviewColors(state) {
285
+ return useMemo(() => {
286
+ const light = state.globalHueGrading.light.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.light.hue), strength: state.globalHueGrading.light.strength } : void 0;
287
+ const dark = state.globalHueGrading.dark.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.dark.hue), strength: state.globalHueGrading.dark.strength } : void 0;
288
+ const hueGrading = light || dark ? { light, dark } : void 0;
289
+ const dynamicRange = {
290
+ lightest: state.dynamicRange.lightest,
291
+ darkest: state.dynamicRange.darkest,
292
+ ...hueGrading ? { hueGrading } : {}
293
+ };
294
+ return state.palettes.map((p) => {
295
+ const oklchHue = traditionalHueToOklch(p.hue);
296
+ const desat = p.desaturationStrength !== "none" ? { direction: p.desaturationDirection, strength: p.desaturationStrength } : void 0;
297
+ const phg = p.hueGradeStrength !== "none" ? { hue: traditionalHueToOklch(p.hueGradeHue), strength: p.hueGradeStrength, direction: p.hueGradeDirection } : void 0;
298
+ return generatePreview(oklchHue, p.saturation, dynamicRange, PREVIEW_STEPS, desat, phg);
299
+ });
300
+ }, [state]);
301
+ }
302
+
303
+ // src/constants.ts
304
+ var SEMANTIC_HUE_RANGES = {
305
+ 2: { min: 80, max: 160 },
306
+ // Success (greens)
307
+ 3: { min: 25, max: 55 },
308
+ // Warning (yellow/orange)
309
+ 4: { min: 345, max: 375 }
310
+ // Error (reds, wraps 0°)
311
+ };
312
+
313
+ // src/panels/PalettePanel.tsx
314
+ var STRENGTH_OPTIONS = [
315
+ { label: "None", value: "none" },
316
+ { label: "Low", value: "low" },
317
+ { label: "Medium", value: "medium" },
318
+ { label: "Hard", value: "hard" }
319
+ ];
320
+ function PalettePanel({ palette, index, dispatch, previewColors }) {
321
+ const tokens = useTokens(1);
322
+ const hueRange = SEMANTIC_HUE_RANGES[index];
323
+ return /* @__PURE__ */ React3.createElement(Card, { elevation: 1, style: styles.container }, /* @__PURE__ */ React3.createElement(Text, { style: [styles.title, { color: srgbToHex(tokens.textPrimary.srgb) }] }, palette.name), previewColors && /* @__PURE__ */ React3.createElement(View, { style: styles.inlineSwatches }, previewColors.map((color, i) => /* @__PURE__ */ React3.createElement(
324
+ View,
325
+ {
326
+ key: i,
327
+ style: [styles.inlineSwatch, { backgroundColor: srgbToHex(color.srgb) }]
328
+ }
329
+ ))), /* @__PURE__ */ React3.createElement(
330
+ HueSlider,
331
+ {
332
+ value: palette.hue,
333
+ onValueChange: (hue) => dispatch({ type: "SET_PALETTE_HUE", index, hue }),
334
+ label: "Hue",
335
+ showValue: true,
336
+ ...hueRange ? { min: hueRange.min, max: hueRange.max } : {}
337
+ }
338
+ ), /* @__PURE__ */ React3.createElement(
339
+ Slider,
340
+ {
341
+ value: palette.saturation,
342
+ onValueChange: (saturation) => dispatch({ type: "SET_PALETTE_SATURATION", index, saturation }),
343
+ min: 0,
344
+ max: 100,
345
+ label: "Saturation",
346
+ showValue: true
347
+ }
348
+ ), /* @__PURE__ */ React3.createElement(View, { style: styles.row }, /* @__PURE__ */ React3.createElement(View, { style: styles.flex }, /* @__PURE__ */ React3.createElement(
349
+ Select,
350
+ {
351
+ options: STRENGTH_OPTIONS,
352
+ value: palette.desaturationStrength,
353
+ onValueChange: (strength) => dispatch({ type: "SET_PALETTE_DESAT_STRENGTH", index, strength }),
354
+ label: "Desaturation"
355
+ }
356
+ )), palette.desaturationStrength !== "none" && /* @__PURE__ */ React3.createElement(View, { style: styles.toggleContainer }, /* @__PURE__ */ React3.createElement(
357
+ Toggle,
358
+ {
359
+ value: palette.desaturationDirection === "dark",
360
+ onValueChange: (v) => dispatch({ type: "SET_PALETTE_DESAT_DIRECTION", index, direction: v ? "dark" : "light" }),
361
+ label: "Invert"
362
+ }
363
+ ))), /* @__PURE__ */ React3.createElement(View, { style: styles.row }, /* @__PURE__ */ React3.createElement(View, { style: styles.flex }, /* @__PURE__ */ React3.createElement(
364
+ Select,
365
+ {
366
+ options: STRENGTH_OPTIONS,
367
+ value: palette.hueGradeStrength,
368
+ onValueChange: (strength) => dispatch({ type: "SET_PALETTE_HUE_GRADE_STRENGTH", index, strength }),
369
+ label: "Hue Grading"
370
+ }
371
+ )), palette.hueGradeStrength !== "none" && /* @__PURE__ */ React3.createElement(View, { style: styles.toggleContainer }, /* @__PURE__ */ React3.createElement(
372
+ Toggle,
373
+ {
374
+ value: palette.hueGradeDirection === "dark",
375
+ onValueChange: (v) => dispatch({ type: "SET_PALETTE_HUE_GRADE_DIRECTION", index, direction: v ? "dark" : "light" }),
376
+ label: "Invert"
377
+ }
378
+ ))), palette.hueGradeStrength !== "none" && /* @__PURE__ */ React3.createElement(
379
+ HueSlider,
380
+ {
381
+ value: palette.hueGradeHue,
382
+ onValueChange: (hue) => dispatch({ type: "SET_PALETTE_HUE_GRADE_HUE", index, hue }),
383
+ label: "Grade Target",
384
+ showValue: true
385
+ }
386
+ ));
387
+ }
388
+ var styles = StyleSheet.create({
389
+ container: {
390
+ gap: 12,
391
+ marginBottom: 12
392
+ },
393
+ title: {
394
+ fontSize: 16,
395
+ fontWeight: "700"
396
+ },
397
+ inlineSwatches: {
398
+ flexDirection: "row",
399
+ flexWrap: "wrap",
400
+ gap: 1
401
+ },
402
+ inlineSwatch: {
403
+ width: 20,
404
+ height: 16,
405
+ borderRadius: 2
406
+ },
407
+ row: {
408
+ flexDirection: "row",
409
+ alignItems: "flex-end",
410
+ gap: 12
411
+ },
412
+ flex: {
413
+ flex: 1
414
+ },
415
+ toggleContainer: {
416
+ paddingBottom: 2
417
+ }
418
+ });
419
+ var STRENGTH_OPTIONS2 = [
420
+ { label: "None", value: "none" },
421
+ { label: "Low", value: "low" },
422
+ { label: "Medium", value: "medium" },
423
+ { label: "Hard", value: "hard" }
424
+ ];
425
+ function GlobalPanel({ state, dispatch }) {
426
+ const tokens = useTokens(1);
427
+ return /* @__PURE__ */ React3.createElement(Card, { elevation: 1, style: styles2.container }, /* @__PURE__ */ React3.createElement(Text, { style: [styles2.title, { color: srgbToHex(tokens.textPrimary.srgb) }] }, "Dynamic Range"), /* @__PURE__ */ React3.createElement(View, { style: styles2.row }, /* @__PURE__ */ React3.createElement(View, { style: styles2.flex }, /* @__PURE__ */ React3.createElement(
428
+ Slider,
429
+ {
430
+ value: Math.round(state.dynamicRange.lightest * 100),
431
+ onValueChange: (v) => dispatch({ type: "SET_LIGHTEST", value: v / 100 }),
432
+ min: 0,
433
+ max: 100,
434
+ label: "Lightest",
435
+ showValue: true
436
+ }
437
+ )), /* @__PURE__ */ React3.createElement(View, { style: styles2.flex }, /* @__PURE__ */ React3.createElement(
438
+ Slider,
439
+ {
440
+ value: Math.round(state.dynamicRange.darkest * 100),
441
+ onValueChange: (v) => dispatch({ type: "SET_DARKEST", value: v / 100 }),
442
+ min: 0,
443
+ max: 100,
444
+ label: "Darkest",
445
+ showValue: true
446
+ }
447
+ ))), /* @__PURE__ */ React3.createElement(Text, { style: [styles2.subtitle, { color: srgbToHex(tokens.textSecondary.srgb) }] }, "Global Hue Grading \u2014 Light End"), /* @__PURE__ */ React3.createElement(View, { style: styles2.row }, /* @__PURE__ */ React3.createElement(View, { style: styles2.flex }, /* @__PURE__ */ React3.createElement(
448
+ Select,
449
+ {
450
+ options: STRENGTH_OPTIONS2,
451
+ value: state.globalHueGrading.light.strength,
452
+ onValueChange: (s) => dispatch({ type: "SET_GLOBAL_GRADE_LIGHT_STRENGTH", strength: s }),
453
+ label: "Strength"
454
+ }
455
+ )), state.globalHueGrading.light.strength !== "none" && /* @__PURE__ */ React3.createElement(View, { style: styles2.flex }, /* @__PURE__ */ React3.createElement(
456
+ HueSlider,
457
+ {
458
+ value: state.globalHueGrading.light.hue,
459
+ onValueChange: (hue) => dispatch({ type: "SET_GLOBAL_GRADE_LIGHT_HUE", hue }),
460
+ label: "Target Hue",
461
+ showValue: true
462
+ }
463
+ ))), /* @__PURE__ */ React3.createElement(Text, { style: [styles2.subtitle, { color: srgbToHex(tokens.textSecondary.srgb) }] }, "Global Hue Grading \u2014 Dark End"), /* @__PURE__ */ React3.createElement(View, { style: styles2.row }, /* @__PURE__ */ React3.createElement(View, { style: styles2.flex }, /* @__PURE__ */ React3.createElement(
464
+ Select,
465
+ {
466
+ options: STRENGTH_OPTIONS2,
467
+ value: state.globalHueGrading.dark.strength,
468
+ onValueChange: (s) => dispatch({ type: "SET_GLOBAL_GRADE_DARK_STRENGTH", strength: s }),
469
+ label: "Strength"
470
+ }
471
+ )), state.globalHueGrading.dark.strength !== "none" && /* @__PURE__ */ React3.createElement(View, { style: styles2.flex }, /* @__PURE__ */ React3.createElement(
472
+ HueSlider,
473
+ {
474
+ value: state.globalHueGrading.dark.hue,
475
+ onValueChange: (hue) => dispatch({ type: "SET_GLOBAL_GRADE_DARK_HUE", hue }),
476
+ label: "Target Hue",
477
+ showValue: true
478
+ }
479
+ ))));
480
+ }
481
+ var styles2 = StyleSheet.create({
482
+ container: {
483
+ gap: 12
484
+ },
485
+ title: {
486
+ fontSize: 16,
487
+ fontWeight: "700"
488
+ },
489
+ subtitle: {
490
+ fontSize: 13,
491
+ fontWeight: "600",
492
+ marginTop: 4
493
+ },
494
+ row: {
495
+ flexDirection: "row",
496
+ gap: 12
497
+ },
498
+ flex: {
499
+ flex: 1
500
+ }
501
+ });
502
+ var THEME_OPTIONS = [
503
+ { label: "Neutral", value: "neutral" },
504
+ { label: "Primary", value: "primary" },
505
+ { label: "Secondary", value: "secondary" },
506
+ { label: "Strong", value: "strong" }
507
+ ];
508
+ function PreviewCard({
509
+ state,
510
+ dispatch
511
+ }) {
512
+ const tokens = useTokens(1);
513
+ return /* @__PURE__ */ React3.createElement(Card, { elevation: 1, style: styles3.container }, /* @__PURE__ */ React3.createElement(Text, { style: [styles3.title, { color: srgbToHex(tokens.textPrimary.srgb) }] }, "Component Preview"), /* @__PURE__ */ React3.createElement(View, { style: styles3.controls }, /* @__PURE__ */ React3.createElement(
514
+ Select,
515
+ {
516
+ options: THEME_OPTIONS,
517
+ value: state.preview.theme,
518
+ onValueChange: (t) => dispatch({ type: "SET_PREVIEW_THEME", theme: t }),
519
+ label: "Theme"
520
+ }
521
+ )), /* @__PURE__ */ React3.createElement(View, { style: previewStyles.wrapper }, /* @__PURE__ */ React3.createElement(View, { style: previewStyles.row }, /* @__PURE__ */ React3.createElement(Button, { variant: "primary", size: "sm" }, "Primary"), /* @__PURE__ */ React3.createElement(Button, { variant: "secondary", size: "sm" }, "Secondary"), /* @__PURE__ */ React3.createElement(Button, { variant: "ghost", size: "sm" }, "Ghost"), /* @__PURE__ */ React3.createElement(Button, { variant: "outline", size: "sm" }, "Outline")), /* @__PURE__ */ React3.createElement(TextInput, { label: "Sample Input", value: "Hello, Newtone", onChangeText: () => {
522
+ } }), /* @__PURE__ */ React3.createElement(View, { style: previewStyles.row }, /* @__PURE__ */ React3.createElement(View, { style: [previewStyles.statusDot, { backgroundColor: srgbToHex(tokens.success.srgb) }] }), /* @__PURE__ */ React3.createElement(Text, { style: [previewStyles.statusText, { color: srgbToHex(tokens.textPrimary.srgb) }] }, "Success"), /* @__PURE__ */ React3.createElement(View, { style: [previewStyles.statusDot, { backgroundColor: srgbToHex(tokens.warning.srgb) }] }), /* @__PURE__ */ React3.createElement(Text, { style: [previewStyles.statusText, { color: srgbToHex(tokens.textPrimary.srgb) }] }, "Warning"), /* @__PURE__ */ React3.createElement(View, { style: [previewStyles.statusDot, { backgroundColor: srgbToHex(tokens.error.srgb) }] }), /* @__PURE__ */ React3.createElement(Text, { style: [previewStyles.statusText, { color: srgbToHex(tokens.textPrimary.srgb) }] }, "Error"))));
523
+ }
524
+ function PreviewPanel({ state, dispatch, themeConfig }) {
525
+ return /* @__PURE__ */ React3.createElement(
526
+ NewtoneProvider,
527
+ {
528
+ key: `${state.preview.mode}-${state.preview.theme}`,
529
+ config: themeConfig,
530
+ initialMode: state.preview.mode,
531
+ initialTheme: state.preview.theme
532
+ },
533
+ /* @__PURE__ */ React3.createElement(PreviewCard, { state, dispatch })
534
+ );
535
+ }
536
+ var styles3 = StyleSheet.create({
537
+ container: {
538
+ gap: 12
539
+ },
540
+ title: {
541
+ fontSize: 16,
542
+ fontWeight: "700"
543
+ },
544
+ controls: {
545
+ flexDirection: "row",
546
+ alignItems: "flex-end",
547
+ gap: 16
548
+ }
549
+ });
550
+ var previewStyles = StyleSheet.create({
551
+ wrapper: {
552
+ gap: 12
553
+ },
554
+ row: {
555
+ flexDirection: "row",
556
+ alignItems: "center",
557
+ gap: 8,
558
+ flexWrap: "wrap"
559
+ },
560
+ statusDot: {
561
+ width: 12,
562
+ height: 12,
563
+ borderRadius: 6
564
+ },
565
+ statusText: {
566
+ fontSize: 13,
567
+ marginRight: 8
568
+ }
569
+ });
570
+ function toCSS(state) {
571
+ const config = toThemeConfig(state);
572
+ const modes = ["light", "dark"];
573
+ let css = "";
574
+ for (const mode of modes) {
575
+ const tokens = computeTokens(
576
+ config.colorSystem,
577
+ mode,
578
+ config.themes.neutral,
579
+ 1,
580
+ config.elevation.offsets
581
+ );
582
+ const selector = mode === "light" ? ":root" : '[data-theme="dark"]';
583
+ css += `${selector} {
584
+ `;
585
+ css += ` --newtone-background: ${srgbToHex(tokens.background.srgb)};
586
+ `;
587
+ css += ` --newtone-background-elevated: ${srgbToHex(tokens.backgroundElevated.srgb)};
588
+ `;
589
+ css += ` --newtone-background-sunken: ${srgbToHex(tokens.backgroundSunken.srgb)};
590
+ `;
591
+ css += ` --newtone-text-primary: ${srgbToHex(tokens.textPrimary.srgb)};
592
+ `;
593
+ css += ` --newtone-text-secondary: ${srgbToHex(tokens.textSecondary.srgb)};
594
+ `;
595
+ css += ` --newtone-interactive: ${srgbToHex(tokens.interactive.srgb)};
596
+ `;
597
+ css += ` --newtone-interactive-hover: ${srgbToHex(tokens.interactiveHover.srgb)};
598
+ `;
599
+ css += ` --newtone-interactive-active: ${srgbToHex(tokens.interactiveActive.srgb)};
600
+ `;
601
+ css += ` --newtone-border: ${srgbToHex(tokens.border.srgb)};
602
+ `;
603
+ css += ` --newtone-success: ${srgbToHex(tokens.success.srgb)};
604
+ `;
605
+ css += ` --newtone-warning: ${srgbToHex(tokens.warning.srgb)};
606
+ `;
607
+ css += ` --newtone-error: ${srgbToHex(tokens.error.srgb)};
608
+ `;
609
+ css += `}
610
+
611
+ `;
612
+ }
613
+ return css;
614
+ }
615
+
616
+ // src/bridge/toJSON.ts
617
+ function toJSON(state) {
618
+ const output = {
619
+ version: "1.0",
620
+ configuratorState: state,
621
+ themeConfig: toThemeConfig(state)
622
+ };
623
+ return JSON.stringify(output, null, 2);
624
+ }
625
+
626
+ // src/panels/ExportPanel.tsx
627
+ function ExportPanel({ state }) {
628
+ const tokens = useTokens(1);
629
+ const [format, setFormat] = useState("css");
630
+ const [copied, setCopied] = useState(false);
631
+ const output = useMemo(() => {
632
+ return format === "css" ? toCSS(state) : toJSON(state);
633
+ }, [state, format]);
634
+ const handleCopy = useCallback(() => {
635
+ if (typeof navigator !== "undefined" && navigator.clipboard) {
636
+ navigator.clipboard.writeText(output).then(() => {
637
+ setCopied(true);
638
+ setTimeout(() => setCopied(false), 2e3);
639
+ });
640
+ }
641
+ }, [output]);
642
+ return /* @__PURE__ */ React3.createElement(Card, { elevation: 1, style: styles4.container }, /* @__PURE__ */ React3.createElement(Text, { style: [styles4.title, { color: srgbToHex(tokens.textPrimary.srgb) }] }, "Export"), /* @__PURE__ */ React3.createElement(View, { style: styles4.tabs }, /* @__PURE__ */ React3.createElement(
643
+ Button,
644
+ {
645
+ variant: format === "css" ? "primary" : "ghost",
646
+ size: "sm",
647
+ onPress: () => setFormat("css")
648
+ },
649
+ "CSS Variables"
650
+ ), /* @__PURE__ */ React3.createElement(
651
+ Button,
652
+ {
653
+ variant: format === "json" ? "primary" : "ghost",
654
+ size: "sm",
655
+ onPress: () => setFormat("json")
656
+ },
657
+ "JSON"
658
+ ), /* @__PURE__ */ React3.createElement(Button, { variant: "outline", size: "sm", onPress: handleCopy }, copied ? "Copied!" : "Copy")), /* @__PURE__ */ React3.createElement(View, { style: [styles4.codeBlock, { backgroundColor: srgbToHex(tokens.backgroundSunken.srgb) }] }, /* @__PURE__ */ React3.createElement(
659
+ Text,
660
+ {
661
+ style: [styles4.code, { color: srgbToHex(tokens.textPrimary.srgb) }],
662
+ selectable: true
663
+ },
664
+ output
665
+ )));
666
+ }
667
+ var styles4 = StyleSheet.create({
668
+ container: {
669
+ gap: 12,
670
+ marginBottom: 12
671
+ },
672
+ title: {
673
+ fontSize: 16,
674
+ fontWeight: "700"
675
+ },
676
+ tabs: {
677
+ flexDirection: "row",
678
+ gap: 8
679
+ },
680
+ codeBlock: {
681
+ borderRadius: 6,
682
+ padding: 12,
683
+ maxHeight: 300,
684
+ overflow: "scroll"
685
+ },
686
+ code: {
687
+ fontSize: 11,
688
+ fontFamily: "monospace",
689
+ lineHeight: 16
690
+ }
691
+ });
692
+
693
+ // src/Configurator.tsx
694
+ function Configurator({
695
+ initialState,
696
+ onChange,
697
+ showExport = true,
698
+ showPreview = true
699
+ }) {
700
+ const { state, dispatch, themeConfig, reset } = useConfigurator(initialState);
701
+ const tokens = useTokens(1);
702
+ const { setMode } = useNewtoneTheme();
703
+ const isInitialRender = useRef(true);
704
+ const [activePaletteIndex, setActivePaletteIndex] = useState(0);
705
+ const previews = usePreviewColors(state);
706
+ useEffect(() => {
707
+ setMode(state.preview.mode);
708
+ }, []);
709
+ useEffect(() => {
710
+ if (isInitialRender.current) {
711
+ isInitialRender.current = false;
712
+ return;
713
+ }
714
+ onChange?.(state, themeConfig);
715
+ }, [state, themeConfig, onChange]);
716
+ return /* @__PURE__ */ React3.createElement(View, { style: [styles5.container, { backgroundColor: srgbToHex(tokens.backgroundSunken.srgb) }] }, /* @__PURE__ */ React3.createElement(View, { style: styles5.topBar }, /* @__PURE__ */ React3.createElement(
717
+ Toggle,
718
+ {
719
+ value: state.preview.mode === "dark",
720
+ onValueChange: (isDark) => {
721
+ const mode = isDark ? "dark" : "light";
722
+ dispatch({ type: "SET_PREVIEW_MODE", mode });
723
+ setMode(mode);
724
+ },
725
+ label: "Dark Mode"
726
+ }
727
+ )), /* @__PURE__ */ React3.createElement(View, { style: styles5.topRow }, /* @__PURE__ */ React3.createElement(View, { style: styles5.topRowPanel }, /* @__PURE__ */ React3.createElement(GlobalPanel, { state, dispatch })), showPreview && /* @__PURE__ */ React3.createElement(View, { style: styles5.topRowPanel }, /* @__PURE__ */ React3.createElement(PreviewPanel, { state, dispatch, themeConfig }))), /* @__PURE__ */ React3.createElement(View, { style: styles5.tabBar }, state.palettes.map((palette, index) => /* @__PURE__ */ React3.createElement(
728
+ Pressable,
729
+ {
730
+ key: index,
731
+ onPress: () => setActivePaletteIndex(index),
732
+ style: [
733
+ styles5.tab,
734
+ {
735
+ backgroundColor: index === activePaletteIndex ? srgbToHex(tokens.background.srgb) : "transparent",
736
+ borderColor: index === activePaletteIndex ? srgbToHex(tokens.border.srgb) : "transparent"
737
+ }
738
+ ]
739
+ },
740
+ /* @__PURE__ */ React3.createElement(Text, { style: [
741
+ styles5.tabText,
742
+ {
743
+ color: srgbToHex(
744
+ index === activePaletteIndex ? tokens.textPrimary.srgb : tokens.textSecondary.srgb
745
+ ),
746
+ fontWeight: index === activePaletteIndex ? "700" : "500"
747
+ }
748
+ ] }, palette.name)
749
+ ))), /* @__PURE__ */ React3.createElement(
750
+ PalettePanel,
751
+ {
752
+ palette: state.palettes[activePaletteIndex],
753
+ index: activePaletteIndex,
754
+ dispatch,
755
+ previewColors: previews[activePaletteIndex]
756
+ }
757
+ ), showExport && /* @__PURE__ */ React3.createElement(ExportPanel, { state }), /* @__PURE__ */ React3.createElement(
758
+ "button",
759
+ {
760
+ onClick: () => reset(),
761
+ style: {
762
+ marginTop: 16,
763
+ padding: "8px 16px",
764
+ backgroundColor: "#ffffff",
765
+ color: "#000000",
766
+ border: "2px solid #000000",
767
+ borderRadius: 4,
768
+ fontWeight: 700,
769
+ fontSize: 14,
770
+ cursor: "pointer"
771
+ }
772
+ },
773
+ "Reset to Defaults"
774
+ ));
775
+ }
776
+ var styles5 = StyleSheet.create({
777
+ container: {
778
+ borderRadius: 8,
779
+ padding: 16,
780
+ gap: 4
781
+ },
782
+ topBar: {
783
+ flexDirection: "row",
784
+ justifyContent: "flex-end",
785
+ alignItems: "center",
786
+ marginBottom: 8
787
+ },
788
+ topRow: {
789
+ flexDirection: "row",
790
+ gap: 8,
791
+ marginBottom: 4
792
+ },
793
+ topRowPanel: {
794
+ flex: 1
795
+ },
796
+ tabBar: {
797
+ flexDirection: "row",
798
+ gap: 4,
799
+ marginBottom: 4
800
+ },
801
+ tab: {
802
+ paddingVertical: 8,
803
+ paddingHorizontal: 12,
804
+ borderRadius: 6,
805
+ borderWidth: 1
806
+ },
807
+ tabText: {
808
+ fontSize: 13
809
+ }
810
+ });
811
+
812
+ export { Configurator, DEFAULT_CONFIGURATOR_STATE, toCSS, toJSON, toThemeConfig, traditionalHueToOklch, useConfigurator, usePreviewColors };
813
+ //# sourceMappingURL=index.js.map
814
+ //# sourceMappingURL=index.js.map