@newtonedev/configurator 0.1.0 → 0.1.2

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/index.cjs CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
- var React3 = require('react');
3
+ var React = require('react');
4
4
  var reactNative = require('react-native');
5
5
  var components = require('@newtonedev/components');
6
6
  var newtone = require('newtone');
7
7
 
8
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
 
10
- var React3__default = /*#__PURE__*/_interopDefault(React3);
10
+ var React__default = /*#__PURE__*/_interopDefault(React);
11
11
 
12
12
  // src/Configurator.tsx
13
13
 
@@ -76,6 +76,42 @@ var DEFAULT_CONFIGURATOR_STATE = {
76
76
  preview: {
77
77
  mode: "light",
78
78
  theme: "neutral"
79
+ },
80
+ spacing: {
81
+ preset: "md"
82
+ // Medium (8px base): default/balanced spacing
83
+ },
84
+ roundness: {
85
+ intensity: 0.5
86
+ // 1.0x multiplier = preserves current hardcoded values
87
+ },
88
+ typography: {
89
+ fonts: {
90
+ mono: {
91
+ type: "system",
92
+ family: "ui-monospace",
93
+ fallback: "SFMono-Regular, Menlo, Monaco, Consolas, monospace"
94
+ },
95
+ display: {
96
+ type: "system",
97
+ family: "system-ui",
98
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
99
+ },
100
+ default: {
101
+ type: "system",
102
+ family: "system-ui",
103
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
104
+ }
105
+ },
106
+ scale: { baseSize: 16, ratio: 1.25 }
107
+ },
108
+ icons: {
109
+ variant: "rounded",
110
+ // Material Design 3 aesthetic
111
+ weight: 400,
112
+ // Normal weight
113
+ autoGrade: true
114
+ // Enable mode-aware grade
79
115
  }
80
116
  };
81
117
 
@@ -142,6 +178,52 @@ function configuratorReducer(state, action) {
142
178
  hueGradeDirection: action.direction
143
179
  })
144
180
  };
181
+ case "SET_PALETTE_KEY_COLOR":
182
+ return {
183
+ ...state,
184
+ palettes: updatePalette(state.palettes, action.index, {
185
+ keyColor: clamp(action.normalizedValue, 0, 1)
186
+ })
187
+ };
188
+ case "CLEAR_PALETTE_KEY_COLOR":
189
+ return {
190
+ ...state,
191
+ palettes: updatePalette(state.palettes, action.index, {
192
+ keyColor: void 0
193
+ })
194
+ };
195
+ case "SET_PALETTE_FROM_HEX":
196
+ return {
197
+ ...state,
198
+ palettes: updatePalette(state.palettes, action.index, {
199
+ hue: wrapHue(action.hue),
200
+ saturation: clamp(action.saturation, 0, 100),
201
+ keyColor: clamp(action.keyColor, 0, 1)
202
+ })
203
+ };
204
+ case "SET_PALETTE_KEY_COLOR_DARK":
205
+ return {
206
+ ...state,
207
+ palettes: updatePalette(state.palettes, action.index, {
208
+ keyColorDark: clamp(action.normalizedValue, 0, 1)
209
+ })
210
+ };
211
+ case "CLEAR_PALETTE_KEY_COLOR_DARK":
212
+ return {
213
+ ...state,
214
+ palettes: updatePalette(state.palettes, action.index, {
215
+ keyColorDark: void 0
216
+ })
217
+ };
218
+ case "SET_PALETTE_FROM_HEX_DARK":
219
+ return {
220
+ ...state,
221
+ palettes: updatePalette(state.palettes, action.index, {
222
+ hue: wrapHue(action.hue),
223
+ saturation: clamp(action.saturation, 0, 100),
224
+ keyColorDark: clamp(action.keyColor, 0, 1)
225
+ })
226
+ };
145
227
  // Dynamic range actions
146
228
  case "SET_LIGHTEST":
147
229
  return {
@@ -192,6 +274,125 @@ function configuratorReducer(state, action) {
192
274
  dark: { ...state.globalHueGrading.dark, hue: wrapHue(action.hue) }
193
275
  }
194
276
  };
277
+ // Spacing actions
278
+ case "SET_SPACING_PRESET":
279
+ return {
280
+ ...state,
281
+ spacing: {
282
+ preset: action.preset
283
+ }
284
+ };
285
+ // Roundness actions
286
+ case "SET_ROUNDNESS_INTENSITY":
287
+ return {
288
+ ...state,
289
+ roundness: {
290
+ intensity: clamp(action.intensity, 0, 1)
291
+ }
292
+ };
293
+ // Typography actions
294
+ case "SET_TYPOGRAPHY_BASE_SIZE": {
295
+ const defaultTypography = DEFAULT_CONFIGURATOR_STATE.typography;
296
+ return {
297
+ ...state,
298
+ typography: {
299
+ fonts: state.typography?.fonts ?? defaultTypography.fonts,
300
+ scale: {
301
+ baseSize: clamp(action.baseSize, 12, 24),
302
+ ratio: state.typography?.scale.ratio ?? defaultTypography.scale.ratio
303
+ }
304
+ }
305
+ };
306
+ }
307
+ case "SET_TYPOGRAPHY_RATIO": {
308
+ const defaultTypography = DEFAULT_CONFIGURATOR_STATE.typography;
309
+ return {
310
+ ...state,
311
+ typography: {
312
+ fonts: state.typography?.fonts ?? defaultTypography.fonts,
313
+ scale: {
314
+ baseSize: state.typography?.scale.baseSize ?? defaultTypography.scale.baseSize,
315
+ ratio: clamp(action.ratio, 1.1, 1.5)
316
+ }
317
+ }
318
+ };
319
+ }
320
+ case "SET_FONT_MONO": {
321
+ const defaultTypography = DEFAULT_CONFIGURATOR_STATE.typography;
322
+ return {
323
+ ...state,
324
+ typography: {
325
+ fonts: {
326
+ mono: action.font,
327
+ display: state.typography?.fonts.display ?? defaultTypography.fonts.display,
328
+ default: state.typography?.fonts.default ?? defaultTypography.fonts.default
329
+ },
330
+ scale: state.typography?.scale ?? defaultTypography.scale
331
+ }
332
+ };
333
+ }
334
+ case "SET_FONT_DISPLAY": {
335
+ const defaultTypography = DEFAULT_CONFIGURATOR_STATE.typography;
336
+ return {
337
+ ...state,
338
+ typography: {
339
+ fonts: {
340
+ mono: state.typography?.fonts.mono ?? defaultTypography.fonts.mono,
341
+ display: action.font,
342
+ default: state.typography?.fonts.default ?? defaultTypography.fonts.default
343
+ },
344
+ scale: state.typography?.scale ?? defaultTypography.scale
345
+ }
346
+ };
347
+ }
348
+ case "SET_FONT_DEFAULT": {
349
+ const defaultTypography = DEFAULT_CONFIGURATOR_STATE.typography;
350
+ return {
351
+ ...state,
352
+ typography: {
353
+ fonts: {
354
+ mono: state.typography?.fonts.mono ?? defaultTypography.fonts.mono,
355
+ display: state.typography?.fonts.display ?? defaultTypography.fonts.display,
356
+ default: action.font
357
+ },
358
+ scale: state.typography?.scale ?? defaultTypography.scale
359
+ }
360
+ };
361
+ }
362
+ // Icons actions
363
+ case "SET_ICON_VARIANT": {
364
+ const defaultIcons = DEFAULT_CONFIGURATOR_STATE.icons;
365
+ return {
366
+ ...state,
367
+ icons: {
368
+ variant: action.variant,
369
+ weight: state.icons?.weight ?? defaultIcons.weight,
370
+ autoGrade: state.icons?.autoGrade ?? defaultIcons.autoGrade
371
+ }
372
+ };
373
+ }
374
+ case "SET_ICON_WEIGHT": {
375
+ const defaultIcons = DEFAULT_CONFIGURATOR_STATE.icons;
376
+ return {
377
+ ...state,
378
+ icons: {
379
+ variant: state.icons?.variant ?? defaultIcons.variant,
380
+ weight: action.weight,
381
+ autoGrade: state.icons?.autoGrade ?? defaultIcons.autoGrade
382
+ }
383
+ };
384
+ }
385
+ case "SET_ICON_AUTO_GRADE": {
386
+ const defaultIcons = DEFAULT_CONFIGURATOR_STATE.icons;
387
+ return {
388
+ ...state,
389
+ icons: {
390
+ variant: state.icons?.variant ?? defaultIcons.variant,
391
+ weight: state.icons?.weight ?? defaultIcons.weight,
392
+ autoGrade: action.autoGrade
393
+ }
394
+ };
395
+ }
195
396
  // Preview actions
