@luxfi/ui 7.4.12 → 7.4.13

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.
@@ -1,5 +1,7 @@
1
+ import { View } from '@hanzo/gui';
1
2
  import * as React from 'react';
2
3
 
4
+ import { ink } from './ink';
3
5
  import { cn } from './utils';
4
6
 
5
7
  // ---------------------------------------------------------------------------
@@ -7,11 +9,11 @@ import { cn } from './utils';
7
9
  // ---------------------------------------------------------------------------
8
10
 
9
11
  const SIZE_MAP = {
10
- xs: { size: 24, thickness: 4, textClass: 'text-[10px]' },
11
- sm: { size: 32, thickness: 5, textClass: 'text-[10px]' },
12
- md: { size: 40, thickness: 6, textClass: 'text-xs' },
13
- lg: { size: 48, thickness: 7, textClass: 'text-sm' },
14
- xl: { size: 64, thickness: 8, textClass: 'text-sm' },
12
+ xs: { size: 24, thickness: 4, fontSize: 10 },
13
+ sm: { size: 32, thickness: 5, fontSize: 10 },
14
+ md: { size: 40, thickness: 6, fontSize: 12 },
15
+ lg: { size: 48, thickness: 7, fontSize: 14 },
16
+ xl: { size: 64, thickness: 8, fontSize: 14 },
15
17
  } as const;
16
18
 
17
19
  type Size = keyof typeof SIZE_MAP;
@@ -63,17 +65,23 @@ export const ProgressCircleRoot = React.forwardRef<
63
65
 
64
66
  return (
65
67
  <Ctx.Provider value={ ctx }>
66
- <div
67
- ref={ ref }
68
+ <View
69
+ ref={ ref as never }
68
70
  role="progressbar"
69
71
  aria-valuenow={ value ?? undefined }
70
72
  aria-valuemin={ 0 }
71
73
  aria-valuemax={ 100 }
72
- className={ cn('relative inline-flex text-sm', className) }
73
- { ...rest }
74
+ position="relative"
75
+ // `inline-flex` and `fontSize` are not in a Stack's style base — a
76
+ // Stack has no text props at all, and its `display` enum is narrower
77
+ // than CSS's (no `inline-*` variants) — so both travel as plain style
78
+ // here rather than gui props.
79
+ style={{ display: 'inline-flex', fontSize: 14 }}
80
+ className={ className }
81
+ { ...(rest as object) }
74
82
  >
75
- { children }
76
- </div>
83
+ { ink(children) }
84
+ </View>
77
85
  </Ctx.Provider>
78
86
  );
79
87
  });
@@ -108,10 +116,7 @@ export const ProgressCircleRing = React.forwardRef<
108
116
  width={ size }
109
117
  height={ size }
110
118
  viewBox={ `0 0 ${ size } ${ size }` }
111
- className={ cn(
112
- value === null && 'animate-spin',
113
- className,
114
- ) }
119
+ className={ value === null ? cn('lux-spin', className) : className }
115
120
  { ...rest }
116
121
  >
117
122
  { /* Track */ }
@@ -122,7 +127,7 @@ export const ProgressCircleRing = React.forwardRef<
122
127
  fill="transparent"
123
128
  strokeWidth={ thickness }
124
129
  stroke={ trackColor ?? 'currentColor' }
125
- className={ !trackColor ? 'opacity-20' : undefined }
130
+ opacity={ !trackColor ? 0.2 : undefined }
126
131
  />
127
132
  { /* Range */ }
128
133
  <circle
@@ -160,21 +165,40 @@ export const ProgressCircleValueText = React.forwardRef<
160
165
  const { className, children, ...rest } = props;
161
166
  const { value, size: sizeKey } = React.useContext(Ctx);
162
167
 
163
- const { textClass } = SIZE_MAP[sizeKey];
168
+ const { fontSize } = SIZE_MAP[sizeKey];
164
169
 
