@luxfi/ui 7.4.10 → 7.4.11

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/dist/accordion.cjs +107 -166
  2. package/dist/accordion.d.cts +18 -51
  3. package/dist/accordion.d.ts +18 -51
  4. package/dist/accordion.js +109 -166
  5. package/dist/chrome.cjs +193 -379
  6. package/dist/chrome.js +195 -379
  7. package/dist/collapsible.cjs +35 -26
  8. package/dist/collapsible.d.cts +2 -1
  9. package/dist/collapsible.d.ts +2 -1
  10. package/dist/collapsible.js +34 -24
  11. package/dist/dialog.d.cts +1 -1
  12. package/dist/dialog.d.ts +1 -1
  13. package/dist/drawer.cjs +92 -115
  14. package/dist/drawer.d.cts +43 -10
  15. package/dist/drawer.d.ts +43 -10
  16. package/dist/drawer.js +94 -115
  17. package/dist/field.cjs +60 -109
  18. package/dist/field.d.cts +0 -1
  19. package/dist/field.d.ts +0 -1
  20. package/dist/field.js +61 -109
  21. package/dist/heading.cjs +10 -17
  22. package/dist/heading.d.cts +0 -2
  23. package/dist/heading.d.ts +0 -2
  24. package/dist/heading.js +10 -17
  25. package/dist/icon-button.cjs +82 -82
  26. package/dist/icon-button.d.cts +16 -12
  27. package/dist/icon-button.d.ts +16 -12
  28. package/dist/icon-button.js +82 -82
  29. package/dist/index.cjs +1073 -1419
  30. package/dist/index.d.cts +1 -5
  31. package/dist/index.d.ts +1 -5
  32. package/dist/index.js +1075 -1414
  33. package/dist/input-group.cjs +11 -12
  34. package/dist/input-group.js +11 -12
  35. package/dist/input.cjs +27 -27
  36. package/dist/input.d.cts +21 -1
  37. package/dist/input.d.ts +21 -1
  38. package/dist/input.js +25 -28
  39. package/dist/menu.cjs +160 -271
  40. package/dist/menu.d.cts +41 -57
  41. package/dist/menu.d.ts +41 -57
  42. package/dist/menu.js +161 -251
  43. package/dist/popover.cjs +1 -1
  44. package/dist/popover.js +1 -1
  45. package/dist/select.cjs +40 -39
  46. package/dist/select.d.cts +35 -4
  47. package/dist/select.d.ts +35 -4
  48. package/dist/select.js +40 -38
  49. package/dist/separator.cjs +8 -33
  50. package/dist/separator.js +8 -33
  51. package/dist/tabs.cjs +108 -197
  52. package/dist/tabs.d.cts +16 -60
  53. package/dist/tabs.d.ts +16 -60
  54. package/dist/tabs.js +109 -196
  55. package/dist/textarea.cjs +56 -27
  56. package/dist/textarea.js +57 -28
  57. package/dist/toaster.cjs +97 -79
  58. package/dist/toaster.d.cts +7 -8
  59. package/dist/toaster.d.ts +7 -8
  60. package/dist/toaster.js +78 -80
  61. package/package.json +2 -16
  62. package/src/accordion.tsx +173 -227
  63. package/src/collapsible.tsx +55 -52
  64. package/src/drawer.tsx +123 -115
  65. package/src/field.tsx +68 -124
  66. package/src/heading.tsx +22 -26
  67. package/src/icon-button.tsx +134 -141
  68. package/src/ink.tsx +37 -0
  69. package/src/input-group.tsx +21 -12
  70. package/src/input.tsx +41 -32
  71. package/src/menu.tsx +224 -397
  72. package/src/popover.tsx +1 -1
  73. package/src/select.tsx +80 -59
  74. package/src/separator.tsx +13 -34
  75. package/src/tabs.tsx +166 -296
  76. package/src/textarea.tsx +19 -29
  77. package/src/toaster.tsx +118 -86
  78. package/tokens.css +61 -0