196
397
  case "SET_PREVIEW_MODE":
197
398
  return {
@@ -245,6 +446,57 @@ function traditionalHueToOklch(hue) {
245
446
  }
246
447
 
247
448
  // src/bridge/toThemeConfig.ts
449
+ var SPACING_PRESET_TO_BASE = {
450
+ xs: 6,
451
+ // Extra Small: compact/dense UI
452
+ sm: 7,
453
+ // Small: tighter spacing
454
+ md: 8,
455
+ // Medium: default/balanced
456
+ lg: 9,
457
+ // Large: more spacious
458
+ xl: 10
459
+ // Extra Large: maximum spacing
460
+ };
461
+ function roundnessToMultiplier(intensity) {
462
+ return intensity * 2;
463
+ }
464
+ function computeTypographyScale(baseSize, ratio) {
465
+ return {
466
+ xs: Math.round(baseSize / ratio ** 2),
467
+ sm: Math.round(baseSize / ratio),
468
+ base: baseSize,
469
+ md: Math.round(baseSize * ratio),
470
+ lg: Math.round(baseSize * ratio ** 2),
471
+ xl: Math.round(baseSize * ratio ** 3),
472
+ xxl: Math.round(baseSize * ratio ** 4)
473
+ };
474
+ }
475
+ var DEFAULT_FONTS = {
476
+ mono: {
477
+ type: "system",
478
+ family: "ui-monospace",
479
+ fallback: "SFMono-Regular, Menlo, Monaco, Consolas, monospace"
480
+ },
481
+ display: {
482
+ type: "system",
483
+ family: "system-ui",
484
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
485
+ },
486
+ default: {
487
+ type: "system",
488
+ family: "system-ui",
489
+ fallback: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
490
+ }
491
+ };
492
+ var DEFAULT_ICONS = {
493
+ variant: "rounded",
494
+ // Material Design 3 aesthetic
495
+ weight: 400,
496
+ // Normal weight
497
+ autoGrade: true
498
+ // Enable mode-aware grade
499
+ };
248
500
  function toThemeConfig(state) {
249
501
  const palettes = state.palettes.map((p) => {
250
502
  const oklchHue = traditionalHueToOklch(p.hue);
@@ -254,7 +506,14 @@ function toThemeConfig(state) {
254
506
  strength: p.hueGradeStrength,
255
507
  direction: p.hueGradeDirection
256
508
  } : void 0;
257
- return { hue: oklchHue, saturation: p.saturation, desaturation, paletteHueGrading };
509
+ return {
510
+ hue: oklchHue,
511
+ saturation: p.saturation,
512
+ desaturation,
513
+ paletteHueGrading,
514
+ ...p.keyColor !== void 0 ? { keyNormalizedValue: p.keyColor } : {},
515
+ ...p.keyColorDark !== void 0 ? { keyNormalizedValueDark: p.keyColorDark } : {}
516
+ };
258
517
  });
259
518
  const light = state.globalHueGrading.light.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.light.hue), strength: state.globalHueGrading.light.strength } : void 0;
260
519
  const dark = state.globalHueGrading.dark.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.dark.hue), strength: state.globalHueGrading.dark.strength } : void 0;
@@ -264,6 +523,8 @@ function toThemeConfig(state) {
264
523
  darkest: state.dynamicRange.darkest,
265
524
  ...hueGrading ? { hueGrading } : {}
266
525
  };
526
+ const spacingBase = SPACING_PRESET_TO_BASE[state.spacing?.preset ?? "md"];
527
+ const radiusMultiplier = roundnessToMultiplier(state.roundness?.intensity ?? 0.5);
267
528
  return {
268
529
  colorSystem: { dynamicRange, palettes },
269
530
  themes: {
@@ -274,21 +535,56 @@ function toThemeConfig(state) {
274
535
  },
275
536
  elevation: {
276
537
  offsets: [-0.02, 0, 0.04]
277
- }
538
+ },
539
+ spacing: {
540
+ "00": Math.round(spacingBase * 0),
541
+ // Always 0
542
+ "02": Math.round(spacingBase * 0.25),
543
+ "04": Math.round(spacingBase * 0.5),
544
+ "06": Math.round(spacingBase * 0.75),
545
+ "08": Math.round(spacingBase * 1),
546
+ // Equals base
547
+ "10": Math.round(spacingBase * 1.25),
548
+ "12": Math.round(spacingBase * 1.5),
549
+ "16": Math.round(spacingBase * 2),
550
+ "20": Math.round(spacingBase * 2.5),
551
+ "24": Math.round(spacingBase * 3),
552
+ "32": Math.round(spacingBase * 4),
553
+ "40": Math.round(spacingBase * 5),
554
+ "48": Math.round(spacingBase * 6)
555
+ },
556
+ radius: {
557
+ none: 0,
558
+ sm: Math.round(4 * radiusMultiplier),
559
+ md: Math.round(6 * radiusMultiplier),
560
+ lg: Math.round(8 * radiusMultiplier),
561
+ xl: Math.round(12 * radiusMultiplier),
562
+ pill: 999
563
+ },
564
+ typography: {
565
+ fonts: state.typography?.fonts ?? DEFAULT_FONTS,
566
+ scale: computeTypographyScale(
567
+ state.typography?.scale.baseSize ?? 16,
568
+ state.typography?.scale.ratio ?? 1.25
569
+ ),
570
+ lineHeight: { tight: 1.25, normal: 1.5, relaxed: 1.75 },
571
+ fontWeight: { regular: 400, medium: 500, semibold: 600, bold: 700 }
572
+ },
573
+ icons: state.icons ?? DEFAULT_ICONS
278
574
  };
