@luxfi/ui 7.4.11 → 7.4.12

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 (70) hide show
  1. package/dist/alert.cjs +68 -46
  2. package/dist/alert.js +69 -46
  3. package/dist/avatar.cjs +49 -46
  4. package/dist/avatar.js +52 -48
  5. package/dist/badge.cjs +82 -49
  6. package/dist/badge.js +83 -50
  7. package/dist/checkbox.cjs +42 -75
  8. package/dist/checkbox.js +43 -76
  9. package/dist/chrome.cjs +3 -7
  10. package/dist/chrome.js +3 -7
  11. package/dist/close-button.cjs +3 -7
  12. package/dist/close-button.js +3 -7
  13. package/dist/dialog.cjs +3 -7
  14. package/dist/dialog.js +3 -7
  15. package/dist/drawer.cjs +3 -7
  16. package/dist/drawer.js +3 -7
  17. package/dist/empty-state.cjs +13 -13
  18. package/dist/empty-state.js +13 -13
  19. package/dist/expiration-selector.cjs +58 -63
  20. package/dist/expiration-selector.js +58 -63
  21. package/dist/greeks-display.cjs +48 -39
  22. package/dist/greeks-display.js +48 -39
  23. package/dist/index.cjs +756 -703
  24. package/dist/index.js +757 -704
  25. package/dist/option-chain.cjs +89 -67
  26. package/dist/option-chain.js +89 -67
  27. package/dist/option-position.cjs +88 -85
  28. package/dist/option-position.js +88 -85
  29. package/dist/pnl-diagram.cjs +20 -20
  30. package/dist/pnl-diagram.js +20 -20
  31. package/dist/popover.cjs +3 -7
  32. package/dist/popover.js +3 -7
  33. package/dist/progress.cjs +15 -22
  34. package/dist/progress.d.cts +6 -6
  35. package/dist/progress.d.ts +6 -6
  36. package/dist/progress.js +15 -22
  37. package/dist/radio.cjs +35 -67
  38. package/dist/radio.d.cts +18 -18
  39. package/dist/radio.d.ts +18 -18
  40. package/dist/radio.js +36 -68
  41. package/dist/slider.cjs +33 -49
  42. package/dist/slider.js +34 -50
  43. package/dist/switch.cjs +48 -55
  44. package/dist/switch.js +49 -56
  45. package/dist/tag.cjs +112 -69
  46. package/dist/tag.js +113 -69
  47. package/dist/tooltip.cjs +28 -17
  48. package/dist/tooltip.js +30 -18
  49. package/package.json +1 -1
  50. package/src/alert.tsx +78 -45
  51. package/src/avatar.tsx +54 -37
  52. package/src/badge.tsx +65 -44
  53. package/src/checkbox.tsx +70 -89
  54. package/src/close-button.tsx +3 -8
  55. package/src/empty-state.tsx +21 -17
  56. package/src/expiration-selector.tsx +84 -66
  57. package/src/greeks-display.tsx +59 -51
  58. package/src/input-group.tsx +38 -24
  59. package/src/option-chain.tsx +154 -104
  60. package/src/option-position.tsx +136 -114
  61. package/src/pin-input.tsx +37 -29
  62. package/src/pnl-diagram.tsx +36 -28
  63. package/src/progress.tsx +17 -21
  64. package/src/radio.tsx +56 -75
  65. package/src/slider.tsx +43 -44
  66. package/src/strategy-builder.tsx +245 -156
  67. package/src/switch.tsx +63 -64
  68. package/src/tag.tsx +89 -51
  69. package/src/tooltip.tsx +21 -13
  70. package/tokens.css +18 -0
package/src/tag.tsx CHANGED
@@ -1,7 +1,6 @@
1
+ import { Text, XStack } from '@hanzo/gui';
1
2
  import * as React from 'react';
2
3
 
3
- import { cn } from './utils';
4
-
5
4
  import { CloseButton } from './close-button';
6
5
  import { Skeleton } from './skeleton';
7
6
  import { Tooltip } from './tooltip';
