@fragments-sdk/ui 0.9.6 → 0.10.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 (78) hide show
  1. package/README.md +32 -24
  2. package/dist/codeblock.cjs +25 -29
  3. package/dist/codeblock.cjs.map +1 -1
  4. package/dist/codeblock.js +25 -29
  5. package/dist/codeblock.js.map +1 -1
  6. package/dist/components/Chip/index.cjs +2 -1
  7. package/dist/components/Chip/index.cjs.map +1 -1
  8. package/dist/components/Chip/index.d.ts.map +1 -1
  9. package/dist/components/Chip/index.js +2 -1
  10. package/dist/components/Chip/index.js.map +1 -1
  11. package/dist/components/CodeBlock/index.d.ts.map +1 -1
  12. package/dist/components/Command/index.cjs +6 -0
  13. package/dist/components/Command/index.cjs.map +1 -1
  14. package/dist/components/Command/index.d.ts.map +1 -1
  15. package/dist/components/Command/index.js +6 -0
  16. package/dist/components/Command/index.js.map +1 -1
  17. package/dist/components/DataTable/index.cjs +26 -26
  18. package/dist/components/DataTable/index.cjs.map +1 -1
  19. package/dist/components/DataTable/index.d.ts.map +1 -1
  20. package/dist/components/DataTable/index.js +26 -26
  21. package/dist/components/DataTable/index.js.map +1 -1
  22. package/dist/components/Listbox/index.cjs +6 -0
  23. package/dist/components/Listbox/index.cjs.map +1 -1
  24. package/dist/components/Listbox/index.d.ts.map +1 -1
  25. package/dist/components/Listbox/index.js +6 -0
  26. package/dist/components/Listbox/index.js.map +1 -1
  27. package/dist/components/Loading/index.cjs +2 -12
  28. package/dist/components/Loading/index.cjs.map +1 -1
  29. package/dist/components/Loading/index.d.ts.map +1 -1
  30. package/dist/components/Loading/index.js +2 -12
  31. package/dist/components/Loading/index.js.map +1 -1
  32. package/dist/components/NavigationMenu/index.cjs +12 -1
  33. package/dist/components/NavigationMenu/index.cjs.map +1 -1
  34. package/dist/components/NavigationMenu/index.d.ts.map +1 -1
  35. package/dist/components/NavigationMenu/index.js +12 -1
  36. package/dist/components/NavigationMenu/index.js.map +1 -1
  37. package/dist/components/Skeleton/index.cjs +3 -3
  38. package/dist/components/Skeleton/index.cjs.map +1 -1
  39. package/dist/components/Skeleton/index.js +3 -3
  40. package/dist/components/Skeleton/index.js.map +1 -1
  41. package/dist/components/Stack/index.cjs +4 -3
  42. package/dist/components/Stack/index.cjs.map +1 -1
  43. package/dist/components/Stack/index.d.ts.map +1 -1
  44. package/dist/components/Stack/index.js +4 -3
  45. package/dist/components/Stack/index.js.map +1 -1
  46. package/dist/components/Theme/index.cjs +86 -1
  47. package/dist/components/Theme/index.cjs.map +1 -1
  48. package/dist/components/Theme/index.d.ts +44 -1
  49. package/dist/components/Theme/index.d.ts.map +1 -1
  50. package/dist/components/Theme/index.js +86 -1
  51. package/dist/components/Theme/index.js.map +1 -1
  52. package/dist/index.cjs +2 -0
  53. package/dist/index.cjs.map +1 -1
  54. package/dist/index.d.ts +1 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +3 -1
  57. package/dist/markdown.cjs +1 -1
  58. package/dist/markdown.cjs.map +1 -1
  59. package/dist/markdown.js +1 -1
  60. package/dist/markdown.js.map +1 -1
  61. package/fragments.json +1 -1
  62. package/package.json +6 -2
  63. package/src/components/Chip/index.tsx +3 -1
  64. package/src/components/CodeBlock/index.tsx +35 -41
  65. package/src/components/ColorPicker/ColorPicker.fragment.tsx +17 -15
  66. package/src/components/Command/index.tsx +1 -0
  67. package/src/components/DataTable/index.tsx +45 -45
  68. package/src/components/Listbox/index.tsx +1 -0
  69. package/src/components/Loading/index.tsx +6 -12
  70. package/src/components/Markdown/index.tsx +2 -2
  71. package/src/components/Menu/Menu.fragment.tsx +17 -15
  72. package/src/components/NavigationMenu/index.tsx +6 -1
  73. package/src/components/Skeleton/index.tsx +3 -3
  74. package/src/components/Slider/Slider.fragment.tsx +19 -17
  75. package/src/components/Stack/index.tsx +4 -3
  76. package/src/components/Theme/index.tsx +168 -1
  77. package/src/index.ts +6 -0
  78. package/src/tokens/_seeds.scss +20 -0