279
575
  }
280
576
 
281
577
  // src/hooks/useConfigurator.ts
282
578
  function useConfigurator(initialState) {
283
579
  const mergedInitial = initialState ? { ...DEFAULT_CONFIGURATOR_STATE, ...initialState } : DEFAULT_CONFIGURATOR_STATE;
284
- const [state, dispatch] = React3.useReducer(configuratorReducer, mergedInitial);
285
- const themeConfig = React3.useMemo(() => toThemeConfig(state), [state]);
286
- const reset = React3.useCallback(() => dispatch({ type: "RESET" }), []);
580
+ const [state, dispatch] = React.useReducer(configuratorReducer, mergedInitial);
581
+ const themeConfig = React.useMemo(() => toThemeConfig(state), [state]);
582
+ const reset = React.useCallback(() => dispatch({ type: "RESET" }), []);
287
583
  return { state, dispatch, themeConfig, reset };
288
584
  }
289
585
  var PREVIEW_STEPS = 26;
290
586
  function usePreviewColors(state) {
291
- return React3.useMemo(() => {
587
+ return React.useMemo(() => {
292
588
  const light = state.globalHueGrading.light.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.light.hue), strength: state.globalHueGrading.light.strength } : void 0;
293
589
  const dark = state.globalHueGrading.dark.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.dark.hue), strength: state.globalHueGrading.dark.strength } : void 0;
294
590
  const hueGrading = light || dark ? { light, dark } : void 0;
@@ -315,6 +611,105 @@ var SEMANTIC_HUE_RANGES = {
315
611
  4: { min: 345, max: 375 }
316
612
  // Error (reds, wraps 0°)
317
613
  };
614
+ function useWcagValidation(state, paletteIndex) {
615
+ return React.useMemo(() => {
616
+ const palette = state.palettes[paletteIndex];
617
+ const neutral = state.palettes[0];
618
+ if (!palette || !neutral) {
619
+ return { keyColorContrast: null, passesAA: true, passesAALargeText: true, autoNormalizedValue: 0.5 };
620
+ }
621
+ const mode = state.preview.mode;
622
+ const light = state.globalHueGrading.light.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.light.hue), strength: state.globalHueGrading.light.strength } : void 0;
623
+ const dark = state.globalHueGrading.dark.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.dark.hue), strength: state.globalHueGrading.dark.strength } : void 0;
624
+ const hueGrading = light || dark ? { light, dark } : void 0;
625
+ const dynamicRange = {
626
+ lightest: state.dynamicRange.lightest,
627
+ darkest: state.dynamicRange.darkest,
628
+ ...hueGrading ? { hueGrading } : {}
629
+ };
630
+ const neutralOklchHue = traditionalHueToOklch(neutral.hue);
631
+ const neutralDesat = neutral.desaturationStrength !== "none" ? { direction: neutral.desaturationDirection, strength: neutral.desaturationStrength } : void 0;
632
+ const neutralPhg = neutral.hueGradeStrength !== "none" ? { hue: traditionalHueToOklch(neutral.hueGradeHue), strength: neutral.hueGradeStrength, direction: neutral.hueGradeDirection } : void 0;
633
+ const backgroundNv = mode === "light" ? 0.95 : 0.1;
634
+ const background = newtone.getColor(neutralOklchHue, neutral.saturation, dynamicRange, backgroundNv, neutralDesat, neutralPhg);
635
+ const paletteOklchHue = traditionalHueToOklch(palette.hue);
636
+ const effectiveTextMode = mode === "light" ? "light" : "dark";
637
+ const paletteDesat = palette.desaturationStrength !== "none" ? { direction: palette.desaturationDirection, strength: palette.desaturationStrength } : void 0;
638
+ const palettePhg = palette.hueGradeStrength !== "none" ? { hue: traditionalHueToOklch(palette.hueGradeHue), strength: palette.hueGradeStrength, direction: palette.hueGradeDirection } : void 0;
639
+ const autoColor = newtone.getColorByContrast(
640
+ paletteOklchHue,
641
+ palette.saturation,
642
+ dynamicRange,
643
+ 4.5,
644
+ effectiveTextMode,
645
+ paletteDesat,
646
+ palettePhg,
647
+ background
648
+ );
649
+ const autoNormalizedValue = newtone.lightnessToNormalizedValue(dynamicRange, autoColor.oklch.L);
650
+ const effectiveKeyColor = mode === "dark" ? palette.keyColorDark : palette.keyColor;
651
+ if (effectiveKeyColor === void 0) {
652
+ return { keyColorContrast: null, passesAA: true, passesAALargeText: true, autoNormalizedValue };
653
+ }
654
+ const keyColor = newtone.getColor(
655
+ paletteOklchHue,
656
+ palette.saturation,
657
+ dynamicRange,
658
+ effectiveKeyColor,
659
+ paletteDesat,
660
+ palettePhg
661
+ );
662
+ const contrast = newtone.getWcagContrastRatio(keyColor.srgb, background.srgb);
663
+ return {
664
+ keyColorContrast: contrast,
665
+ passesAA: contrast >= 4.5,
666
+ passesAALargeText: contrast >= 3,
667
+ autoNormalizedValue
668
+ };
669
+ }, [state, paletteIndex]);
670
+ }
671
+ var ACHROMATIC_THRESHOLD = 5e-3;
672
+ var traditionalHueLut = null;
673
+ function buildTraditionalHueLut() {
674
+ const lut = new Array(360);
675
+ for (let h = 0; h < 360; h++) {
676
+ lut[h] = traditionalHueToOklch(h);
677
+ }
678
+ return lut;
679
+ }
680
+ function oklchHueToTraditional(oklchHue) {
681
+ if (!traditionalHueLut) {
682
+ traditionalHueLut = buildTraditionalHueLut();
683
+ }
684
+ const target = (oklchHue % 360 + 360) % 360;
685
+ let bestHue = 0;
686
+ let bestDelta = Infinity;
687
+ for (let h = 0; h < 360; h++) {
688
+ const raw = Math.abs(traditionalHueLut[h] - target);
689
+ const delta = Math.min(raw, 360 - raw);
690
+ if (delta < bestDelta) {
691
+ bestDelta = delta;
692
+ bestHue = h;
693
+ }
694
+ }
695
+ return bestHue;
696
+ }
697
+ function hexToPaletteParams(hex, dynamicRange) {
698
+ const cleaned = hex.startsWith("#") ? hex.slice(1) : hex;
699
+ if (!/^[0-9a-fA-F]{3}$/.test(cleaned) && !/^[0-9a-fA-F]{6}$/.test(cleaned)) {
700
+ return null;
701
+ }
702
+ const srgb = newtone.hexToSrgb(hex);
703
+ const oklch = newtone.srgbToOklch(srgb);
704
+ const normalizedValue = newtone.lightnessToNormalizedValue(dynamicRange, oklch.L);
705
+ if (oklch.C < ACHROMATIC_THRESHOLD) {
706
+ return { hue: 0, saturation: 0, normalizedValue };
707
+ }
708
+ const hue = oklchHueToTraditional(oklch.h);
709
+ const maxChroma = newtone.findMaxChromaInGamut(oklch.L, oklch.h);
710
+ const saturation = maxChroma > 0 ? Math.min(100, Math.round(oklch.C / maxChroma * 100)) : 0;
711
+ return { hue, saturation, normalizedValue };
712
+ }
318
713
 
