@luxfi/ui 7.4.11 → 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.
- package/dist/alert.cjs +68 -46
- package/dist/alert.js +69 -46
- package/dist/avatar.cjs +49 -47
- package/dist/avatar.js +52 -49
- 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 +1053 -888
- package/dist/index.js +1054 -889
- package/dist/input-group.cjs +36 -19
- package/dist/input-group.js +37 -19
- 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/pin-input.cjs +30 -29
- package/dist/pin-input.js +30 -29
- 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-circle.cjs +40 -22
- package/dist/progress-circle.d.cts +5 -5
- package/dist/progress-circle.d.ts +5 -5
- package/dist/progress-circle.js +41 -22
- 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 -68
- package/dist/radio.d.cts +18 -18
- package/dist/radio.d.ts +18 -18
- package/dist/radio.js +36 -69
- package/dist/rating.cjs +15 -17
- package/dist/rating.js +15 -17
- package/dist/slider.cjs +33 -50
- package/dist/slider.js +34 -51
- package/dist/strategy-builder.cjs +194 -115
- package/dist/strategy-builder.js +194 -115
- package/dist/switch.cjs +48 -55
- package/dist/switch.js +49 -56
- package/dist/tag.cjs +115 -69
- package/dist/tag.js +116 -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 -38
- 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 +143 -114
- package/src/pin-input.tsx +37 -29
- package/src/pnl-diagram.tsx +36 -28
- package/src/progress-circle.tsx +52 -28
- package/src/progress.tsx +17 -21
- package/src/radio.tsx +56 -76
- package/src/rating.tsx +22 -20
- package/src/slider.tsx +43 -45
- package/src/strategy-builder.tsx +260 -162
- 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-circle.tsx
CHANGED
|
@@ -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,
|
|
11
|
-
sm: { size: 32, thickness: 5,
|
|
12
|
-
md: { size: 40, thickness: 6,
|
|
13
|
-
lg: { size: 48, thickness: 7,
|
|
14
|
-
xl: { size: 64, thickness: 8,
|
|
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
|
-
<
|
|
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
|
-
|
|
73
|
-
|
|
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
|
-
</
|
|
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
|
-
|
|
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 {
|
|
168
|
+
const { fontSize } = SIZE_MAP[sizeKey];
|
|
164
169
|
|
|
165
170
|
return (
|
|
166
|
-
<
|
|
167
|
-
ref={ ref }
|
|
171
|
+
<View
|
|
172
|
+
ref={ ref as never }
|
|
168
173
|
aria-live="polite"
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
</
|
|
201
|
+
{ ink(children ?? `${ Math.round(value ?? 0) }%`) }
|
|
202
|
+
</View>
|
|
179
203
|
);
|
|
180
204
|
});
|
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
|
|
|
@@ -83,7 +64,6 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
83
64
|
from its headless core, real Pressables on native. */ }
|
|
84
65
|
<GuiRadioGroup
|
|
85
66
|
ref={ ref as never }
|
|
86
|
-
unstyled
|
|
87
67
|
name={ name }
|
|
88
68
|
required={ required }
|
|
89
69
|
disabled={ disabled || readOnly }
|
|
@@ -91,11 +71,9 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
91
71
|
defaultValue={ defaultValue }
|
|
92
72
|
value={ value ?? undefined }
|
|
93
73
|
onValueChange={ handleValueChange }
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
className,
|
|
98
|
-
) }
|
|
74
|
+
display="flex"
|
|
75
|
+
flexDirection={ orientation === 'vertical' ? 'column' : 'row' }
|
|
76
|
+
gap={ orientation === 'vertical' ? 12 : 24 }
|
|
99
77
|
{ ...(rest as object) }
|
|
100
78
|
>
|
|
101
79
|
{ children }
|
|
@@ -119,59 +97,61 @@ export interface RadioProps extends Omit<React.ComponentPropsWithoutRef<'label'>
|
|
|
119
97
|
|
|
120
98
|
const RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
121
99
|
function Radio(props, ref) {
|
|
122
|
-
const { children, inputProps, rootRef, value, disabled,
|
|
100
|
+
const { children, inputProps, rootRef, value, disabled, ...rest } = props;
|
|
123
101
|
const size = React.useContext(RadioSizeContext);
|
|
124
|
-
const
|
|
102
|
+
const s = SIZE[size];
|
|
125
103
|
|
|
126
104
|
return (
|
|
127
|
-
<
|
|
128
|
-
ref={ rootRef }
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
105
|
+
<Label
|
|
106
|
+
ref={ rootRef as never }
|
|
107
|
+
unstyled
|
|
108
|
+
flexDirection="row"
|
|
109
|
+
alignItems="center"
|
|
110
|
+
gap={ s.rootGap }
|
|
111
|
+
cursor={ disabled ? 'not-allowed' : 'pointer' }
|
|
112
|
+
userSelect="none"
|
|
113
|
+
opacity={ disabled ? 0.5 : 1 }
|
|
135
114
|
data-disabled={ disabled || undefined }
|
|
136
|
-
{ ...rest }
|
|
115
|
+
{ ...(rest as object) }
|
|
137
116
|
>
|
|
138
117
|
<GuiRadioGroup.Item
|
|
139
118
|
unstyled
|
|
140
119
|
value={ value }
|
|
141
120
|
disabled={ disabled }
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
121
|
+
flexShrink={ 0 }
|
|
122
|
+
alignItems="center"
|
|
123
|
+
justifyContent="center"
|
|
124
|
+
width={ s.control }
|
|
125
|
+
height={ s.control }
|
|
126
|
+
borderRadius={ 9999 }
|
|
127
|
+
borderWidth={ 2 }
|
|
128
|
+
borderColor={ RESTING_BORDER }
|
|
129
|
+
transition="quicker"
|
|
130
|
+
focusStyle={{ outlineWidth: 2, outlineStyle: 'solid', outlineColor: OUTLINE, outlineOffset: 2 }}
|
|
150
131
|
>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
) }
|
|
157
|
-
/>
|
|
132
|
+
{ /* gui only mounts this when the item is the group's selected
|
|
133
|
+
value — the primitive's own headless state, not a prop this
|
|
134
|
+
file tracks — so the dot is the correct+sufficient "selected"
|
|
135
|
+
signal without needing the item's checked state here too. */ }
|
|
136
|
+
<GuiRadioGroup.Indicator unstyled borderRadius={ 9999 } width={ s.indicator } height={ s.indicator } backgroundColor={ INK }/>
|
|
158
137
|
</GuiRadioGroup.Item>
|
|
159
138
|
{ /* Hidden native input for form compatibility and ref forwarding */ }
|
|
160
|
-
<
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
139
|
+
<VisuallyHidden>
|
|
140
|
+
<input
|
|
141
|
+
ref={ ref }
|
|
142
|
+
type="radio"
|
|
143
|
+
value={ value }
|
|
144
|
+
disabled={ disabled }
|
|
145
|
+
tabIndex={ -1 }
|
|
146
|
+
aria-hidden
|
|
147
|
+
onChange={ NOOP }
|
|
148
|
+
{ ...inputProps }
|
|
149
|
+
/>
|
|
150
|
+
</VisuallyHidden>
|
|
171
151
|
{ children != null && (
|
|
172
|
-
<
|
|
152
|
+
<Text fontSize={ s.label }>{ children }</Text>
|
|
173
153
|
) }
|
|
174
|
-
</
|
|
154
|
+
</Label>
|
|
175
155
|
);
|
|
176
156
|
},
|
|
177
157
|
);
|