@@ -21,29 +20,29 @@ function TruncatedTextTooltip({ label, children }: { readonly label: React.React
21
20
  type TagVariant = 'subtle' | 'clickable' | 'filter' | 'select';
22
21
  type TagSize = 'sm' | 'md' | 'lg';
23
22
 
24
- const TAG_BASE = 'inline-flex items-center align-top max-w-full select-text rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500';
25
-
26
- const TAG_VARIANT_CLASSES: Record<TagVariant, string> = {
27
- subtle: 'bg-[var(--color-tag-subtle-bg)] text-[var(--color-tag-subtle-fg)]',
28
- clickable: 'cursor-pointer bg-[var(--color-tag-clickable-bg)] text-[var(--color-tag-clickable-fg)] hover:opacity-76',
29
- filter: 'bg-[var(--color-tag-filter-bg)]',
30
- select: 'cursor-pointer bg-[var(--color-tag-select-bg)] text-[var(--color-tag-select-fg)] hover:text-[var(--color-hover)] hover:opacity-76',
23
+ /** The chip background, cursor, and the hover opacity `clickable`/`select` get. */
24
+ const TAG_VARIANT: Record<TagVariant, { backgroundColor: string; cursor?: string; hoverStyle?: Record<string, unknown> }> = {
25
+ subtle: { backgroundColor: 'var(--color-tag-subtle-bg)' },
26
+ clickable: { backgroundColor: 'var(--color-tag-clickable-bg)', cursor: 'pointer', hoverStyle: { opacity: 0.76 } },
27
+ filter: { backgroundColor: 'var(--color-tag-filter-bg)' },
28
+ select: { backgroundColor: 'var(--color-tag-select-bg)', cursor: 'pointer', hoverStyle: { opacity: 0.76 } },
31
29
  };
32
30
 
33
- const TAG_SIZE_CLASSES: Record<TagSize, string> = {
34
- sm: 'px-1 py-0.5 min-h-5 gap-1 text-xs',
35
- md: 'px-1 py-0.5 min-h-6 gap-1 text-sm',
36
- lg: 'px-1.5 py-1.5 min-h-8 min-w-8 gap-1 text-sm',
31
+ /** The label inside it — same four variants, the colour half (`filter` has none). */
32
+ const TAG_VARIANT_FG: Record<TagVariant, string | undefined> = {
33
+ subtle: 'var(--color-tag-subtle-fg)',
34
+ clickable: 'var(--color-tag-clickable-fg)',
35
+ filter: undefined,
36
+ select: 'var(--color-tag-select-fg)',
37
37
  };
38
38
 
39
- const TAG_SELECTED_CLASSES = [
40
- 'bg-[var(--color-tag-select-selected-bg)]',
41
- 'text-[var(--color-tag-select-selected-fg)]',
42
- 'hover:text-[var(--color-tag-select-selected-fg)]',
43
- 'hover:opacity-76',
44
- ].join(' ');
39
+ const TAG_SIZE: Record<TagSize, Record<string, number>> = {
40
+ sm: { paddingHorizontal: 4, paddingVertical: 2, minHeight: 20, gap: 4 },
41
+ md: { paddingHorizontal: 4, paddingVertical: 2, minHeight: 24, gap: 4 },
42
+ lg: { paddingHorizontal: 6, paddingVertical: 6, minHeight: 32, minWidth: 32, gap: 4 },
43
+ };
45
44
 
46
- const TAG_DISABLED_CLASSES = 'opacity-40 pointer-events-none cursor-not-allowed';
45
+ const TAG_SIZE_FONT: Record<TagSize, number> = { sm: 12, md: 14, lg: 14 };
47
46
 
48
47
  export interface TagProps extends Omit<React.ComponentPropsWithRef<'span'>, 'size'> {
49
48
  readonly variant?: TagVariant;
@@ -80,14 +79,39 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
80
79
  ...rest
81
80
  } = props;
82
81
 
82
+ const resolvedVariant = variant ?? 'subtle';
83
+ const resolvedSize = size ?? 'md';
84
+ const isSelected = Boolean(selected) && !loading;
85
+
86
+ // `selected` overrides the variant's own colours; `disabled` only dims —
87
+ // neither is known until render, so resolve the final tokens here rather
88
+ // than lean on a class-merge order that no longer exists.
89
+ const backgroundColor = isSelected ? 'var(--color-tag-select-selected-bg)' : TAG_VARIANT[resolvedVariant].backgroundColor;
90
+ const color = isSelected ? 'var(--color-tag-select-selected-fg)' : TAG_VARIANT_FG[resolvedVariant];
91
+ const hoverStyle = isSelected ? { opacity: 0.76 } : TAG_VARIANT[resolvedVariant].hoverStyle;
92
+ // Unselected `select` rows preview the selected look on hover; once
93
+ // selected the hover colour equals the resting colour, i.e. no change.
94
+ const textHoverColor = !isSelected && resolvedVariant === 'select' ? 'var(--color-hover)' : undefined;
95
+
83
96
  const labelElement = label ? (
84
- <span className="text-[var(--color-text-secondary)]">{ label }:{ nbsp }</span>
97
+ <Text fontSize={ TAG_SIZE_FONT[resolvedSize] } fontWeight="500" color={ 'var(--color-text-secondary)' as never }>
98
+ { label }:{ nbsp }
99
+ </Text>
85
100
  ) : null;
86
101
 
87
102
  const labelSpan = (
88
- <span className="line-clamp-1 whitespace-nowrap text-ellipsis font-medium inline">
103
+ // `ellipsis` is Text's own built-in single-line-truncation variant —
104
+ // the same rule Tailwind's `line-clamp-1`/`text-ellipsis` drew, through
105
+ // the component's variants rather than three separate style props.
106
+ <Text
107
+ ellipsis
108
+ fontWeight="500"
109
+ fontSize={ TAG_SIZE_FONT[resolvedSize] }
110
+ color={ color as never }
111
+ hoverStyle={ textHoverColor ? { color: textHoverColor as never } : undefined }
112
+ >
89
113
  { labelElement }{ children }
90
- </span>
114
+ </Text>
91
115
  );
92
116
 
93
117
  const contentElement = truncated ? (
@@ -98,43 +122,57 @@ export const Tag = React.forwardRef<HTMLSpanElement, TagProps>(
98
122
 
99
123
  return (
100
124
  <Skeleton loading={ loading } asChild>
101
- <span
102
- ref={ ref }
103
- className={ cn(
104
- TAG_BASE,
105
- TAG_VARIANT_CLASSES[variant ?? 'subtle'],
106
- TAG_SIZE_CLASSES[size ?? 'md'],
107
- selected && !loading && TAG_SELECTED_CLASSES,
108
- disabled && TAG_DISABLED_CLASSES,
109
- className,
110
- ) }
111
- { ...(selected && !loading && { 'data-selected': true }) }
125
+ <XStack
126
+ ref={ ref as never }
127
+ render={ <span/> }
128
+ display="inline-flex"
129
+ alignItems="center"
130
+ verticalAlign="top"
131
+ maxWidth="100%"
132
+ userSelect="text"
133
+ { ...TAG_SIZE[resolvedSize] }
134
+ backgroundColor={ backgroundColor as never }
135
+ hoverStyle={ hoverStyle }
136
+ focusVisibleStyle={{
137
+ outlineWidth: 2,
138
+ outlineOffset: 2,
139
+ outlineColor: 'var(--color-gray-500)' as never,
140
+ outlineStyle: 'solid',
141
+ }}
142
+ // One cursor, decided once: disabled outranks the variant's.
143
+ cursor={ disabled ? 'not-allowed' : TAG_VARIANT[resolvedVariant].cursor }
144
+ { ...(disabled ? { opacity: 0.4, pointerEvents: 'none' } : {}) }
145
+ { ...(isSelected && { 'data-selected': true }) }
112
146
  { ...(disabled && { 'data-disabled': true }) }
113
- { ...rest }
147
+ className={ className }
148
+ { ...(rest as object) }
114
149
  >
115
150
  { startElement && (
116
- <span className="shrink-0 inline-flex items-center justify-center empty:hidden">
151
+ <XStack flexShrink={ 0 } alignItems="center" justifyContent="center">
117
152
  { startElement }
118
- </span>
153
+ </XStack>
119
154
  ) }
120
155
  { contentElement }
121
- { endElement && (
122
- <span
123
- { ...endElementProps }
124
- className={ cn(
125
- 'shrink-0 inline-flex items-center justify-center',
126
- endElementProps?.className,
127
- ) }
128
- >
129
- { endElement }
130
- </span>
131
- ) }
156
+ { endElement && (() => {
157
+ const { className: endClassName, ...endElementRest } = endElementProps ?? {};
158
+ return (
159
+ <XStack
160
+ { ...(endElementRest as object) }
161
+ flexShrink={ 0 }
162
+ alignItems="center"
163
+ justifyContent="center"
164
+ className={ endClassName }
165
+ >
166
+ { endElement }
167
+ </XStack>
168
+ );
169
+ })() }
132
170
  { closable && (
133
- <span className="shrink-0 inline-flex items-center justify-center">
171
+ <XStack flexShrink={ 0 } alignItems="center" justifyContent="center">
134
172
  <CloseButton onClick={ onClose }/>
135
- </span>
173
+ </XStack>
136
174
  ) }
137
- </span>
175
+ </XStack>
138
176
  </Skeleton>
139
177
  );
140
178
  },
package/src/tooltip.tsx CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  import { useClickAway } from '@uidotdev/usehooks';
6
6
  import * as React from 'react';
7
7
 
8
+ import { ink } from './ink';
8
9
  import { useIsTouch } from './use-narrow';
9
- import { cn } from './utils';
10
10
 
11
11
  /** Map placement string to @hanzogui Popper placement prop. */
12
12
  function mapPlacement(p: string | undefined): string | undefined {
@@ -161,14 +161,21 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
161
161
  <GuiTooltip.Content
162
162
  ref={ ref }
163
163
  unstyled
164
- className={ cn(
165
- 'z-[9999] overflow-hidden rounded-lg px-3 py-2 text-sm',
166
- 'animate-in fade-in-0 zoom-in-95',
167
- isPopover && 'bg-[var(--color-popover-bg)] text-[var(--color-text-primary)]',
168
- isPopover && 'shadow-[var(--shadow-popover)] border border-[var(--color-border-divider)] max-w-sm',
169
- !isPopover && 'bg-[var(--color-tooltip-bg)] text-[var(--color-tooltip-fg)] max-w-xs',
170
- contentProps?.className,
171
- ) }
164
+ zIndex={ 9999 }
165
+ overflow="hidden"
166
+ borderRadius={ 8 }
167
+ paddingHorizontal={ 12 }
168
+ paddingVertical={ 8 }
169
+ maxWidth={ isPopover ? 384 : 320 }
170
+ backgroundColor={ (isPopover ? 'var(--color-popover-bg)' : 'var(--color-tooltip-bg)') as never }
171
+ transition="quick"
172
+ enterStyle={{ opacity: 0, scale: 0.95 }}
173
+ exitStyle={{ opacity: 0, scale: 0.95 }}
174
+ { ...(isPopover ? {
175
+ borderWidth: 1,
176
+ borderColor: 'var(--color-border-divider)' as never,
177
+ shadowColor: 'var(--shadow-popover)' as never,
178
+ } : {}) }
172
179
  onClick={ interactive ? handleContentClick : undefined }
173
180
  { ...(selected ? { 'data-selected': true } : {}) }
174
181
  { ...contentProps as any }
@@ -176,12 +183,13 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
176
183
  { showArrow && (
177
184
  <GuiTooltip.Arrow
178
185
  unstyled
179
- className={ cn(
180
- isPopover ? 'fill-[var(--color-popover-bg)]' : 'fill-[var(--color-tooltip-bg)]',
181
- ) }
186
+ backgroundColor={ (isPopover ? 'var(--color-popover-bg)' : 'var(--color-tooltip-bg)') as never }
182
187
  />
183
188
  ) }
184
- { content }
189
+ { ink(content, undefined, {
190
+ fontSize: 14,
191
+ color: (isPopover ? 'var(--color-text-primary)' : 'var(--color-tooltip-fg)') as never,
192
+ }) }
185
193
  </GuiTooltip.Content>
186
194
  </GuiTooltip>
187
195
  </GuiTooltipGroup>
package/tokens.css CHANGED
@@ -890,6 +890,15 @@
890
890
  they do not loop. The spinner rotates forever, so it is CSS. */
891
891
  .lux-spin { animation: lux-spin 0.8s linear infinite }
892
892
 
893
+ /* A scrolling strip with no visible scrollbar. The scrollbar is a
894
+ pseudo-element on the box, so this is the only place it can be said. */
895
+ /* One-line truncation. `text-overflow` and `white-space` are not in gui's Text
896
+ style base, and the three declarations only mean anything together. */
897
+ .lux-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
898
+
899
+ .lux-no-scrollbar { scrollbar-width: none; -ms-overflow-style: none }
900
+ .lux-no-scrollbar::-webkit-scrollbar { display: none }
901
+
893
902
  /* Size the ICON inside a control. A descendant selector has no prop form, so
894
903
  the control publishes its icon size and this rule applies it. */
895
904
  .lux-icon svg { width: var(--icon-size, 20px); height: var(--icon-size, 20px) }
@@ -943,6 +952,15 @@
943
952
  user-select: none;
944
953
  }
945
954
 
955
+ /* A skeleton wrapping real content (asChild, or loading with children already
956
+ mounted) must mask it — an <img> or an icon doesn't pick up `color:
957
+ transparent` above. `visibility` rather than `display:none` so the masked
958
+ subtree keeps its layout instead of collapsing it, which is the zero-CLS
959
+ promise this component makes. A descendant selector has no prop form. */
960
+ .lux-skeleton * {
961
+ visibility: hidden;
962
+ }
963
+
946
964
  /* The one moving part. Fixed, 2px, above everything, out of flow — so it
947
965
  cannot shift layout and cannot tint content. Mount it while any request
948
966
  is in flight; unmount it when they land. One per page. */