319
714
  // src/panels/PalettePanel.tsx
320
715
  var STRENGTH_OPTIONS = [
@@ -323,16 +718,124 @@ var STRENGTH_OPTIONS = [
323
718
  { label: "Medium", value: "medium" },
324
719
  { label: "Hard", value: "hard" }
325
720
  ];
326
- function PalettePanel({ palette, index, dispatch, previewColors }) {
721
+ function getHexAtNv(previewColors, nv) {
722
+ const idx = Math.round((1 - nv) * (previewColors.length - 1));
723
+ const clamped = Math.max(0, Math.min(previewColors.length - 1, idx));
724
+ return newtone.srgbToHex(previewColors[clamped].srgb);
725
+ }
726
+ function PalettePanel({ palette, index, dispatch, previewColors, state }) {
327
727
  const tokens = components.useTokens(1);
328
728
  const hueRange = SEMANTIC_HUE_RANGES[index];
329
- return /* @__PURE__ */ React3__default.default.createElement(components.Card, { elevation: 1, style: styles.container }, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, palette.name), previewColors && /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.inlineSwatches }, previewColors.map((color, i) => /* @__PURE__ */ React3__default.default.createElement(
729
+ const isNeutral = index === 0;
730
+ const mode = state.preview.mode;
731
+ const effectiveKeyColor = mode === "dark" ? palette.keyColorDark : palette.keyColor;
732
+ const setKeyColorAction = mode === "dark" ? "SET_PALETTE_KEY_COLOR_DARK" : "SET_PALETTE_KEY_COLOR";
733
+ const clearKeyColorAction = mode === "dark" ? "CLEAR_PALETTE_KEY_COLOR_DARK" : "CLEAR_PALETTE_KEY_COLOR";
734
+ const hexActionType = mode === "dark" ? "SET_PALETTE_FROM_HEX_DARK" : "SET_PALETTE_FROM_HEX";
735
+ const wcag = useWcagValidation(state, index);
736
+ const [hexText, setHexText] = React__default.default.useState("");
737
+ const [hexError, setHexError] = React__default.default.useState("");
738
+ const [isEditingHex, setIsEditingHex] = React__default.default.useState(false);
739
+ const [isHexUserSet, setIsHexUserSet] = React__default.default.useState(false);
740
+ React__default.default.useEffect(() => {
741
+ setHexText("");
742
+ setHexError("");
743
+ setIsEditingHex(false);
744
+ setIsHexUserSet(false);
745
+ }, [mode]);
746
+ const displayedHex = React__default.default.useMemo(() => {
747
+ if (!previewColors || previewColors.length === 0) return "";
748
+ const nv = effectiveKeyColor ?? wcag.autoNormalizedValue;
749
+ return getHexAtNv(previewColors, nv);
750
+ }, [previewColors, effectiveKeyColor, wcag.autoNormalizedValue]);
751
+ React__default.default.useEffect(() => {
752
+ if (!isEditingHex && !isHexUserSet) {
753
+ setHexText(displayedHex);
754
+ }
755
+ }, [displayedHex, isEditingHex, isHexUserSet]);
756
+ const dynamicRange = React__default.default.useMemo(() => {
757
+ const light = state.globalHueGrading.light.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.light.hue), strength: state.globalHueGrading.light.strength } : void 0;
758
+ const dark = state.globalHueGrading.dark.strength !== "none" ? { hue: traditionalHueToOklch(state.globalHueGrading.dark.hue), strength: state.globalHueGrading.dark.strength } : void 0;
759
+ const hueGrading = light || dark ? { light, dark } : void 0;
760
+ return {
761
+ lightest: state.dynamicRange.lightest,
762
+ darkest: state.dynamicRange.darkest,
763
+ ...hueGrading ? { hueGrading } : {}
764
+ };
765
+ }, [state.dynamicRange, state.globalHueGrading]);
766
+ const handleHexSubmit = React__default.default.useCallback(() => {
767
+ setIsEditingHex(false);
768
+ const trimmed = hexText.trim();
769
+ if (!trimmed) {
770
+ setHexError("");
771
+ return;
772
+ }
773
+ const hex = trimmed.startsWith("#") ? trimmed : `#${trimmed}`;
774
+ const params = hexToPaletteParams(hex, dynamicRange);
775
+ if (!params) {
776
+ setHexError("Invalid hex color");
777
+ return;
778
+ }
779
+ setHexError("");
780
+ setIsHexUserSet(true);
781
+ dispatch({
782
+ type: hexActionType,
783
+ index,
784
+ hue: params.hue,
785
+ saturation: params.saturation,
786
+ keyColor: params.normalizedValue
787
+ });
788
+ }, [hexText, dynamicRange, dispatch, index, hexActionType]);
789
+ const handleClearKeyColor = React__default.default.useCallback(() => {
790
+ dispatch({ type: clearKeyColorAction, index });
791
+ setHexError("");
792
+ setIsHexUserSet(false);
793
+ }, [dispatch, index, clearKeyColorAction]);
794
+ const wcagWarning = React__default.default.useMemo(() => {
795
+ if (effectiveKeyColor === void 0 || wcag.keyColorContrast === null) return void 0;
796
+ if (wcag.passesAA) return void 0;
797
+ const ratio = wcag.keyColorContrast.toFixed(1);
798
+ if (wcag.passesAALargeText) {
799
+ return `Contrast ${ratio}:1 \u2014 passes large text (AA) but fails normal text (requires 4.5:1)`;
800
+ }
801
+ return `Contrast ${ratio}:1 \u2014 fails WCAG AA (requires 4.5:1 for normal text, 3:1 for large text)`;
802
+ }, [effectiveKeyColor, wcag]);
803
+ return /* @__PURE__ */ React__default.default.createElement(components.Card, { elevation: 1, style: styles.container }, /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, palette.name), !isNeutral && previewColors && /* @__PURE__ */ React__default.default.createElement(React__default.default.Fragment, null, /* @__PURE__ */ React__default.default.createElement(
804
+ components.ColorScaleSlider,
805
+ {
806
+ colors: previewColors,
807
+ value: effectiveKeyColor ?? wcag.autoNormalizedValue,
808
+ onValueChange: (nv) => {
809
+ setIsHexUserSet(false);
810
+ dispatch({ type: setKeyColorAction, index, normalizedValue: nv });
811
+ },
812
+ label: "Key Color",
813
+ warning: wcagWarning,
814
+ trimEnds: true,
815
+ snap: true,
816
+ animateValue: true
817
+ }
818
+ ), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.hexRow }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.hexInputContainer }, /* @__PURE__ */ React__default.default.createElement(
819
+ components.TextInput,
820
+ {
821
+ label: "Hex",
822
+ value: hexText,
823
+ onChangeText: (text) => {
824
+ setIsEditingHex(true);
825
+ setHexText(text);
826
+ setHexError("");
827
+ },
828
+ onBlur: handleHexSubmit,
829
+ onSubmitEditing: handleHexSubmit,
830
+ placeholder: "#000000"
831
+ }
832
+ )), effectiveKeyColor !== void 0 && /* @__PURE__ */ React__default.default.createElement(reactNative.Pressable, { onPress: handleClearKeyColor, style: styles.autoButton }, /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles.autoText, { color: newtone.srgbToHex(tokens.interactive.srgb) }] }, "Auto"))), hexError !== "" && /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles.errorText, { color: newtone.srgbToHex(tokens.error.srgb) }] }, hexError)), isNeutral && previewColors && /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.inlineSwatches }, previewColors.map((color, i) => /* @__PURE__ */ React__default.default.createElement(
330
833
  reactNative.View,
331
834
  {
332
835
  key: i,
333
836
  style: [styles.inlineSwatch, { backgroundColor: newtone.srgbToHex(color.srgb) }]
334
837
  }
335
- ))), /* @__PURE__ */ React3__default.default.createElement(
838
+ ))), /* @__PURE__ */ React__default.default.createElement(
336
839
  components.HueSlider,
337
840
  {
338
841
  value: palette.hue,
@@ -341,7 +844,7 @@ function PalettePanel({ palette, index, dispatch, previewColors }) {
341
844
  showValue: true,
342
845
  ...hueRange ? { min: hueRange.min, max: hueRange.max } : {}
343
846
  }
344
- ), /* @__PURE__ */ React3__default.default.createElement(
847
+ ), /* @__PURE__ */ React__default.default.createElement(
345
848
  components.Slider,
346
849
  {
347
850
  value: palette.saturation,
@@ -351,7 +854,7 @@ function PalettePanel({ palette, index, dispatch, previewColors }) {
351
854
  label: "Saturation",
352
855
  showValue: true
353
856
  }
354
- ), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.row }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.flex }, /* @__PURE__ */ React3__default.default.createElement(
857
+ ), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.flex }, /* @__PURE__ */ React__default.default.createElement(
355
858
  components.Select,
356
859
  {
357
860
  options: STRENGTH_OPTIONS,
@@ -359,14 +862,14 @@ function PalettePanel({ palette, index, dispatch, previewColors }) {
359
862
  onValueChange: (strength) => dispatch({ type: "SET_PALETTE_DESAT_STRENGTH", index, strength }),
360
863
  label: "Desaturation"
361
864
  }
362
- )), palette.desaturationStrength !== "none" && /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.toggleContainer }, /* @__PURE__ */ React3__default.default.createElement(
865
+ )), palette.desaturationStrength !== "none" && /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.toggleContainer }, /* @__PURE__ */ React__default.default.createElement(
363
866
  components.Toggle,
364
867
  {
365
868
  value: palette.desaturationDirection === "dark",
366
869
  onValueChange: (v) => dispatch({ type: "SET_PALETTE_DESAT_DIRECTION", index, direction: v ? "dark" : "light" }),
367
870
  label: "Invert"
368
871
  }
369
- ))), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.row }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.flex }, /* @__PURE__ */ React3__default.default.createElement(
872
+ ))), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.flex }, /* @__PURE__ */ React__default.default.createElement(
370
873
  components.Select,
371
874
  {
372
875
  options: STRENGTH_OPTIONS,
@@ -374,14 +877,14 @@ function PalettePanel({ palette, index, dispatch, previewColors }) {
374
877
  onValueChange: (strength) => dispatch({ type: "SET_PALETTE_HUE_GRADE_STRENGTH", index, strength }),
375
878
  label: "Hue Grading"
376
879
  }
377
- )), palette.hueGradeStrength !== "none" && /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.toggleContainer }, /* @__PURE__ */ React3__default.default.createElement(
880
+ )), palette.hueGradeStrength !== "none" && /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles.toggleContainer }, /* @__PURE__ */ React__default.default.createElement(
378
881
  components.Toggle,
379
882
  {
380
883
  value: palette.hueGradeDirection === "dark",
381
884
  onValueChange: (v) => dispatch({ type: "SET_PALETTE_HUE_GRADE_DIRECTION", index, direction: v ? "dark" : "light" }),
382
885
  label: "Invert"
383
886
  }
384
- ))), palette.hueGradeStrength !== "none" && /* @__PURE__ */ React3__default.default.createElement(
887
+ ))), palette.hueGradeStrength !== "none" && /* @__PURE__ */ React__default.default.createElement(
385
888
  components.HueSlider,
386
889
  {
387
890
  value: palette.hueGradeHue,
@@ -420,6 +923,25 @@ var styles = reactNative.StyleSheet.create({
420
923
  },
421
924
  toggleContainer: {
422
925
  paddingBottom: 2
926
+ },
927
+ hexRow: {
928
+ flexDirection: "row",
929
+ alignItems: "flex-end",
930
+ gap: 8
931
+ },
932
+ hexInputContainer: {
933
+ flex: 1
934
+ },
935
+ autoButton: {
936
+ paddingBottom: 6
937
+ },
938
+ autoText: {
939
+ fontSize: 13,
940
+ fontWeight: "600"
941
+ },
942
+ errorText: {
943
+ fontSize: 12,
944
+ fontWeight: "500"
423
945
  }
424
946
  });
425
947
  var STRENGTH_OPTIONS2 = [
@@ -430,7 +952,7 @@ var STRENGTH_OPTIONS2 = [
430
952
  ];
431
953
  function GlobalPanel({ state, dispatch }) {
432
954
  const tokens = components.useTokens(1);
433
- return /* @__PURE__ */ React3__default.default.createElement(components.Card, { elevation: 1, style: styles2.container }, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles2.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Dynamic Range"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.row }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React3__default.default.createElement(
955
+ return /* @__PURE__ */ React__default.default.createElement(components.Card, { elevation: 1, style: styles2.container }, /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles2.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Dynamic Range"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React__default.default.createElement(
434
956
  components.Slider,
435
957
  {
436
958
  value: Math.round(state.dynamicRange.lightest * 100),
@@ -440,7 +962,7 @@ function GlobalPanel({ state, dispatch }) {
440
962
  label: "Lightest",
441
963
  showValue: true
442
964
  }
443
- )), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React3__default.default.createElement(
965
+ )), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React__default.default.createElement(
444
966
  components.Slider,
445
967
  {
446
968
  value: Math.round(state.dynamicRange.darkest * 100),
@@ -450,7 +972,7 @@ function GlobalPanel({ state, dispatch }) {
450
972
  label: "Darkest",
451
973
  showValue: true
452
974
  }
453
- ))), /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles2.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Global Hue Grading \u2014 Light End"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.row }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React3__default.default.createElement(
975
+ ))), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles2.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Global Hue Grading \u2014 Light End"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React__default.default.createElement(
454
976
  components.Select,
