@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.
- package/dist/alert.cjs +68 -46
- package/dist/alert.js +69 -46
- package/dist/avatar.cjs +49 -46
- package/dist/avatar.js +52 -48
- package/dist/badge.cjs +82 -49
- package/dist/badge.js +83 -50
- package/dist/checkbox.cjs +42 -75
- package/dist/checkbox.js +43 -76
- package/dist/chrome.cjs +3 -7
- package/dist/chrome.js +3 -7
- package/dist/close-button.cjs +3 -7
- package/dist/close-button.js +3 -7
- package/dist/dialog.cjs +3 -7
- package/dist/dialog.js +3 -7
- package/dist/drawer.cjs +3 -7
- package/dist/drawer.js +3 -7
- package/dist/empty-state.cjs +13 -13
- package/dist/empty-state.js +13 -13
- package/dist/expiration-selector.cjs +58 -63
- package/dist/expiration-selector.js +58 -63
- package/dist/greeks-display.cjs +48 -39
- package/dist/greeks-display.js +48 -39
- package/dist/index.cjs +756 -703
- package/dist/index.js +757 -704
- package/dist/option-chain.cjs +89 -67
- package/dist/option-chain.js +89 -67
- package/dist/option-position.cjs +88 -85
- package/dist/option-position.js +88 -85
- package/dist/pnl-diagram.cjs +20 -20
- package/dist/pnl-diagram.js +20 -20
- package/dist/popover.cjs +3 -7
- package/dist/popover.js +3 -7
- package/dist/progress.cjs +15 -22
- package/dist/progress.d.cts +6 -6
- package/dist/progress.d.ts +6 -6
- package/dist/progress.js +15 -22
- package/dist/radio.cjs +35 -67
- package/dist/radio.d.cts +18 -18
- package/dist/radio.d.ts +18 -18
- package/dist/radio.js +36 -68
- package/dist/slider.cjs +33 -49
- package/dist/slider.js +34 -50
- package/dist/switch.cjs +48 -55
- package/dist/switch.js +49 -56
- package/dist/tag.cjs +112 -69
- package/dist/tag.js +113 -69
- package/dist/tooltip.cjs +28 -17
- package/dist/tooltip.js +30 -18
- package/package.json +1 -1
- package/src/alert.tsx +78 -45
- package/src/avatar.tsx +54 -37
- package/src/badge.tsx +65 -44
- package/src/checkbox.tsx +70 -89
- package/src/close-button.tsx +3 -8
- package/src/empty-state.tsx +21 -17
- package/src/expiration-selector.tsx +84 -66
- package/src/greeks-display.tsx +59 -51
- package/src/input-group.tsx +38 -24
- package/src/option-chain.tsx +154 -104
- package/src/option-position.tsx +136 -114
- package/src/pin-input.tsx +37 -29
- package/src/pnl-diagram.tsx +36 -28
- package/src/progress.tsx +17 -21
- package/src/radio.tsx +56 -75
- package/src/slider.tsx +43 -44
- package/src/strategy-builder.tsx +245 -156
- package/src/switch.tsx +63 -64
- package/src/tag.tsx +89 -51
- package/src/tooltip.tsx +21 -13
- package/tokens.css +18 -0
package/src/pin-input.tsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { Input as GuiInput, XStack } from '@hanzo/gui';
|
|
1
2
|
import * as React from 'react';
|
|
2
3
|
|
|
3
|
-
import { cn } from './utils';
|
|
4
|
-
|
|
5
4
|
export interface PinInputProps {
|
|
6
5
|
readonly rootRef?: React.Ref<HTMLDivElement>;
|
|
7
6
|
readonly count?: number;
|
|
@@ -19,21 +18,31 @@ export interface PinInputProps {
|
|
|
19
18
|
readonly className?: string;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
// One box's frame. Colour and focus/hover/disabled states are the same
|
|
22
|
+
// `--color-input-*` tokens `Input` itself uses — a pin box is a text control,
|
|
23
|
+
// just a square one. `::placeholder` and the invalid border are the existing
|
|
24
|
+
// `.lux-control` rule in tokens.css (a pseudo-element and a selector, neither
|
|
25
|
+
// of which a prop reaches); `data-invalid` is what wires this box into it.
|
|
26
|
+
const BOX = {
|
|
27
|
+
width: 40,
|
|
28
|
+
height: 40,
|
|
29
|
+
textAlign: 'center' as const,
|
|
30
|
+
fontSize: 14,
|
|
31
|
+
fontWeight: 500 as const,
|
|
32
|
+
borderRadius: 6,
|
|
33
|
+
borderWidth: 2,
|
|
34
|
+
borderStyle: 'solid' as const,
|
|
35
|
+
outlineWidth: 0,
|
|
36
|
+
backgroundColor: 'var(--color-input-bg)' as never,
|
|
37
|
+
color: 'var(--color-input-fg)' as never,
|
|
38
|
+
borderColor: 'var(--color-input-border)' as never,
|
|
39
|
+
hoverStyle: { borderColor: 'var(--color-input-border-hover)' as never },
|
|
40
|
+
focusStyle: {
|
|
41
|
+
borderColor: 'var(--color-input-border-focus)' as never,
|
|
42
|
+
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
|
43
|
+
},
|
|
44
|
+
disabledStyle: { opacity: 0.4 },
|
|
45
|
+
};
|
|
37
46
|
|
|
38
47
|
export const PinInput = React.forwardRef<HTMLInputElement, PinInputProps>(
|
|
39
48
|
function PinInput(props, ref) {
|
|
@@ -142,10 +151,10 @@ export const PinInput = React.forwardRef<HTMLInputElement, PinInputProps>(
|
|
|
142
151
|
handleKeyDown(index, e);
|
|
143
152
|
}, [ handleKeyDown ]);
|
|
144
153
|
|
|
145
|
-
const
|
|
154
|
+
const boxBackground = bgColor ? `var(--color-${ bgColor.replace(/\./g, '-') })` as never : BOX.backgroundColor;
|
|
146
155
|
|
|
147
156
|
return (
|
|
148
|
-
<
|
|
157
|
+
<XStack ref={ rootRef as never } alignItems="center" gap={ attached ? 0 : 8 } className={ className }>
|
|
149
158
|
{ /* Hidden input for form submission */ }
|
|
150
159
|
<input
|
|
151
160
|
ref={ ref }
|
|
@@ -155,9 +164,10 @@ export const PinInput = React.forwardRef<HTMLInputElement, PinInputProps>(
|
|
|
155
164
|
{ ...inputProps }
|
|
156
165
|
/>
|
|
157
166
|
{ Array.from({ length: count }).map((_, index) => (
|
|
158
|
-
<
|
|
167
|
+
<GuiInput
|
|
159
168
|
key={ index }
|
|
160
|
-
ref={ setInputRef(index) }
|
|
169
|
+
ref={ setInputRef(index) as never }
|
|
170
|
+
unstyled
|
|
161
171
|
type="text"
|
|
162
172
|
inputMode="numeric"
|
|
163
173
|
autoComplete={ otp ? 'one-time-code' : 'off' }
|
|
@@ -166,14 +176,12 @@ export const PinInput = React.forwardRef<HTMLInputElement, PinInputProps>(
|
|
|
166
176
|
placeholder={ placeholder }
|
|
167
177
|
disabled={ disabled }
|
|
168
178
|
aria-invalid={ invalid || undefined }
|
|
179
|
+
data-invalid={ invalid || undefined }
|
|
169
180
|
value={ values[index] || '' }
|
|
170
|
-
className=
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
attached && index > 0 && '-ml-0.5',
|
|
175
|
-
) }
|
|
176
|
-
style={ bgStyle }
|
|
181
|
+
className="lux-control"
|
|
182
|
+
{ ...BOX }
|
|
183
|
+
backgroundColor={ boxBackground }
|
|
184
|
+
marginLeft={ attached && index > 0 ? -2 : undefined }
|
|
177
185
|
onInput={ onInputAtIndex(index) }
|
|
178
186
|
onKeyDown={ onKeyDownAtIndex(index) }
|
|
179
187
|
onPaste={ handlePaste }
|
|
@@ -181,7 +189,7 @@ export const PinInput = React.forwardRef<HTMLInputElement, PinInputProps>(
|
|
|
181
189
|
onChange={ handleNoop }
|
|
182
190
|
/>
|
|
183
191
|
)) }
|
|
184
|
-
</
|
|
192
|
+
</XStack>
|
|
185
193
|
);
|
|
186
194
|
},
|
|
187
195
|
);
|
package/src/pnl-diagram.tsx
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { Text, View, XStack } from '@hanzo/gui';
|
|
3
4
|
import * as React from 'react';
|
|
4
5
|
|
|
5
|
-
import { cn } from './utils';
|
|
6
|
-
|
|
7
6
|
import type { StrategyLeg } from './strategy-builder';
|
|
8
7
|
|
|
9
8
|
// ---------------------------------------------------------------------------
|
|
@@ -207,17 +206,24 @@ export const PnlDiagram = React.forwardRef<HTMLDivElement, PnlDiagramProps>(
|
|
|
207
206
|
);
|
|
208
207
|
|
|
209
208
|
return (
|
|
210
|
-
<
|
|
211
|
-
ref={ref}
|
|
212
|
-
|
|
213
|
-
|
|
209
|
+
<View
|
|
210
|
+
ref={ref as never}
|
|
211
|
+
unstyled
|
|
212
|
+
width="100%"
|
|
213
|
+
className={className}
|
|
214
|
+
{...(rest as object)}
|
|
214
215
|
>
|
|
215
|
-
<
|
|
216
|
+
<View unstyled ref={containerRef as never} width="100%" height="100%" minHeight={200}>
|
|
217
|
+
{/* This <svg> and everything inside it is the chart itself — left exactly as
|
|
218
|
+
it was: every fill/stroke/d/viewBox below is an SVG presentation attribute,
|
|
219
|
+
not Tailwind, and out of scope for a className→prop conversion. The one
|
|
220
|
+
change is `select-none`, a real Tailwind utility (not a presentation
|
|
221
|
+
attribute), moved to an equivalent inline style with no visual effect. */}
|
|
216
222
|
<svg
|
|
217
223
|
width={width}
|
|
218
224
|
height={height}
|
|
219
225
|
viewBox={`0 0 ${width} ${height}`}
|
|
220
|
-
|
|
226
|
+
style={{ userSelect: 'none' }}
|
|
221
227
|
onMouseMove={handleMouseMove}
|
|
222
228
|
onMouseLeave={handleMouseLeave}
|
|
223
229
|
>
|
|
@@ -425,39 +431,41 @@ export const PnlDiagram = React.forwardRef<HTMLDivElement, PnlDiagramProps>(
|
|
|
425
431
|
);
|
|
426
432
|
})}
|
|
427
433
|
</svg>
|
|
428
|
-
</
|
|
434
|
+
</View>
|
|
429
435
|
|
|
430
436
|
{/* Legend */}
|
|
431
|
-
<
|
|
432
|
-
<
|
|
433
|
-
<
|
|
434
|
-
<
|
|
435
|
-
</
|
|
437
|
+
<XStack flexWrap="wrap" alignItems="center" gap={16} paddingHorizontal={8} paddingTop={4}>
|
|
438
|
+
<XStack alignItems="center" gap={6}>
|
|
439
|
+
<View height={2} width={16} borderRadius={4} backgroundColor={'var(--color-gray-200)' as never} />
|
|
440
|
+
<Text fontSize={10} color={'var(--color-text-secondary)' as never}>Combined</Text>
|
|
441
|
+
</XStack>
|
|
436
442
|
{legs.map((leg, i) => (
|
|
437
|
-
<
|
|
438
|
-
<
|
|
439
|
-
|
|
443
|
+
<XStack key={i} alignItems="center" gap={6}>
|
|
444
|
+
<View
|
|
445
|
+
height={2}
|
|
446
|
+
width={16}
|
|
447
|
+
borderRadius={4}
|
|
440
448
|
style={{
|
|
441
449
|
backgroundColor: LEG_COLORS[i % LEG_COLORS.length],
|
|
442
450
|
opacity: 0.6,
|
|
443
451
|
backgroundImage: `repeating-linear-gradient(90deg, transparent, transparent 2px, ${LEG_COLORS[i % LEG_COLORS.length]}00 2px, ${LEG_COLORS[i % LEG_COLORS.length]}00 5px)`,
|
|
444
|
-
}}
|
|
452
|
+
} as never}
|
|
445
453
|
/>
|
|
446
|
-
<
|
|
454
|
+
<Text fontSize={10} color={'var(--color-text-secondary)' as never}>
|
|
447
455
|
{leg.action === 'buy' ? 'B' : 'S'} {leg.quantity} {leg.strike} {leg.type === 'call' ? 'C' : 'P'}
|
|
448
|
-
</
|
|
449
|
-
</
|
|
456
|
+
</Text>
|
|
457
|
+
</XStack>
|
|
450
458
|
))}
|
|
451
459
|
{breakevens.length > 0 && (
|
|
452
|
-
<
|
|
453
|
-
<
|
|
454
|
-
<
|
|
460
|
+
<XStack alignItems="center" gap={6}>
|
|
461
|
+
<View width={6} height={6} borderRadius={9999} style={{ background: BREAKEVEN_COLOR } as never} />
|
|
462
|
+
<Text fontSize={10} color={'var(--color-text-secondary)' as never}>
|
|
455
463
|
BE: {breakevens.map((b) => b.toFixed(1)).join(', ')}
|
|
456
|
-
</
|
|
457
|
-
</
|
|
464
|
+
</Text>
|
|
465
|
+
</XStack>
|
|
458
466
|
)}
|
|
459
|
-
</
|
|
460
|
-
</
|
|
467
|
+
</XStack>
|
|
468
|
+
</View>
|
|
461
469
|
);
|
|
462
470
|
},
|
|
463
471
|
);
|
package/src/progress.tsx
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { Progress as GuiProgress } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
lg: 'h-3',
|
|
10
|
-
xl: 'h-4',
|
|
4
|
+
const SIZE = {
|
|
5
|
+
sm: 4,
|
|
6
|
+
md: 8,
|
|
7
|
+
lg: 12,
|
|
8
|
+
xl: 16,
|
|
11
9
|
} as const;
|
|
12
10
|
|
|
13
|
-
type Size = keyof typeof
|
|
11
|
+
type Size = keyof typeof SIZE;
|
|
14
12
|
|
|
15
13
|
interface TrackProps {
|
|
16
14
|
readonly borderStartRadius?: number | string;
|
|
@@ -47,7 +45,6 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
|
47
45
|
size = 'md',
|
|
48
46
|
color,
|
|
49
47
|
trackProps,
|
|
50
|
-
className,
|
|
51
48
|
style,
|
|
52
49
|
w,
|
|
53
50
|
flex,
|
|
@@ -69,6 +66,7 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
|
69
66
|
};
|
|
70
67
|
|
|
71
68
|
const rootStyle: React.CSSProperties = {
|
|
69
|
+
position: 'relative',
|
|
72
70
|
...style,
|
|
73
71
|
...(w !== undefined && { width: w === 'full' ? '100%' : w }),
|
|
74
72
|
...(flex !== undefined && { flex }),
|
|
@@ -77,7 +75,6 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
|
77
75
|
return (
|
|
78
76
|
<div
|
|
79
77
|
ref={ ref }
|
|
80
|
-
className={ cn('relative', className) }
|
|
81
78
|
style={ rootStyle }
|
|
82
79
|
{ ...rest }
|
|
83
80
|
>
|
|
@@ -85,26 +82,25 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
|
85
82
|
value={ percent }
|
|
86
83
|
max={ 100 }
|
|
87
84
|
unstyled
|
|
88
|
-
className={
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
) }
|
|
85
|
+
className={ trackProps?.className }
|
|
86
|
+
width="100%"
|
|
87
|
+
overflow="hidden"
|
|
88
|
+
borderRadius={ 9999 }
|
|
89
|
+
height={ SIZE[size] }
|
|
90
|
+
backgroundColor={ 'var(--color-progress-track)' as never }
|
|
94
91
|
style={ trackStyle as never }
|
|
95
92
|
>
|
|
96
93
|
{ /* gui drives the fill off `value` — it sizes the indicator to 200%
|
|
97
94
|
and translates it — and eases it with the config's transition
|
|
98
|
-
driver. Geometry from the primitive, paint from
|
|
95
|
+
driver. Geometry from the primitive, paint from these props,
|
|
99
96
|
so the bar is a consequence of state rather than a hand-written
|
|
100
97
|
width + CSS transition. */ }
|
|
101
98
|
<GuiProgress.Indicator
|
|
102
99
|
unstyled
|
|
103
100
|
transition="slow"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
) }
|
|
101
|
+
height="100%"
|
|
102
|
+
borderRadius={ 9999 }
|
|
103
|
+
backgroundColor={ (color ? undefined : 'var(--color-progress-indicator)') as never }
|
|
108
104
|
style={{
|
|
109
105
|
...(color && { backgroundColor: `var(--color-${ color.replace('.', '-') }, ${ color })` }),
|
|
110
106
|
} as never}
|
package/src/radio.tsx
CHANGED
|
@@ -1,37 +1,19 @@
|
|
|
1
|
-
import { RadioGroup as GuiRadioGroup } from '@hanzo/gui';
|
|
1
|
+
import { Label, RadioGroup as GuiRadioGroup, Text, VisuallyHidden } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
control: 'h-3 w-3',
|
|
11
|
-
indicator: 'h-1.5 w-1.5',
|
|
12
|
-
label: 'text-xs',
|
|
13
|
-
},
|
|
14
|
-
sm: {
|
|
15
|
-
root: 'gap-1.5',
|
|
16
|
-
control: 'h-3.5 w-3.5',
|
|
17
|
-
indicator: 'h-1.5 w-1.5',
|
|
18
|
-
label: 'text-xs',
|
|
19
|
-
},
|
|
20
|
-
md: {
|
|
21
|
-
root: 'gap-2',
|
|
22
|
-
control: 'h-4 w-4',
|
|
23
|
-
indicator: 'h-2 w-2',
|
|
24
|
-
label: 'text-sm',
|
|
25
|
-
},
|
|
26
|
-
lg: {
|
|
27
|
-
root: 'gap-2.5',
|
|
28
|
-
control: 'h-5 w-5',
|
|
29
|
-
indicator: 'h-2.5 w-2.5',
|
|
30
|
-
label: 'text-base',
|
|
31
|
-
},
|
|
4
|
+
// ─── Size mappings ──────────────────────────────────────────────────
|
|
5
|
+
const SIZE = {
|
|
6
|
+
xs: { rootGap: 6, control: 12, indicator: 6, label: 12 },
|
|
7
|
+
sm: { rootGap: 6, control: 14, indicator: 6, label: 12 },
|
|
8
|
+
md: { rootGap: 8, control: 16, indicator: 8, label: 14 },
|
|
9
|
+
lg: { rootGap: 10, control: 20, indicator: 10, label: 16 },
|
|
32
10
|
} as const;
|
|
33
11
|
|
|
34
|
-
type RadioSize = keyof typeof
|
|
12
|
+
type RadioSize = keyof typeof SIZE;
|
|
13
|
+
|
|
14
|
+
const INK = 'var(--color-text-primary)' as never;
|
|
15
|
+
const RESTING_BORDER = 'var(--color-input-border)' as never;
|
|
16
|
+
const OUTLINE = 'var(--color-gray-500)' as never;
|
|
35
17
|
|
|
36
18
|
// ─── Size context ───────────────────────────────────────────────────
|
|
37
19
|
const RadioSizeContext = React.createContext<RadioSize>('md');
|
|
@@ -65,7 +47,6 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
65
47
|
value,
|
|
66
48
|
onValueChange,
|
|
67
49
|
size = 'md',
|
|
68
|
-
className,
|
|
69
50
|
...rest
|
|
70
51
|
} = props;
|
|
71
52
|
|
|
@@ -91,11 +72,9 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
91
72
|
defaultValue={ defaultValue }
|
|
92
73
|
value={ value ?? undefined }
|
|
93
74
|
onValueChange={ handleValueChange }
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
className,
|
|
98
|
-
) }
|
|
75
|
+
display="flex"
|
|
76
|
+
flexDirection={ orientation === 'vertical' ? 'column' : 'row' }
|
|
77
|
+
gap={ orientation === 'vertical' ? 12 : 24 }
|
|
99
78
|
{ ...(rest as object) }
|
|
100
79
|
>
|
|
101
80
|
{ children }
|
|
@@ -119,59 +98,61 @@ export interface RadioProps extends Omit<React.ComponentPropsWithoutRef<'label'>
|
|
|
119
98
|
|
|
120
99
|
const RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
121
100
|
function Radio(props, ref) {
|
|
122
|
-
const { children, inputProps, rootRef, value, disabled,
|
|
101
|
+
const { children, inputProps, rootRef, value, disabled, ...rest } = props;
|
|
123
102
|
const size = React.useContext(RadioSizeContext);
|
|
124
|
-
const
|
|
103
|
+
const s = SIZE[size];
|
|
125
104
|
|
|
126
105
|
return (
|
|
127
|
-
<
|
|
128
|
-
ref={ rootRef }
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
106
|
+
<Label
|
|
107
|
+
ref={ rootRef as never }
|
|
108
|
+
unstyled
|
|
109
|
+
flexDirection="row"
|
|
110
|
+
alignItems="center"
|
|
111
|
+
gap={ s.rootGap }
|
|
112
|
+
cursor={ disabled ? 'not-allowed' : 'pointer' }
|
|
113
|
+
userSelect="none"
|
|
114
|
+
opacity={ disabled ? 0.5 : 1 }
|
|
135
115
|
data-disabled={ disabled || undefined }
|
|
136
|
-
{ ...rest }
|
|
116
|
+
{ ...(rest as object) }
|
|
137
117
|
>
|
|
138
118
|
<GuiRadioGroup.Item
|
|
139
119
|
unstyled
|
|
140
120
|
value={ value }
|
|
141
121
|
disabled={ disabled }
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
122
|
+
flexShrink={ 0 }
|
|
123
|
+
alignItems="center"
|
|
124
|
+
justifyContent="center"
|
|
125
|
+
width={ s.control }
|
|
126
|
+
height={ s.control }
|
|
127
|
+
borderRadius={ 9999 }
|
|
128
|
+
borderWidth={ 2 }
|
|
129
|
+
borderColor={ RESTING_BORDER }
|
|
130
|
+
transition="quicker"
|
|
131
|
+
focusStyle={{ outlineWidth: 2, outlineStyle: 'solid', outlineColor: OUTLINE, outlineOffset: 2 }}
|
|
150
132
|
>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
) }
|
|
157
|
-
/>
|
|
133
|
+
{ /* gui only mounts this when the item is the group's selected
|
|
134
|
+
value — the primitive's own headless state, not a prop this
|
|
135
|
+
file tracks — so the dot is the correct+sufficient "selected"
|
|
136
|
+
signal without needing the item's checked state here too. */ }
|
|
137
|
+
<GuiRadioGroup.Indicator unstyled borderRadius={ 9999 } width={ s.indicator } height={ s.indicator } backgroundColor={ INK }/>
|
|
158
138
|
</GuiRadioGroup.Item>
|
|
159
139
|
{ /* Hidden native input for form compatibility and ref forwarding */ }
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
140
|
+
<VisuallyHidden>
|
|
141
|
+
<input
|
|
142
|
+
ref={ ref }
|
|
143
|
+
type="radio"
|
|
144
|
+
value={ value }
|
|
145
|
+
disabled={ disabled }
|
|
146
|
+
tabIndex={ -1 }
|
|
147
|
+
aria-hidden
|
|
148
|
+
onChange={ NOOP }
|
|
149
|
+
{ ...inputProps }
|
|
150
|
+
/>
|
|
151
|
+
</VisuallyHidden>
|
|
171
152
|
{ children != null && (
|
|
172
|
-
<
|
|
153
|
+
<Text fontSize={ s.label }>{ children }</Text>
|
|
173
154
|
) }
|
|
174
|
-
</
|
|
155
|
+
</Label>
|
|
175
156
|
);
|
|
176
157
|
},
|
|
177
158
|
);
|
package/src/slider.tsx
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { Slider as GuiSlider } from '@hanzo/gui';
|
|
1
|
+
import { Slider as GuiSlider, Text, View, XStack, YStack } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
-
import { cn } from './utils';
|
|
5
|
-
|
|
6
4
|
export interface SliderProps {
|
|
7
5
|
readonly marks?: Array<number | { readonly value: number; readonly label: React.ReactNode }>;
|
|
8
6
|
readonly label?: React.ReactNode;
|
|
@@ -20,6 +18,12 @@ export interface SliderProps {
|
|
|
20
18
|
readonly className?: string;
|
|
21
19
|
}
|
|
22
20
|
|
|
21
|
+
const MUTED = 'var(--color-text-secondary)' as never;
|
|
22
|
+
const TRACK = 'var(--color-border-divider)' as never;
|
|
23
|
+
const ACTIVE = 'var(--color-link-primary)' as never;
|
|
24
|
+
const ACTIVE_HOVER = 'var(--color-link-primary-hover)' as never;
|
|
25
|
+
const WHITE = 'var(--color-white)' as never;
|
|
26
|
+
|
|
23
27
|
// gui's Slider: pointer, touch and keyboard from one implementation, with the
|
|
24
28
|
// drag handled as a pan gesture by the engine rather than by mouse events — so it
|
|
25
29
|
// works under a finger, not only under a cursor. `role=slider` and the arrow-key
|
|
@@ -64,30 +68,32 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
64
68
|
}, [ onValueCommit ]);
|
|
65
69
|
|
|
66
70
|
return (
|
|
67
|
-
<
|
|
71
|
+
<YStack className={ className } gap={ 4 }>
|
|
68
72
|
{ label && !showValue && (
|
|
69
|
-
<
|
|
73
|
+
<Text fontSize={ 14 } fontWeight="500" color={ MUTED }>
|
|
70
74
|
{ label }
|
|
71
|
-
</
|
|
75
|
+
</Text>
|
|
72
76
|
) }
|
|
73
77
|
{ label && showValue && (
|
|
74
|
-
<
|
|
75
|
-
<
|
|
78
|
+
<XStack alignItems="center" justifyContent="space-between">
|
|
79
|
+
<Text fontSize={ 14 } fontWeight="500" color={ MUTED }>
|
|
76
80
|
{ label }
|
|
77
|
-
</
|
|
78
|
-
<
|
|
81
|
+
</Text>
|
|
82
|
+
<Text fontSize={ 14 } color={ MUTED }>
|
|
79
83
|
{ (value ?? defaultValue ?? [])?.join(', ') }
|
|
80
|
-
</
|
|
81
|
-
</
|
|
84
|
+
</Text>
|
|
85
|
+
</XStack>
|
|
82
86
|
) }
|
|
83
87
|
|
|
84
88
|
<GuiSlider
|
|
85
89
|
ref={ ref as never }
|
|
86
90
|
unstyled
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
position="relative"
|
|
92
|
+
flexDirection="row"
|
|
93
|
+
width="100%"
|
|
94
|
+
userSelect="none"
|
|
95
|
+
alignItems="center"
|
|
96
|
+
marginBottom={ hasMarkLabel ? 24 : 0 }
|
|
91
97
|
value={ value }
|
|
92
98
|
defaultValue={ defaultValue }
|
|
93
99
|
min={ min }
|
|
@@ -99,14 +105,8 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
99
105
|
onValueChange={ handleValueChange }
|
|
100
106
|
onSlideEnd={ handleSlideEnd }
|
|
101
107
|
>
|
|
102
|
-
<GuiSlider.Track
|
|
103
|
-
unstyled
|
|
104
|
-
className="relative grow h-1.5 rounded-full bg-border-divider"
|
|
105
|
-
>
|
|
106
|
-
<GuiSlider.TrackActive
|
|
107
|
-
unstyled
|
|
108
|
-
className="absolute h-full rounded-full bg-link-primary"
|
|
109
|
-
/>
|
|
108
|
+
<GuiSlider.Track unstyled position="relative" flexGrow={ 1 } height={ 6 } borderRadius={ 9999 } backgroundColor={ TRACK }>
|
|
109
|
+
<GuiSlider.TrackActive unstyled position="absolute" height="100%" borderRadius={ 9999 } backgroundColor={ ACTIVE }/>
|
|
110
110
|
</GuiSlider.Track>
|
|
111
111
|
|
|
112
112
|
{ resolvedValue?.map((_, index) => (
|
|
@@ -114,20 +114,23 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
114
114
|
key={ index }
|
|
115
115
|
index={ index }
|
|
116
116
|
unstyled
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
width={ 20 }
|
|
118
|
+
height={ 20 }
|
|
119
|
+
borderRadius={ 9999 }
|
|
120
|
+
borderWidth={ 2 }
|
|
121
|
+
borderColor={ ACTIVE }
|
|
122
|
+
backgroundColor={ WHITE }
|
|
123
|
+
boxShadow="0 1px 2px 0 rgba(0,0,0,0.05)"
|
|
124
|
+
transition="quick"
|
|
125
|
+
hoverStyle={{ borderColor: ACTIVE_HOVER }}
|
|
126
|
+
focusStyle={{ outlineWidth: 2, outlineStyle: 'solid', outlineColor: ACTIVE, outlineOffset: 2 }}
|
|
127
|
+
disabledStyle={{ pointerEvents: 'none', opacity: 0.5 }}
|
|
125
128
|
/>
|
|
126
129
|
)) }
|
|
127
130
|
</GuiSlider>
|
|
128
131
|
|
|
129
132
|
<SliderMarks marks={ marks } min={ min } max={ max }/>
|
|
130
|
-
</
|
|
133
|
+
</YStack>
|
|
131
134
|
);
|
|
132
135
|
},
|
|
133
136
|
);
|
|
@@ -145,24 +148,20 @@ function SliderMarks(props: SliderMarksProps): React.ReactNode {
|
|
|
145
148
|
const range = max - min;
|
|
146
149
|
|
|
147
150
|
return (
|
|
148
|
-
<
|
|
151
|
+
<View position="relative" width="100%" height={ 16 }>
|
|
149
152
|
{ marks.map((mark, index) => {
|
|
150
153
|
const percent = ((mark.value - min) / range) * 100;
|
|
151
154
|
return (
|
|
152
|
-
<
|
|
153
|
-
|
|
154
|
-
className="absolute flex flex-col items-center -translate-x-1/2"
|
|
155
|
-
style={{ left: `${ percent }%` }}
|
|
156
|
-
>
|
|
157
|
-
<div className="h-1.5 w-0.5 bg-border-divider"/>
|
|
155
|
+
<View key={ index } position="absolute" left={ `${ percent }%` as never } alignItems="center">
|
|
156
|
+
<View width={ 2 } height={ 6 } backgroundColor={ TRACK }/>
|
|
158
157
|
{ mark.label != null && (
|
|
159
|
-
<
|
|
158
|
+
<Text marginTop={ 2 } fontSize={ 12 } color={ MUTED }>
|
|
160
159
|
{ mark.label }
|
|
161
|
-
</
|
|
160
|
+
</Text>
|
|
162
161
|
) }
|
|
163
|
-
</
|
|
162
|
+
</View>
|
|
164
163
|
);
|
|
165
164
|
}) }
|
|
166
|
-
</
|
|
165
|
+
</View>
|
|
167
166
|
);
|
|
168
167
|
}
|