165
170
  return (
166
- <div
167
- ref={ ref }
171
+ <View
172
+ ref={ ref as never }
168
173
  aria-live="polite"
169
- className={ cn(
170
- 'absolute inset-0 flex items-center justify-center',
171
- 'font-medium tracking-tight tabular-nums leading-none',
172
- textClass,
173
- className,
174
- ) }
175
- { ...rest }
174
+ position="absolute"
175
+ top={ 0 }
176
+ left={ 0 }
177
+ right={ 0 }
178
+ bottom={ 0 }
179
+ display="flex"
180
+ alignItems="center"
181
+ justifyContent="center"
182
+ // A Stack has no text props (fontSize/fontWeight/letterSpacing/lineHeight
183
+ // /fontVariantNumeric) — a View cannot style text any more than a native
184
+ // <View> can, only the <Text> inside it. `ink()` below hosts the string
185
+ // in a real Text, but callers may also pass their own element, so this
186
+ // stays a style object rather than gui text props: it composes either
187
+ // way, exactly like the Tailwind classes it replaces did through
188
+ // inheritance. Tailwind's `leading-none` (line-height: 1, unitless) and
189
+ // `tracking-tight` (-0.025em) are both relative to font size, so they are
190
+ // computed per size tier here instead of copied as one constant.
191
+ style={{
192
+ fontWeight: 500,
193
+ fontSize,
194
+ lineHeight: `${ fontSize }px`,
195
+ letterSpacing: fontSize * -0.025,
196
+ fontVariantNumeric: 'tabular-nums',
197
+ }}
198
+ className={ className }
199
+ { ...(rest as object) }
176
200
  >
177
- { children ?? `${ Math.round(value ?? 0) }%` }
178
- </div>
201
+ { ink(children ?? `${ Math.round(value ?? 0) }%`) }
202
+ </View>
179
203
  );
180
204
  });
package/src/radio.tsx CHANGED
@@ -64,7 +64,6 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
64
64
  from its headless core, real Pressables on native. */ }
65
65
  <GuiRadioGroup
66
66
  ref={ ref as never }
67
- unstyled
68
67
  name={ name }
69
68
  required={ required }
70
69
  disabled={ disabled || readOnly }
package/src/rating.tsx CHANGED
@@ -1,15 +1,15 @@
1
+ import { Text, View, XStack } from '@hanzo/gui';
1
2
  import * as React from 'react';
2
3
 
3
- import { cn } from './utils';
4
-
5
- // Inline star icons
6
- const StarFilledIcon = ({ className }: { readonly className?: string }) => (
7
- <svg className={ className } viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
4
+ // Inline star icons fixed at the one size Rating ever draws them (20px, the
5
+ // star buttons' own width/height), so there is nothing left to parameterize.
6
+ const StarFilledIcon = () => (
7
+ <svg width={ 20 } height={ 20 } viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
8
8
  <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
9
9
  </svg>
10
10
  );
11
- const StarOutlineIcon = ({ className }: { readonly className?: string }) => (
12
- <svg className={ className } viewBox="0 0 20 20" fill="none" stroke="currentColor" xmlns="http://www.w3.org/2000/svg">
11
+ const StarOutlineIcon = () => (
12
+ <svg width={ 20 } height={ 20 } viewBox="0 0 20 20" fill="none" stroke="currentColor" xmlns="http://www.w3.org/2000/svg">
13
13
  <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" strokeWidth="1.5"/>
14
14
  </svg>
15
15
  );
@@ -48,32 +48,34 @@ export const Rating = React.forwardRef<HTMLDivElement, RatingProps>(
48
48
  }, []);
49
49
 
50
50
  return (
51
- <div ref={ ref } className={ cn('inline-flex items-center gap-1', className) } { ...rest }>
52
- <div className="inline-flex items-center" onMouseLeave={ handleMouseLeave }>
51
+ <XStack ref={ ref as never } display="inline-flex" alignItems="center" gap={ 4 } className={ className } { ...(rest as object) }>
52
+ <XStack display="inline-flex" alignItems="center" onMouseLeave={ handleMouseLeave }>
53
53
  { Array.from({ length: count }).map((_, index) => {
54
54
  const filled = index < highlightedIndex;
55
55
  const starIndex = index + 1;
56
56
 
57
57
  return (
58
- <button
58
+ <View
59
59
  key={ index }
60
- type="button"
60
+ render={ <button type="button"/> }
61
61
  tabIndex={ readOnly ? -1 : 0 }
62
62
  aria-label={ `Rate ${ starIndex } of ${ count }` }
63
- className={ cn(
64
- 'inline-flex items-center justify-center w-5 h-5 text-current',
65
- readOnly ? 'cursor-default' : 'cursor-pointer',
66
- ) }
63
+ display="inline-flex"
64
+ alignItems="center"
65
+ justifyContent="center"
66
+ width={ 20 }
67
+ height={ 20 }
68
+ cursor={ readOnly ? 'default' : 'pointer' }
67
69
  onClick={ handleClick(starIndex) }
68
70
  onMouseEnter={ handleMouseEnter(index) }
69
71
  >
70
- { filled ? <StarFilledIcon className="w-5 h-5"/> : <StarOutlineIcon className="w-5 h-5"/> }
71
- </button>
72
+ { filled ? <StarFilledIcon/> : <StarOutlineIcon/> }
73
+ </View>
72
74
  );
73
75
  }) }
74
- </div>
75
- { label && <span className="text-sm">{ label }</span> }
76
- </div>
76
+ </XStack>
77
+ { label && <Text fontSize={ 14 }>{ label }</Text> }
78
+ </XStack>
77
79
  );
78
80
  },