455
977
  {
456
978
  options: STRENGTH_OPTIONS2,
@@ -458,7 +980,7 @@ function GlobalPanel({ state, dispatch }) {
458
980
  onValueChange: (s) => dispatch({ type: "SET_GLOBAL_GRADE_LIGHT_STRENGTH", strength: s }),
459
981
  label: "Strength"
460
982
  }
461
- )), state.globalHueGrading.light.strength !== "none" && /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React3__default.default.createElement(
983
+ )), state.globalHueGrading.light.strength !== "none" && /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React__default.default.createElement(
462
984
  components.HueSlider,
463
985
  {
464
986
  value: state.globalHueGrading.light.hue,
@@ -466,7 +988,7 @@ function GlobalPanel({ state, dispatch }) {
466
988
  label: "Target Hue",
467
989
  showValue: true
468
990
  }
469
- ))), /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles2.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Global Hue Grading \u2014 Dark End"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.row }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React3__default.default.createElement(
991
+ ))), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles2.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Global Hue Grading \u2014 Dark End"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React__default.default.createElement(
470
992
  components.Select,
471
993
  {
472
994
  options: STRENGTH_OPTIONS2,
@@ -474,7 +996,7 @@ function GlobalPanel({ state, dispatch }) {
474
996
  onValueChange: (s) => dispatch({ type: "SET_GLOBAL_GRADE_DARK_STRENGTH", strength: s }),
475
997
  label: "Strength"
476
998
  }
477
- )), state.globalHueGrading.dark.strength !== "none" && /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React3__default.default.createElement(
999
+ )), state.globalHueGrading.dark.strength !== "none" && /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles2.flex }, /* @__PURE__ */ React__default.default.createElement(
478
1000
  components.HueSlider,
479
1001
  {
480
1002
  value: state.globalHueGrading.dark.hue,
@@ -516,7 +1038,7 @@ function PreviewCard({
516
1038
  dispatch
517
1039
  }) {
518
1040
  const tokens = components.useTokens(1);
519
- return /* @__PURE__ */ React3__default.default.createElement(components.Card, { elevation: 1, style: styles3.container }, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles3.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Component Preview"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles3.controls }, /* @__PURE__ */ React3__default.default.createElement(
1041
+ return /* @__PURE__ */ React__default.default.createElement(components.Card, { elevation: 1, style: styles3.container }, /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles3.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Component Preview"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles3.controls }, /* @__PURE__ */ React__default.default.createElement(
520
1042
  components.Select,
521
1043
  {
522
1044
  options: THEME_OPTIONS,
@@ -524,11 +1046,11 @@ function PreviewCard({
524
1046
  onValueChange: (t) => dispatch({ type: "SET_PREVIEW_THEME", theme: t }),
525
1047
  label: "Theme"
526
1048
  }
527
- )), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: previewStyles.wrapper }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: previewStyles.row }, /* @__PURE__ */ React3__default.default.createElement(components.Button, { variant: "primary", size: "sm" }, "Primary"), /* @__PURE__ */ React3__default.default.createElement(components.Button, { variant: "secondary", size: "sm" }, "Secondary"), /* @__PURE__ */ React3__default.default.createElement(components.Button, { variant: "ghost", size: "sm" }, "Ghost"), /* @__PURE__ */ React3__default.default.createElement(components.Button, { variant: "outline", size: "sm" }, "Outline")), /* @__PURE__ */ React3__default.default.createElement(components.TextInput, { label: "Sample Input", value: "Hello, Newtone", onChangeText: () => {
528
- } }), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: previewStyles.row }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: [previewStyles.statusDot, { backgroundColor: newtone.srgbToHex(tokens.success.srgb) }] }), /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [previewStyles.statusText, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Success"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: [previewStyles.statusDot, { backgroundColor: newtone.srgbToHex(tokens.warning.srgb) }] }), /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [previewStyles.statusText, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Warning"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: [previewStyles.statusDot, { backgroundColor: newtone.srgbToHex(tokens.error.srgb) }] }), /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [previewStyles.statusText, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Error"))));
1049
+ )), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: previewStyles.wrapper }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: previewStyles.row }, /* @__PURE__ */ React__default.default.createElement(components.Button, { variant: "primary", size: "sm" }, "Primary"), /* @__PURE__ */ React__default.default.createElement(components.Button, { variant: "secondary", size: "sm" }, "Secondary"), /* @__PURE__ */ React__default.default.createElement(components.Button, { variant: "ghost", size: "sm" }, "Ghost"), /* @__PURE__ */ React__default.default.createElement(components.Button, { variant: "outline", size: "sm" }, "Outline")), /* @__PURE__ */ React__default.default.createElement(components.TextInput, { label: "Sample Input", value: "Hello, Newtone", onChangeText: () => {
1050
+ } }), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: previewStyles.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: [previewStyles.statusDot, { backgroundColor: newtone.srgbToHex(tokens.success.srgb) }] }), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [previewStyles.statusText, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Success"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: [previewStyles.statusDot, { backgroundColor: newtone.srgbToHex(tokens.warning.srgb) }] }), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [previewStyles.statusText, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Warning"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: [previewStyles.statusDot, { backgroundColor: newtone.srgbToHex(tokens.error.srgb) }] }), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [previewStyles.statusText, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Error"))));
529
1051
  }