@@ -13,7 +13,9 @@ export interface ThemeProviderProps {
13
13
  children: React.ReactNode;
14
14
  /** Default theme mode for uncontrolled usage */
15
15
  defaultMode?: ThemeMode;
16
- /** Alias for defaultMode (more intuitive naming) */
16
+ /**
17
+ * @deprecated Use `defaultMode` instead. This alias will be removed in v1.0.
18
+ */
17
19
  defaultTheme?: ThemeMode;
18
20
  /** Controlled theme mode */
19
21
  mode?: ThemeMode;
@@ -183,6 +185,14 @@ function ThemeProvider({
183
185
  }: ThemeProviderProps) {
184
186
  const systemPreference = useSystemPreference();
185
187
 
188
+ // Warn on deprecated prop usage (dev only)
189
+ if (process.env.NODE_ENV !== 'production' && defaultTheme !== undefined) {
190
+ console.warn(
191
+ '[Fragments] ThemeProvider: `defaultTheme` is deprecated. Use `defaultMode` instead. ' +
192
+ '`defaultTheme` will be removed in v1.0.'
193
+ );
194
+ }
195
+
186
196
  // Resolve default: defaultMode takes precedence, then defaultTheme, then 'system'
187
197
  const resolvedDefault = defaultMode ?? defaultTheme ?? 'system';
188
198
 
@@ -353,3 +363,160 @@ export const Theme = Object.assign(ThemeProvider, {
353
363
  });
354
364
 
355
365
  export { ThemeProvider, ThemeToggle, useTheme };
366
+
367
+ // ============================================
368
+ // configureTheme — JS-only seed configuration
369
+ // ============================================
370
+
371
+ export type NeutralPalette = 'stone' | 'ice' | 'earth' | 'sand' | 'fire';
372
+ export type DensityPreset = 'compact' | 'default' | 'relaxed';
373
+ export type RadiusStyle = 'sharp' | 'subtle' | 'default' | 'rounded' | 'pill';
374
+
375
+ export interface ConfigureThemeOptions {
376
+ /** Brand/accent color as hex */
377
+ brand?: string;
378
+ /** Neutral palette name */
379
+ neutral?: NeutralPalette;
380
+ /** Spacing density preset */
381
+ density?: DensityPreset;
382
+ /** Border radius style */
383
+ radiusStyle?: RadiusStyle;
384
+ /** Danger/error color as hex */
385
+ danger?: string;
386
+ /** Success color as hex */
387
+ success?: string;
388
+ /** Warning color as hex */
389
+ warning?: string;
390
+ /** Info color as hex */
391
+ info?: string;
392
+ }
393
+
394
+ // -- Radius presets (match _radius.scss) --
395
+
396
+ const RADIUS_PRESETS: Record<RadiusStyle, Record<string, string>> = {
397
+ sharp: { sm: '0', md: '0', lg: '0', xl: '0' },
398
+ subtle: { sm: '0.125rem', md: '0.25rem', lg: '0.375rem', xl: '0.5rem' },
399
+ default: { sm: '0.25rem', md: '0.429rem', lg: '0.571rem', xl: '0.857rem' },
400
+ rounded: { sm: '0.375rem', md: '0.5rem', lg: '0.75rem', xl: '1rem' },
401
+ pill: { sm: '0.5rem', md: '0.75rem', lg: '1rem', xl: '1.5rem' },
402
+ };
403
+
404
+ // -- Density presets (match _density.scss) --
405
+
406
+ interface DensityConfig {
407
+ baseUnit: number;
408
+ baseFontSize: number;
409
+ buttonHeights: [number, number, number]; // sm, md, lg
410
+ inputHeights: [number, number, number]; // sm, default, lg
411
+ }
412
+
413
+ const DENSITY_CONFIGS: Record<DensityPreset, DensityConfig> = {
414
+ compact: { baseUnit: 6, baseFontSize: 14, buttonHeights: [24, 30, 36], inputHeights: [24, 32, 36] },
415
+ default: { baseUnit: 7, baseFontSize: 14, buttonHeights: [28, 36, 44], inputHeights: [28, 40, 44] },
416
+ relaxed: { baseUnit: 8, baseFontSize: 14, buttonHeights: [32, 40, 48], inputHeights: [32, 44, 48] },
417
+ };
418
+
419
+ function pxToRem(px: number, baseFontSize: number): string {
420
+ return `${px / baseFontSize}rem`;
421
+ }
422
+
423
+ function hexToRgb(hex: string): [number, number, number] | null {
424
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
425
+ if (!result) return null;
426
+ return [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)];
427
+ }
428
+
429
+ function adjustLightness(hex: string, amount: number): string {
430
+ const rgb = hexToRgb(hex);
431
+ if (!rgb) return hex;
432
+ const [r, g, b] = rgb;
433
+ const adjust = (v: number) => Math.max(0, Math.min(255, Math.round(v + amount)));
434
+ return `#${adjust(r).toString(16).padStart(2, '0')}${adjust(g).toString(16).padStart(2, '0')}${adjust(b).toString(16).padStart(2, '0')}`;
435
+ }
436
+
437
+ function setVar(el: HTMLElement, name: string, value: string) {
438
+ el.style.setProperty(name, value);
439
+ }
440
+
441
+ /**
442
+ * Configure theme seeds at runtime via JS. Sets CSS custom properties on
443
+ * `:root` without requiring SCSS. Call this once at app startup.
444
+ *
445
+ * Note: For full control over all 120+ tokens, use the SCSS `@use...with()`
446
+ * approach. `configureTheme` covers the most commonly customized tokens.
447
+ *
448
+ * @example
449
+ * ```ts
450
+ * import { configureTheme } from '@fragments-sdk/ui';
451
+ *
452
+ * configureTheme({
453
+ * brand: '#6366f1',
454
+ * neutral: 'ice',
455
+ * density: 'compact',
456
+ * radiusStyle: 'rounded',
457
+ * });
458
+ * ```
459
+ */
460
+ export function configureTheme(options: ConfigureThemeOptions): void {
461
+ if (typeof document === 'undefined') return;
462
+
463
+ const root = document.documentElement;
464
+
465
+ // -- Brand / Accent --
466
+ if (options.brand) {
467
+ setVar(root, '--fui-color-accent', options.brand);
468
+ setVar(root, '--fui-color-accent-hover', adjustLightness(options.brand, -20));
469
+ setVar(root, '--fui-color-accent-active', adjustLightness(options.brand, -40));
470
+ setVar(root, '--fui-focus-ring-color', `${options.brand}66`); // 40% alpha
471
+ }
472
+
473
+ // -- Semantic colors --
474
+ if (options.danger) {
475
+ setVar(root, '--fui-color-danger', options.danger);
476
+ setVar(root, '--fui-color-danger-hover', adjustLightness(options.danger, -20));
477
+ }
478
+ if (options.success) setVar(root, '--fui-color-success', options.success);
479
+ if (options.warning) setVar(root, '--fui-color-warning', options.warning);
480
+ if (options.info) setVar(root, '--fui-color-info', options.info);
481
+
482
+ // -- Radius --
483
+ if (options.radiusStyle) {
484
+ const r = RADIUS_PRESETS[options.radiusStyle];
485
+ if (r) {
486
+ setVar(root, '--fui-radius-sm', r.sm);
487
+ setVar(root, '--fui-radius-md', r.md);
488
+ setVar(root, '--fui-radius-lg', r.lg);
489
+ setVar(root, '--fui-radius-xl', r.xl);
490
+ }
491
+ }
492
+
493
+ // -- Density --
494
+ if (options.density) {
495
+ const d = DENSITY_CONFIGS[options.density];
496
+ if (d) {
497
+ const unitRem = d.baseUnit / d.baseFontSize;
498
+
499
+ // Spacing scale
500
+ setVar(root, '--fui-space-1', `${unitRem}rem`);
501
+ setVar(root, '--fui-space-2', `${unitRem * 2}rem`);
502
+ setVar(root, '--fui-space-3', `${unitRem * 3}rem`);
503
+ setVar(root, '--fui-space-4', `${unitRem * 4}rem`);
504
+ setVar(root, '--fui-space-5', `${unitRem * 5}rem`);
505
+ setVar(root, '--fui-space-6', `${unitRem * 6}rem`);
506
+ setVar(root, '--fui-space-8', `${unitRem * 8}rem`);
507
+ setVar(root, '--fui-space-10', `${unitRem * 10}rem`);
508
+ setVar(root, '--fui-space-12', `${unitRem * 12}rem`);
509
+
510
+ // Component heights
511
+ setVar(root, '--fui-button-height-sm', pxToRem(d.buttonHeights[0], d.baseFontSize));
512
+ setVar(root, '--fui-button-height-md', pxToRem(d.buttonHeights[1], d.baseFontSize));
513
+ setVar(root, '--fui-button-height-lg', pxToRem(d.buttonHeights[2], d.baseFontSize));
514
+ setVar(root, '--fui-input-height-sm', pxToRem(d.inputHeights[0], d.baseFontSize));
515
+ setVar(root, '--fui-input-height', pxToRem(d.inputHeights[1], d.baseFontSize));
516
+ setVar(root, '--fui-input-height-lg', pxToRem(d.inputHeights[2], d.baseFontSize));
517
+
518
+ // Base unit
519
+ setVar(root, '--fui-base-unit', `${d.baseUnit}px`);
520
+ }
521
+ }
522
+ }
package/src/index.ts CHANGED
@@ -305,13 +305,19 @@ export {
305
305
 
306
306
  // Theme
307
307
  export {
308
+ Theme,
308
309
  ThemeProvider,
309
310
  ThemeToggle,
310
311
  useTheme,
312
+ configureTheme,
311
313
  type ThemeProviderProps,
312
314
  type ThemeToggleProps,
313
315
  type ThemeMode,
314
316
  type UseThemeReturn,
317
+ type ConfigureThemeOptions,
318
+ type NeutralPalette,
319
+ type DensityPreset,
320
+ type RadiusStyle,
315
321
  } from './components/Theme';
316
322
 
317
323
  // Header
@@ -38,6 +38,26 @@ $fui-density: "default" !default;
38
38
  /// @type String
39
39
  $fui-radius-style: "default" !default;
40
40
 
41
+ // --------------------------------------------
42
+ // Seed Validation
43
+ // --------------------------------------------
44
+ // Fail fast at compile time if invalid seed values are provided.
45
+
46
+ $_valid-neutrals: "stone", "ice", "earth", "sand", "fire";
47
+ @if not index($_valid-neutrals, $fui-neutral) {
48
+ @error "Invalid $fui-neutral: '#{$fui-neutral}'. Must be one of: #{$_valid-neutrals}";
49
+ }
50
+
51
+ $_valid-densities: "compact", "default", "relaxed";
52
+ @if not index($_valid-densities, $fui-density) {
53
+ @error "Invalid $fui-density: '#{$fui-density}'. Must be one of: #{$_valid-densities}";
54
+ }
55
+
56
+ $_valid-radii: "sharp", "subtle", "default", "rounded", "pill";
57
+ @if not index($_valid-radii, $fui-radius-style) {
58
+ @error "Invalid $fui-radius-style: '#{$fui-radius-style}'. Must be one of: #{$_valid-radii}";
59
+ }
60
+
41
61
  // --------------------------------------------
42
62
  // Semantic Color Overrides (Optional)
43
63
  // --------------------------------------------