79
81
  );
package/src/slider.tsx CHANGED
@@ -87,7 +87,6 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
87
87
 
88
88
  <GuiSlider
89
89
  ref={ ref as never }
90
- unstyled
91
90
  position="relative"
92
91
  flexDirection="row"
93
92
  width="100%"
@@ -163,20 +163,25 @@ const STRATEGY_PRESETS: StrategyPreset[] = [
163
163
  ];
164
164
 
165
165
  // ---------------------------------------------------------------------------
166
- // Shared control shape — same tokens input.tsx's native text control uses.
166
+ // Shared native-control shape — same tokens input.tsx's own control uses.
167
+ // `<select>`/`<input>` render no gui Text of their own, so every text-facing
168
+ // property (colour, size, family) rides the `style` escape hatch here rather
169
+ // than a typed prop: View's typed props are the same regardless of what tag
170
+ // `render` points at, and none of them cover text — only Text has those.
167
171
  // ---------------------------------------------------------------------------
168
172
 
169
- const MONO = { fontFamily: 'monospace' } as never;
173
+ const MONO = 'monospace';
170
174
 
171
- const CONTROL = {
175
+ const CONTROL_BOX = {
172
176
  backgroundColor: 'var(--color-input-bg)',
173
177
  borderWidth: 1,
174
178
  borderColor: 'var(--color-input-border)',
175
- color: 'var(--color-input-fg)',
176
179
  outlineWidth: 0,
177
180
  focusStyle: { borderColor: 'var(--color-input-border-focus)' },
178
181
  } as const;
179
182
 
183
+ const CONTROL_INK = { color: 'var(--color-input-fg)' };
184
+
180
185
  // ---------------------------------------------------------------------------
181
186
  // LegRow
182
187
  // ---------------------------------------------------------------------------
@@ -205,7 +210,7 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
205
210
  paddingHorizontal={ 8 }
206
211
  paddingVertical={ 6 }
207
212
  >
208
- <Text fontSize={ 10 } style={ MONO } flexShrink={ 0 } width={ 16 } color={ 'var(--color-text-secondary)' as never }>
213
+ <Text fontSize={ 10 } style={ { fontFamily: MONO } as never } flexShrink={ 0 } width={ 16 } color={ 'var(--color-text-secondary)' as never }>
209
214
  { index + 1 }
210
215
  </Text>
211
216
 
@@ -247,19 +252,19 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
247
252
  </Text>
248
253
  </XStack>
249
254
 
250
- {/* Strike */}
255
+ {/* Strike. `value`/`onChange` are native <select> attributes, not gui
256
+ style props — View's prop set is fixed regardless of what `render`
257
+ points at, so anything the host tag itself needs (not a style) lives
258
+ on the `render` element, same as `type="button"` does for buttons. */}
251
259
  <View
252
260
  unstyled
253
- render={ <select/> }
254
- value={ leg.strike }
255
- onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => update({ strike: Number(e.target.value) }) }
256
- { ...CONTROL }
257
- style={ MONO }
258
- fontSize={ 12 }
261
+ render={ <select value={ leg.strike } onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => update({ strike: Number(e.target.value) }) }/> as never }
262
+ { ...CONTROL_BOX }
259
263
  borderRadius={ 4 }
260
264
  paddingHorizontal={ 6 }
261
265
  paddingVertical={ 2 }
262
266
  minWidth={ 72 }