530
1052
  function PreviewPanel({ state, dispatch, themeConfig }) {
531
- return /* @__PURE__ */ React3__default.default.createElement(
1053
+ return /* @__PURE__ */ React__default.default.createElement(
532
1054
  components.NewtoneProvider,
533
1055
  {
534
1056
  key: `${state.preview.mode}-${state.preview.theme}`,
@@ -536,7 +1058,7 @@ function PreviewPanel({ state, dispatch, themeConfig }) {
536
1058
  initialMode: state.preview.mode,
537
1059
  initialTheme: state.preview.theme
538
1060
  },
539
- /* @__PURE__ */ React3__default.default.createElement(PreviewCard, { state, dispatch })
1061
+ /* @__PURE__ */ React__default.default.createElement(PreviewCard, { state, dispatch })
540
1062
  );
541
1063
  }
542
1064
  var styles3 = reactNative.StyleSheet.create({
@@ -583,7 +1105,11 @@ function toCSS(state) {
583
1105
  mode,
584
1106
  config.themes.neutral,
585
1107
  1,
586
- config.elevation.offsets
1108
+ config.elevation.offsets,
1109
+ config.spacing,
1110
+ config.radius,
1111
+ config.typography,
1112
+ config.icons
587
1113
  );
588
1114
  const selector = mode === "light" ? ":root" : '[data-theme="dark"]';
589
1115
  css += `${selector} {
@@ -632,12 +1158,12 @@ function toJSON(state) {
632
1158
  // src/panels/ExportPanel.tsx
633
1159
  function ExportPanel({ state }) {
634
1160
  const tokens = components.useTokens(1);
635
- const [format, setFormat] = React3.useState("css");
636
- const [copied, setCopied] = React3.useState(false);
637
- const output = React3.useMemo(() => {
1161
+ const [format, setFormat] = React.useState("css");
1162
+ const [copied, setCopied] = React.useState(false);
1163
+ const output = React.useMemo(() => {
638
1164
  return format === "css" ? toCSS(state) : toJSON(state);
639
1165
  }, [state, format]);
640
- const handleCopy = React3.useCallback(() => {
1166
+ const handleCopy = React.useCallback(() => {
641
1167
  if (typeof navigator !== "undefined" && navigator.clipboard) {
642
1168
  navigator.clipboard.writeText(output).then(() => {
643
1169
  setCopied(true);
@@ -645,7 +1171,7 @@ function ExportPanel({ state }) {
645
1171
  });
646
1172
  }
647
1173
  }, [output]);
648
- return /* @__PURE__ */ React3__default.default.createElement(components.Card, { elevation: 1, style: styles4.container }, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles4.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Export"), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles4.tabs }, /* @__PURE__ */ React3__default.default.createElement(
1174
+ return /* @__PURE__ */ React__default.default.createElement(components.Card, { elevation: 1, style: styles4.container }, /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles4.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Export"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles4.tabs }, /* @__PURE__ */ React__default.default.createElement(
649
1175
  components.Button,
650
1176
  {
651
1177
  variant: format === "css" ? "primary" : "ghost",
@@ -653,7 +1179,7 @@ function ExportPanel({ state }) {
653
1179
  onPress: () => setFormat("css")
654
1180
  },
655
1181
  "CSS Variables"
656
- ), /* @__PURE__ */ React3__default.default.createElement(
1182
+ ), /* @__PURE__ */ React__default.default.createElement(
657
1183
  components.Button,
658
1184
  {
659
1185
  variant: format === "json" ? "primary" : "ghost",
@@ -661,7 +1187,7 @@ function ExportPanel({ state }) {
661
1187
  onPress: () => setFormat("json")
662
1188
  },
663
1189
  "JSON"
664
- ), /* @__PURE__ */ React3__default.default.createElement(components.Button, { variant: "outline", size: "sm", onPress: handleCopy }, copied ? "Copied!" : "Copy")), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: [styles4.codeBlock, { backgroundColor: newtone.srgbToHex(tokens.backgroundSunken.srgb) }] }, /* @__PURE__ */ React3__default.default.createElement(
1190
+ ), /* @__PURE__ */ React__default.default.createElement(components.Button, { variant: "outline", size: "sm", onPress: handleCopy }, copied ? "Copied!" : "Copy")), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: [styles4.codeBlock, { backgroundColor: newtone.srgbToHex(tokens.backgroundSunken.srgb) }] }, /* @__PURE__ */ React__default.default.createElement(
665
1191
  reactNative.Text,
666
1192
  {
667
1193
  style: [styles4.code, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }],
@@ -695,6 +1221,113 @@ var styles4 = reactNative.StyleSheet.create({
695
1221
  lineHeight: 16
696
1222
  }
697
1223
  });
1224
+ var ICON_VARIANT_OPTIONS = [
1225
+ { label: "Outlined", value: "outlined" },
1226
+ { label: "Rounded", value: "rounded" },
1227
+ { label: "Sharp", value: "sharp" }
1228
+ ];
1229
+ var ICON_WEIGHT_OPTIONS = [
1230
+ { label: "100", value: "100" },
1231
+ { label: "200", value: "200" },
1232
+ { label: "300", value: "300" },
1233
+ { label: "400", value: "400" },
1234
+ { label: "500", value: "500" },
1235
+ { label: "600", value: "600" },
1236
+ { label: "700", value: "700" }
1237
+ ];
1238
+ function DesignPanel({ state, dispatch }) {
1239
+ const tokens = components.useTokens(1);
1240
+ const spacingPreset = state.spacing?.preset ?? "md";
1241
+ const intensity = state.roundness?.intensity ?? 0.5;
1242
+ const baseSize = state.typography?.scale.baseSize ?? 16;
1243
+ const ratio = state.typography?.scale.ratio ?? 1.25;
1244
+ const variant = state.icons?.variant ?? "rounded";
1245
+ const weight = state.icons?.weight ?? 400;
1246
+ return /* @__PURE__ */ React__default.default.createElement(components.Card, { elevation: 1, style: styles5.container }, /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles5.title, { color: newtone.srgbToHex(tokens.textPrimary.srgb) }] }, "Design System"), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles5.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Spacing"), /* @__PURE__ */ React__default.default.createElement(
1247
+ components.Select,
1248
+ {
1249
+ value: spacingPreset,
1250
+ onValueChange: (preset) => dispatch({ type: "SET_SPACING_PRESET", preset }),
1251
+ options: [
1252
+ { value: "xs", label: "Extra Small" },
1253
+ { value: "sm", label: "Small" },
1254
+ { value: "md", label: "Medium" },
1255
+ { value: "lg", label: "Large" },
1256
+ { value: "xl", label: "Extra Large" }
1257
+ ],
1258
+ label: "Preset"
1259
+ }
1260
+ ), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles5.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Roundness"), /* @__PURE__ */ React__default.default.createElement(
1261
+ components.Slider,
1262
+ {
1263
+ value: Math.round(intensity * 100),
1264
+ onValueChange: (v) => dispatch({ type: "SET_ROUNDNESS_INTENSITY", intensity: v / 100 }),
1265
+ min: 0,
1266
+ max: 100,
1267
+ label: "Intensity",
1268
+ showValue: true
1269
+ }
1270
+ ), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles5.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Typography"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles5.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles5.flex }, /* @__PURE__ */ React__default.default.createElement(
1271
+ components.Slider,
1272
+ {
1273
+ value: baseSize,
1274
+ onValueChange: (v) => dispatch({ type: "SET_TYPOGRAPHY_BASE_SIZE", baseSize: v }),
1275
+ min: 12,
1276
+ max: 24,
1277
+ step: 1,
1278
+ label: "Base Size",
1279
+ showValue: true
1280
+ }
1281
+ )), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles5.flex }, /* @__PURE__ */ React__default.default.createElement(
1282
+ components.Slider,
1283
+ {
1284
+ value: Math.round(ratio * 100),
1285
+ onValueChange: (v) => dispatch({ type: "SET_TYPOGRAPHY_RATIO", ratio: v / 100 }),
1286
+ min: 110,
1287
+ max: 150,
1288
+ step: 5,
1289
+ label: "Scale Ratio",
1290
+ showValue: true
1291
+ }
1292
+ ))), /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [styles5.subtitle, { color: newtone.srgbToHex(tokens.textSecondary.srgb) }] }, "Icons"), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles5.row }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles5.flex }, /* @__PURE__ */ React__default.default.createElement(
1293
+ components.Select,
1294
+ {
1295
+ options: ICON_VARIANT_OPTIONS,
1296
+ value: variant,
1297
+ onValueChange: (v) => dispatch({ type: "SET_ICON_VARIANT", variant: v }),
1298
+ label: "Variant"
1299
+ }
1300
+ )), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles5.flex }, /* @__PURE__ */ React__default.default.createElement(
1301
+ components.Select,
1302
+ {
1303
+ options: ICON_WEIGHT_OPTIONS,
1304
+ value: weight.toString(),
1305
+ onValueChange: (v) => dispatch({ type: "SET_ICON_WEIGHT", weight: parseInt(v) }),
1306
+ label: "Weight"
1307
+ }
1308
+ ))));
1309
+ }
1310
+ var styles5 = reactNative.StyleSheet.create({
1311
+ container: {
1312
+ gap: 12
1313
+ },
1314
+ title: {
1315
+ fontSize: 16,
1316
+ fontWeight: "700"
1317
+ },
1318
+ subtitle: {
1319
+ fontSize: 13,
1320
+ fontWeight: "600",
1321
+ marginTop: 4
1322
+ },
1323
+ row: {
1324
+ flexDirection: "row",
1325
+ gap: 12
1326
+ },
1327
+ flex: {
1328
+ flex: 1
1329
+ }
1330
+ });
698
1331
 
699
1332
  // src/Configurator.tsx
700
1333
  function Configurator({
@@ -706,20 +1339,20 @@ function Configurator({
706
1339
  const { state, dispatch, themeConfig, reset } = useConfigurator(initialState);
707
1340
  const tokens = components.useTokens(1);
708
1341
  const { setMode } = components.useNewtoneTheme();
709
- const isInitialRender = React3.useRef(true);
710
- const [activePaletteIndex, setActivePaletteIndex] = React3.useState(0);
1342
+ const isInitialRender = React.useRef(true);
1343
+ const [activePaletteIndex, setActivePaletteIndex] = React.useState(0);
711
1344
  const previews = usePreviewColors(state);
712
- React3.useEffect(() => {
1345
+ React.useEffect(() => {
713
1346
  setMode(state.preview.mode);
714
1347
  }, []);
715
- React3.useEffect(() => {
1348
+ React.useEffect(() => {
716
1349
  if (isInitialRender.current) {
717
1350
  isInitialRender.current = false;
718
1351
  return;
719
1352
  }
720
1353
  onChange?.(state, themeConfig);
721
1354
  }, [state, themeConfig, onChange]);
722
- return /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: [styles5.container, { backgroundColor: newtone.srgbToHex(tokens.backgroundSunken.srgb) }] }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles5.topBar }, /* @__PURE__ */ React3__default.default.createElement(
1355
+ return /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: [styles6.container, { backgroundColor: newtone.srgbToHex(tokens.backgroundSunken.srgb) }] }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles6.topBar }, /* @__PURE__ */ React__default.default.createElement(
723
1356
  components.Toggle,
724
1357
  {
725
1358
  value: state.preview.mode === "dark",
@@ -730,21 +1363,21 @@ function Configurator({
730
1363
  },
731
1364
  label: "Dark Mode"
732
1365
  }
733
- )), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles5.topRow }, /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles5.topRowPanel }, /* @__PURE__ */ React3__default.default.createElement(GlobalPanel, { state, dispatch })), showPreview && /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles5.topRowPanel }, /* @__PURE__ */ React3__default.default.createElement(PreviewPanel, { state, dispatch, themeConfig }))), /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles5.tabBar }, state.palettes.map((palette, index) => /* @__PURE__ */ React3__default.default.createElement(
1366
+ )), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles6.topRow }, /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles6.topRowPanel }, /* @__PURE__ */ React__default.default.createElement(GlobalPanel, { state, dispatch })), showPreview && /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles6.topRowPanel }, /* @__PURE__ */ React__default.default.createElement(PreviewPanel, { state, dispatch, themeConfig }))), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles6.designRow }, /* @__PURE__ */ React__default.default.createElement(DesignPanel, { state, dispatch })), /* @__PURE__ */ React__default.default.createElement(reactNative.View, { style: styles6.tabBar }, state.palettes.map((palette, index) => /* @__PURE__ */ React__default.default.createElement(
734
1367
  reactNative.Pressable,
735
1368
  {
736
1369
  key: index,
737
1370
  onPress: () => setActivePaletteIndex(index),
738
1371
  style: [
739
- styles5.tab,
1372
+ styles6.tab,
740
1373
  {
741
1374
  backgroundColor: index === activePaletteIndex ? newtone.srgbToHex(tokens.background.srgb) : "transparent",
742
1375
  borderColor: index === activePaletteIndex ? newtone.srgbToHex(tokens.border.srgb) : "transparent"
743
1376
  }
744
1377
  ]
745
1378
  },
746
- /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [
747
- styles5.tabText,
1379
+ /* @__PURE__ */ React__default.default.createElement(reactNative.Text, { style: [
1380
+ styles6.tabText,
748
1381
  {
749
1382
  color: newtone.srgbToHex(
750
1383
  index === activePaletteIndex ? tokens.textPrimary.srgb : tokens.textSecondary.srgb
@@ -752,15 +1385,16 @@ function Configurator({
752
1385
  fontWeight: index === activePaletteIndex ? "700" : "500"
753
1386
  }
754
1387
  ] }, palette.name)
755
- ))), /* @__PURE__ */ React3__default.default.createElement(
1388
+ ))), /* @__PURE__ */ React__default.default.createElement(
756
1389
  PalettePanel,
757
1390
  {
758
1391
  palette: state.palettes[activePaletteIndex],
759
1392
  index: activePaletteIndex,
760
1393
  dispatch,
761
- previewColors: previews[activePaletteIndex]
1394
+ previewColors: previews[activePaletteIndex],
1395
+ state
762
1396
  }
763
- ), showExport && /* @__PURE__ */ React3__default.default.createElement(ExportPanel, { state }), /* @__PURE__ */ React3__default.default.createElement(
1397
+ ), showExport && /* @__PURE__ */ React__default.default.createElement(ExportPanel, { state }), /* @__PURE__ */ React__default.default.createElement(
764
1398
  "button",
765
1399
  {
766
1400
  onClick: () => reset(),
@@ -779,7 +1413,7 @@ function Configurator({
779
1413
  "Reset to Defaults"
780
1414
  ));
781
1415
  }
782
- var styles5 = reactNative.StyleSheet.create({
1416
+ var styles6 = reactNative.StyleSheet.create({
783
1417
  container: {
784
1418
  borderRadius: 8,
785
1419
  padding: 16,
@@ -799,6 +1433,9 @@ var styles5 = reactNative.StyleSheet.create({
799
1433
  topRowPanel: {
800
1434
  flex: 1
801
1435
  },
1436
+ designRow: {
1437
+ marginBottom: 4
1438
+ },
802
1439
  tabBar: {
803
1440
  flexDirection: "row",
804
1441
  gap: 4,
@@ -817,11 +1454,15 @@ var styles5 = reactNative.StyleSheet.create({
817
1454
 
818
1455
  exports.Configurator = Configurator;
819
1456
  exports.DEFAULT_CONFIGURATOR_STATE = DEFAULT_CONFIGURATOR_STATE;
1457
+ exports.SEMANTIC_HUE_RANGES = SEMANTIC_HUE_RANGES;
1458
+ exports.hexToPaletteParams = hexToPaletteParams;
1459
+ exports.oklchHueToTraditional = oklchHueToTraditional;
820
1460
  exports.toCSS = toCSS;
821
1461
  exports.toJSON = toJSON;
822
1462
  exports.toThemeConfig = toThemeConfig;
823
1463
  exports.traditionalHueToOklch = traditionalHueToOklch;
824
1464
  exports.useConfigurator = useConfigurator;
825
1465
  exports.usePreviewColors = usePreviewColors;
1466
+ exports.useWcagValidation = useWcagValidation;
826
1467
  //# sourceMappingURL=index.cjs.map
827
1468
  //# sourceMappingURL=index.cjs.map