package/src/popover.tsx CHANGED
@@ -195,7 +195,7 @@ export const PopoverContent = React.forwardRef<
195
195
  className={ cn(
196
196
  'z-50 rounded-lg border border-[var(--color-popover-border,var(--color-border-divider))]',
197
197
  'bg-[var(--color-popover-bg,var(--color-dialog-bg))]',
198
- 'shadow-[var(--shadow-popover,var(--shadow-lg))]',
198
+ 'shadow-[var(--shadow-popover)]',
199
199
  'outline-none',
200
200
  'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
201
201
  'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
package/src/select.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import * as RadixSelect from '@radix-ui/react-select';
3
+ import { Select as GuiSelect } from '@hanzo/gui';
4
4
  import { useDebounce } from '@uidotdev/usehooks';
5
5
  import * as React from 'react';
6
6
 
@@ -229,15 +229,14 @@ export const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(
229
229
 
230
230
  return (
231
231
  <SelectInternalContext.Provider value={ ctxValue }>
232
- <RadixSelect.Root
232
+ <GuiSelect
233
233
  value={ currentValue[0] ?? '' }
234
234
  defaultValue={ defaultValueArr?.[0] }
235
235
  onValueChange={ handleRadixValueChange }
236
236
  open={ isOpen }
237
237
  onOpenChange={ handleOpenChange }
238
- disabled={ disabled || readOnly }
238
+ disablePreventBodyScroll
239
239
  name={ name }
240
- required={ required }
241
240
  >
242
241
  <div
243
242
  ref={ ref }
@@ -250,7 +249,7 @@ export const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(
250
249
  >
251
250
  { children }
252
251
  </div>
253
- </RadixSelect.Root>
252
+ </GuiSelect>
254
253
  </SelectInternalContext.Provider>
255
254
  );
256
255
  },
@@ -263,7 +262,7 @@ export const SelectRoot = React.forwardRef<HTMLDivElement, SelectRootProps>(
263
262
  export interface SelectControlProps {
264
263
  children?: React.ReactNode;
265
264
  noIndicator?: boolean;
266
- triggerProps?: React.ComponentPropsWithoutRef<typeof RadixSelect.Trigger> & {
265
+ triggerProps?: React.ComponentProps<typeof GuiSelect.Trigger> & {
267
266
  asChild?: boolean;
268
267
  px?: string | number;
269
268
  className?: string;
@@ -286,9 +285,10 @@ export const SelectControl = React.forwardRef<HTMLButtonElement, SelectControlPr
286
285
  const { asChild, px: _px, className: triggerClassName, ...radixTriggerProps } = triggerProps ?? {};
287
286
 
288
287
  const trigger = (
289
- <RadixSelect.Trigger
290
- ref={ ref }
288
+ <GuiSelect.Trigger
289
+ ref={ ref as never }
291
290
  asChild={ asChild }
291
+ unstyled
292
292
  className={ cn(
293
293
  'group peer inline-flex items-center gap-2 cursor-pointer',
294
294
  'rounded-lg text-sm transition-colors outline-none',
@@ -314,7 +314,7 @@ export const SelectControl = React.forwardRef<HTMLButtonElement, SelectControlPr
314
314
  ) }
315
315
  </>
316
316
  ) }
317
- </RadixSelect.Trigger>
317
+ </GuiSelect.Trigger>
318
318
  );
319
319
 
320
320
  if (loading) {
@@ -376,39 +376,33 @@ export interface SelectContentProps {
376
376
  }
377
377
 
378
378
  export const SelectContent = React.forwardRef<HTMLDivElement, SelectContentProps>(
379
- function SelectContent(props, ref) {
380
- const { portalled = true, portalRef, children, className, ...rest } = props;
379
+ function SelectContent(props, _ref) {
380
+ // `portalled` and `portalRef` stay in the type because callers pass them,
381
+ // but gui's Select.Content owns its own layering — it mounts through the
382
+ // engine's portal and z-index stack whatever we say. There is no second
383
+ // way to place it, so there is nothing here to switch on.
384
+ const { portalled: _portalled, portalRef: _portalRef, children, className, ...rest } = props;
381
385
 
382
- const content = (
383
- <RadixSelect.Content
384
- ref={ ref }
385
- position="popper"
386
- sideOffset={ 4 }
387
- className={ cn(
388
- 'z-50 min-w-[8rem] overflow-hidden rounded-lg',
389
- 'bg-[var(--color-popover-bg)] shadow-[var(--shadow-popover)]',
390
- 'border border-[var(--color-border-divider)]',
391
- 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
392
- 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
393
- className,
394
- ) }
395
- { ...rest }
396
- >
397
- <RadixSelect.Viewport className="p-1">
398
- { children }
399
- </RadixSelect.Viewport>
400
- </RadixSelect.Content>
401
- );
386
+ // One counter per render, walked over the rows below.
387
+ let n = 0;
388
+ const counter = () => n++;
402
389
 
403
- if (portalled) {
404
- return (
405
- <RadixSelect.Portal container={ portalRef?.current ?? undefined }>
406
- { content }
407
- </RadixSelect.Portal>
408
- );
409
- }
410
-
411
- return content;
390
+ return (
391
+ // Content is the focus scope — it takes no style props at all. The
392
+ // Viewport is the box you can see, so the surface is declared there.
393
+ <GuiSelect.Content { ...rest }>
394
+ <GuiSelect.Viewport
395
+ className={ cn(
396
+ 'min-w-[8rem] overflow-hidden rounded-lg p-1',
397
+ 'bg-[var(--color-popover-bg)] shadow-[var(--shadow-popover)]',
398
+ 'border border-[var(--color-border-divider)]',
399
+ className,
400
+ ) }
401
+ >
402
+ { stampIndices(children, counter) }
403
+ </GuiSelect.Viewport>
404
+ </GuiSelect.Content>
405
+ );
412
406
  },
413
407
  );
414
408
 
@@ -420,17 +414,44 @@ export interface SelectItemProps {
420
414
  item: SelectOption;
421
415
  children?: React.ReactNode;
422
416
  className?: string;
417
+
418
+ /**
419
+ * The row's position in the list. gui's Select is positional — it keeps its
420
+ * own map of index to value to drive type-ahead, the active descendant and
421
+ * scrolling, so a row without one is a row the keyboard cannot reach. It is
422
+ * stamped by `SelectContent` rather than asked of the caller, because the
423
+ * caller writing `<SelectItem item={x}/>` has no idea what its own ordinal is
424
+ * and would have to be told twice.
425
+ */
426
+ index?: number;
423
427
  }
424
428
 
429
+ /**
430
+ * Number every `SelectItem` beneath a node, depth first, in render order.
431
+ *
432
+ * Depth first because rows are routinely nested — inside a `SelectItemGroup`,
433
+ * inside the `React.Fragment` that pairs a row with its `afterElement`. A
434
+ * shallow pass over the content's direct children would number the groups and
435
+ * leave every actual row at index 0, which reads as a list whose keyboard
436
+ * silently only ever lands on the first entry.
437
+ */
438
+ const stampIndices = (children: React.ReactNode, next: () => number): React.ReactNode =>
439
+ React.Children.map(children, (child) => {
440
+ if (!React.isValidElement(child)) return child;
441
+ if (child.type === SelectItem) return React.cloneElement(child, { index: next() } as never);
442
+ const kids = (child.props as { children?: React.ReactNode }).children;
443
+ return kids === undefined ? child : React.cloneElement(child, undefined, stampIndices(kids, next));
444
+ });
445
+
425
446
  export const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
426
447
  function SelectItem(props, ref) {
427
- const { item, children, className, ...rest } = props;
448
+ const { item, children, className, index, ...rest } = props;
428
449
 
429
450
  return (
430
- <RadixSelect.Item
431
- ref={ ref }
451
+ <GuiSelect.Item
452
+ ref={ ref as never }
453
+ index={ index ?? 0 }
432
454
  value={ item.value }
433
- textValue={ item.label }
434
455
  className={ cn(
435
456
  'relative flex cursor-pointer select-none items-center gap-2',
436
457
  'rounded-md px-3 py-2 text-sm outline-none',
@@ -443,13 +464,13 @@ export const SelectItem = React.forwardRef<HTMLDivElement, SelectItemProps>(
443
464
  { ...rest }
444
465
  >
445
466
  { item.icon }
446
- <RadixSelect.ItemText>
447
- { children }
448
- </RadixSelect.ItemText>
449
- <RadixSelect.ItemIndicator className="ml-auto shrink-0">
467
+ <GuiSelect.ItemText>
468
+ { children ?? item.label }
469
+ </GuiSelect.ItemText>
470
+ <GuiSelect.ItemIndicator className="ml-auto shrink-0">
450
471
  <CheckIcon className="h-5 w-5"/>
451
- </RadixSelect.ItemIndicator>
452
- </RadixSelect.Item>
472
+ </GuiSelect.ItemIndicator>
473
+ </GuiSelect.Item>
453
474
  );
454
475
  },
455
476
  );
@@ -529,14 +550,14 @@ export const SelectValueText = React.forwardRef<HTMLSpanElement, SelectValueText
529
550
  // Radix SelectValue renders the selected value or placeholder.
530
551
  // We override with our custom content.
531
552
  return (
532
- <RadixSelect.Value
533
- ref={ ref }
553
+ <GuiSelect.Value
554
+ ref={ ref as never }
534
555
  placeholder={ placeholderText }
535
556
  className={ cn('flex flex-col justify-center min-w-0 truncate', className) }
536
557
  { ...rest }
537
558
  >
538
559
  { content }
539
- </RadixSelect.Value>
560
+ </GuiSelect.Value>
540
561
  );
541
562
  },
542
563
  );
@@ -555,12 +576,12 @@ export const SelectItemGroup = React.forwardRef<HTMLDivElement, SelectItemGroupP
555
576
  function SelectItemGroup(props, ref) {
556
577
  const { children, label, className, ...rest } = props;
557
578
  return (
558
- <RadixSelect.Group ref={ ref } className={ cn('py-1', className) } { ...rest }>
559
- <RadixSelect.Label className="px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)]">
579
+ <GuiSelect.Group ref={ ref as never } className={ cn('py-1', className) } { ...rest }>
580
+ <GuiSelect.Label className="px-3 py-1.5 text-xs font-medium text-[var(--color-text-secondary)]">
560
581
  { label }
561
- </RadixSelect.Label>
582
+ </GuiSelect.Label>
562
583
  { children }
563
- </RadixSelect.Group>
584
+ </GuiSelect.Group>
564
585
  );
565
586
  },
566
587
  );
@@ -569,8 +590,8 @@ export const SelectItemGroup = React.forwardRef<HTMLDivElement, SelectItemGroupP
569
590
  // SelectLabel / SelectItemText
570
591
  // ---------------------------------------------------------------------------
571
592
 
572
- export const SelectLabel = RadixSelect.Label;
573
- export const SelectItemText = RadixSelect.ItemText;
593
+ export const SelectLabel = GuiSelect.Label;
594
+ export const SelectItemText = GuiSelect.ItemText;
574
595
 
575
596
  // ---------------------------------------------------------------------------
576
597
  // Select (composite)
package/src/separator.tsx CHANGED
@@ -1,8 +1,6 @@
1
1
  import { Separator as GuiSeparator } from '@hanzo/gui';
2
2
  import React from 'react';
3
3
 
4
- import { cn } from './utils';
5
-
6
4
  type Orientation = 'horizontal' | 'vertical';
7
5
  type Variant = 'solid' | 'dashed' | 'dotted';
8
6
  type Size = 'xs' | 'sm' | 'md' | 'lg';
@@ -13,50 +11,31 @@ export interface SeparatorProps extends React.HTMLAttributes<HTMLHRElement> {
13
11
  readonly size?: Size;
14
12
  }
15
13
 
16
- const VARIANT_CLASSES: Record<Variant, string> = {
17
- solid: 'border-solid',
18
- dashed: 'border-dashed',
19
- dotted: 'border-dotted',
20
- };
21
-
22
- const HORIZONTAL_SIZE_CLASSES: Record<Size, string> = {
23
- xs: 'border-t-[0.5px]',
24
- sm: 'border-t',
25
- md: 'border-t-2',
26
- lg: 'border-t-[3px]',
27
- };
28
-
29
- const VERTICAL_SIZE_CLASSES: Record<Size, string> = {
30
- xs: 'border-l-[0.5px]',
31
- sm: 'border-l',
32
- md: 'border-l-2',
33
- lg: 'border-l-[3px]',
34
- };
14
+ /** Border weight by size — the rule is one edge, so the weight is one number. */
15
+ const WEIGHT: Record<Size, number> = { xs: 0.5, sm: 1, md: 2, lg: 3 };
35
16
 
36
17
  // gui's Separator wearing the Lux border scale. `vertical` is the primitive's
37
- // own orientation variant, so it lays out correctly on native too; the Tailwind
38
- // classes only carry border weight and dash pattern.
18
+ // own orientation variant, so it lays out correctly on native too; weight and
19
+ // dash pattern are props rather than classes for the same reason.
39
20
  export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
40
21
  function Separator(
41
- { orientation = 'horizontal', variant = 'solid', size = 'sm', className, ...rest },
22
+ { orientation = 'horizontal', variant = 'solid', size = 'sm', ...rest },
42
23
  ref,
43
24
  ) {
44
- const isVertical = orientation === 'vertical';
25
+ const vertical = orientation === 'vertical';
45
26
 
46
27
  return (
47
28
  <GuiSeparator
48
29
  ref={ ref as never }
49
- vertical={ isVertical }
30
+ vertical={ vertical }
50
31
  role="separator"
51
32
  aria-orientation={ orientation }
52
- className={ cn(
53
- 'border-[var(--color-border-divider)]',
54
- 'border-0',
55
- VARIANT_CLASSES[variant],
56
- isVertical ? VERTICAL_SIZE_CLASSES[size] : HORIZONTAL_SIZE_CLASSES[size],
57
- isVertical ? 'self-stretch h-auto w-0' : 'w-full',
58
- className,
59
- ) }
33
+ borderWidth={ 0 }
34
+ borderStyle={ variant }
35
+ borderColor={ 'var(--color-border-divider)' as never }
36
+ { ...(vertical ?
37
+ { borderLeftWidth: WEIGHT[size], alignSelf: 'stretch', height: 'auto', width: 0 } :
38
+ { borderTopWidth: WEIGHT[size], width: '100%' }) }
60
39
  { ...(rest as object) }
61
40
  />
62
41
  );