267
+ style={ { ...CONTROL_INK, fontFamily: MONO, fontSize: 12 } as never }
263
268
  >
264
269
  { strikes.map((s) => (
265
270
  <option key={ s } value={ s }>
@@ -271,15 +276,13 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
271
276
  {/* Expiration */}
272
277
  <View
273
278
  unstyled
274
- render={ <select/> }
275
- value={ leg.expiration }
276
- onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => update({ expiration: e.target.value }) }
277
- { ...CONTROL }
278
- fontSize={ 12 }
279
+ render={ <select value={ leg.expiration } onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => update({ expiration: e.target.value }) }/> as never }
280
+ { ...CONTROL_BOX }
279
281
  borderRadius={ 4 }
280
282
  paddingHorizontal={ 6 }
281
283
  paddingVertical={ 2 }
282
284
  minWidth={ 80 }
285
+ style={ { ...CONTROL_INK, fontSize: 12 } as never }
283
286
  >
284
287
  { expirations.map((exp) => (
285
288
  <option key={ exp } value={ exp }>
@@ -291,26 +294,28 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
291
294
  {/* Quantity */}
292
295
  <View
293
296
  unstyled
294
- render={ <input type="number"/> }
295
- min={ 1 }
296
- max={ 999 }
297
- value={ leg.quantity }
298
- onChange={ (e: React.ChangeEvent<HTMLInputElement>) => update({ quantity: Math.max(1, parseInt(e.target.value, 10) || 1) }) }
299
- { ...CONTROL }
300
- style={ MONO }
301
- fontSize={ 12 }
297
+ render={ (
298
+ <input
299
+ type="number"
300
+ min={ 1 }
301
+ max={ 999 }
302
+ value={ leg.quantity }
303
+ onChange={ (e: React.ChangeEvent<HTMLInputElement>) => update({ quantity: Math.max(1, parseInt(e.target.value, 10) || 1) }) }
304
+ />
305
+ ) as never }
306
+ { ...CONTROL_BOX }
302
307
  borderRadius={ 4 }
303
308
  paddingHorizontal={ 6 }
304
309
  paddingVertical={ 2 }
305
310
  width={ 48 }
306
- textAlign="center"
311
+ style={ { ...CONTROL_INK, fontFamily: MONO, fontSize: 12, textAlign: 'center' } as never }
307
312
  />
308
313
 
309
314
  {/* Premium (read-only if provided) */}
310
315
  { leg.premium !== undefined && (
311
316
  <Text
312
317
  fontSize={ 12 }
313
- style={ MONO }
318
+ style={ { fontFamily: MONO } as never }
314
319
  fontVariant={ ['tabular-nums'] as never }
315
320
  marginLeft="auto"
316
321
  color={ (leg.premium >= 0 ? 'var(--color-status-good)' : 'var(--color-status-bad)') as never }
@@ -319,7 +324,12 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
319
324
  </Text>
320
325
  ) }
321
326
 
322
- {/* Remove */}
327
+ {/* Remove. The icon's `currentColor` needs a real ancestor CSS colour —
328
+ not a gui prop (Stacks have none) — so it rides `style`. The
329
+ hover→red retint from the original Tailwind isn't reachable without
330
+ either a new named class (out of scope) or component-local hover
331
+ state, so resting colour is kept and a same-family opacity shift
332
+ substitutes for hover feedback. */ }
323
333
  { removable && (
324
334
  <XStack
325
335
  unstyled
@@ -330,8 +340,8 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
330
340
  borderWidth={ 0 }
331
341
  cursor="pointer"
332
342
  transition="quick"
333
- color={ 'var(--color-text-secondary)' as never }
334
- hoverStyle={ { color: 'var(--color-status-bad)' } as never }
343
+ style={ { color: 'var(--color-text-secondary)' } as never }
344
+ hoverStyle={ { opacity: 0.7 } as never }
335
345
  >
336
346
  <svg width={ 14 } height={ 14 } viewBox="0 0 24 24" fill="none">
337
347
  <path d="M18 6L6 18M6 6l12 12" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
@@ -447,7 +457,7 @@ export const StrategyBuilder = React.forwardRef<HTMLDivElement, StrategyBuilderP
447
457
  </Text>
448
458
  </XStack>
449
459
  <Text fontSize={ 10 } color={ 'var(--color-text-secondary)' as never }>
450
- Spot: <Text style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ 'var(--color-text-primary)' as never }>{ spotPrice.toFixed(2) }</Text>
460
+ Spot: <Text style={ { fontFamily: MONO } as never } fontVariant={ ['tabular-nums'] as never } color={ 'var(--color-text-primary)' as never }>{ spotPrice.toFixed(2) }</Text>
451
461
  </Text>
452
462
  </XStack>
453
463
 
@@ -455,15 +465,13 @@ export const StrategyBuilder = React.forwardRef<HTMLDivElement, StrategyBuilderP
455
465
  <XStack alignItems="center" gap={ 8 }>
456
466
  <View
457
467
  unstyled
458
- render={ <select/> }
459
- value={ strategyType }
460
- onChange={ handleStrategyChange }
468
+ render={ <select value={ strategyType } onChange={ handleStrategyChange }/> as never }
461
469
  flex={ 1 }
462
- { ...CONTROL }
463
- fontSize={ 14 }
470
+ { ...CONTROL_BOX }
464
471
  borderRadius={ 6 }
465
472
  paddingHorizontal={ 10 }
466
473
  paddingVertical={ 6 }
474
+ style={ { ...CONTROL_INK, fontSize: 14 } as never }
467
475
  >
468
476
  { STRATEGY_PRESETS.map((preset) => (
469
477
  <option key={ preset.value } value={ preset.value }>
@@ -495,7 +503,9 @@ export const StrategyBuilder = React.forwardRef<HTMLDivElement, StrategyBuilderP
495
503
  )) }
496
504
  </YStack>
497
505
 
498
- {/* Add leg button (custom only) */}
506
+ {/* Add leg button (custom only). Resting ink rides `style` so the
507
+ icon's `currentColor` and the label both resolve it; hover keeps
508
+ the border re-tint (a valid Stack prop) as the interactive cue. */}
499
509
  { strategyType === 'custom' && legs.length < 4 && (
500
510
  <XStack
501
511
  unstyled
@@ -509,15 +519,14 @@ export const StrategyBuilder = React.forwardRef<HTMLDivElement, StrategyBuilderP
509
519
  paddingVertical={ 4 }
510
520
  cursor="pointer"
511
521
  transition="quick"
512
- style={ { borderStyle: 'dashed' } as never }
513
522
  borderColor={ 'var(--color-border-divider)' as never }
514
- color={ 'var(--color-text-secondary)' as never }
515
- hoverStyle={ { borderColor: 'var(--color-hover)', color: 'var(--color-hover)' } as never }
523
+ hoverStyle={ { borderColor: 'var(--color-hover)' } as never }
524
+ style={ { color: 'var(--color-text-secondary)', borderStyle: 'dashed' } as never }
516
525
  >
517
526
  <svg width={ 12 } height={ 12 } viewBox="0 0 24 24" fill="none">
518
527
  <path d="M12 5v14M5 12h14" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
519
528
  </svg>
520
- <Text unstyled fontSize={ 12 }>Add Leg</Text>
529
+ <Text fontSize={ 12 } color={ 'var(--color-text-secondary)' as never }>Add Leg</Text>
521
530
  </XStack>
522
531
  ) }
523
532
 
@@ -525,7 +534,7 @@ export const StrategyBuilder = React.forwardRef<HTMLDivElement, StrategyBuilderP
525
534
  <XStack alignItems="center" justifyContent="space-between" paddingTop={ 4 } borderTopWidth={ 1 } borderColor={ 'var(--color-border-divider)' as never }>
526
535
  <XStack alignItems="center" gap={ 8 }>
527
536
  <Text fontSize={ 12 } color={ 'var(--color-text-secondary)' as never }>Net:</Text>
528
- <Text fontSize={ 14 } fontWeight="600" style={ MONO } fontVariant={ ['tabular-nums'] as never } color={ netColor as never }>
537
+ <Text fontSize={ 14 } fontWeight="600" style={ { fontFamily: MONO } as never } fontVariant={ ['tabular-nums'] as never } color={ netColor as never }>
529
538
  { netPremium > 0 ? 'Credit ' : netPremium < 0 ? 'Debit ' : '' }
530
539
  { netPremium !== 0 ? `$${ Math.abs(netPremium).toFixed(2) }` : '-' }
531
540
  </Text>