@luxfi/ui 7.4.10 → 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/accordion.cjs +107 -166
- package/dist/accordion.d.cts +18 -51
- package/dist/accordion.d.ts +18 -51
- package/dist/accordion.js +109 -166
- 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 +196 -386
- package/dist/chrome.js +198 -386
- package/dist/close-button.cjs +3 -7
- package/dist/close-button.js +3 -7
- package/dist/collapsible.cjs +35 -26
- package/dist/collapsible.d.cts +2 -1
- package/dist/collapsible.d.ts +2 -1
- package/dist/collapsible.js +34 -24
- package/dist/dialog.cjs +3 -7
- package/dist/dialog.d.cts +1 -1
- package/dist/dialog.d.ts +1 -1
- package/dist/dialog.js +3 -7
- package/dist/drawer.cjs +95 -122
- package/dist/drawer.d.cts +43 -10
- package/dist/drawer.d.ts +43 -10
- package/dist/drawer.js +97 -122
- 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/field.cjs +60 -109
- package/dist/field.d.cts +0 -1
- package/dist/field.d.ts +0 -1
- package/dist/field.js +61 -109
- package/dist/greeks-display.cjs +48 -39
- package/dist/greeks-display.js +48 -39
- package/dist/heading.cjs +10 -17
- package/dist/heading.d.cts +0 -2
- package/dist/heading.d.ts +0 -2
- package/dist/heading.js +10 -17
- package/dist/icon-button.cjs +82 -82
- package/dist/icon-button.d.cts +16 -12
- package/dist/icon-button.d.ts +16 -12
- package/dist/icon-button.js +82 -82
- package/dist/index.cjs +1812 -2105
- package/dist/index.d.cts +1 -5
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1814 -2100
- package/dist/input-group.cjs +11 -12
- package/dist/input-group.js +11 -12
- package/dist/input.cjs +27 -27
- package/dist/input.d.cts +21 -1
- package/dist/input.d.ts +21 -1
- package/dist/input.js +25 -28
- package/dist/menu.cjs +160 -271
- package/dist/menu.d.cts +41 -57
- package/dist/menu.d.ts +41 -57
- package/dist/menu.js +161 -251
- 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 +4 -8
- package/dist/popover.js +4 -8
- 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/select.cjs +40 -39
- package/dist/select.d.cts +35 -4
- package/dist/select.d.ts +35 -4
- package/dist/select.js +40 -38
- package/dist/separator.cjs +8 -33
- package/dist/separator.js +8 -33
- 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/tabs.cjs +108 -197
- package/dist/tabs.d.cts +16 -60
- package/dist/tabs.d.ts +16 -60
- package/dist/tabs.js +109 -196
- package/dist/tag.cjs +112 -69
- package/dist/tag.js +113 -69
- package/dist/textarea.cjs +56 -27
- package/dist/textarea.js +57 -28
- package/dist/toaster.cjs +97 -79
- package/dist/toaster.d.cts +7 -8
- package/dist/toaster.d.ts +7 -8
- package/dist/toaster.js +78 -80
- package/dist/tooltip.cjs +28 -17
- package/dist/tooltip.js +30 -18
- package/package.json +2 -16
- package/src/accordion.tsx +173 -227
- 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/collapsible.tsx +55 -52
- package/src/drawer.tsx +123 -115
- package/src/empty-state.tsx +21 -17
- package/src/expiration-selector.tsx +84 -66
- package/src/field.tsx +68 -124
- package/src/greeks-display.tsx +59 -51
- package/src/heading.tsx +22 -26
- package/src/icon-button.tsx +134 -141
- package/src/ink.tsx +37 -0
- package/src/input-group.tsx +59 -36
- package/src/input.tsx +41 -32
- package/src/menu.tsx +224 -397
- 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/popover.tsx +1 -1
- package/src/progress.tsx +17 -21
- package/src/radio.tsx +56 -75
- package/src/select.tsx +80 -59
- package/src/separator.tsx +13 -34
- package/src/slider.tsx +43 -44
- package/src/strategy-builder.tsx +245 -156
- package/src/switch.tsx +63 -64
- package/src/tabs.tsx +166 -296
- package/src/tag.tsx +89 -51
- package/src/textarea.tsx +19 -29
- package/src/toaster.tsx +118 -86
- package/src/tooltip.tsx +21 -13
- package/tokens.css +79 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { Text, XStack, YStack } from '@hanzo/gui';
|
|
3
4
|
import * as React from 'react';
|
|
4
5
|
|
|
5
|
-
import { cn } from './utils';
|
|
6
|
-
|
|
7
6
|
// ---------------------------------------------------------------------------
|
|
8
7
|
// Types
|
|
9
8
|
// ---------------------------------------------------------------------------
|
|
@@ -48,31 +47,62 @@ interface ExpirationPillProps {
|
|
|
48
47
|
readonly onClick: () => void;
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
// Resting and selected are the same two states every selectable pill in this
|
|
51
|
+
// package uses (tabs, menu rows): a solid fill when selected, a bordered card
|
|
52
|
+
// otherwise. The date label sets no colour of its own — it inherits the
|
|
53
|
+
// button's, so `hoverStyle` here is what brightens it on hover, in one place.
|
|
54
|
+
const SHAPE = {
|
|
55
|
+
selected: {
|
|
56
|
+
borderColor: 'transparent',
|
|
57
|
+
backgroundColor: 'var(--color-selected-control-bg)',
|
|
58
|
+
color: 'var(--color-selected-control-text)',
|
|
59
|
+
},
|
|
60
|
+
resting: {
|
|
61
|
+
borderColor: 'var(--color-border-divider)',
|
|
62
|
+
backgroundColor: 'var(--card)',
|
|
63
|
+
color: 'var(--color-text-secondary)',
|
|
64
|
+
hoverStyle: { borderColor: 'var(--color-hover)', color: 'var(--color-hover)' },
|
|
65
|
+
},
|
|
66
|
+
} as const;
|
|
67
|
+
|
|
51
68
|
function ExpirationPill({ exp, isSelected, onClick }: ExpirationPillProps) {
|
|
69
|
+
const shape = isSelected ? SHAPE.selected : SHAPE.resting;
|
|
70
|
+
|
|
71
|
+
// Near-expiry is a notice, and a notice in Lux is the loudest neutral — it
|
|
72
|
+
// only shows while resting; a selected pill's own fill already carries the
|
|
73
|
+
// emphasis.
|
|
74
|
+
const dteColor = exp.dte <= 7 && !isSelected
|
|
75
|
+
? 'var(--color-status-warn)'
|
|
76
|
+
: isSelected ? 'var(--color-selected-control-text)' : 'var(--muted-foreground)';
|
|
77
|
+
|
|
52
78
|
return (
|
|
53
|
-
<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
79
|
+
<YStack
|
|
80
|
+
unstyled
|
|
81
|
+
render={ <button type="button"/> }
|
|
82
|
+
onClick={ onClick }
|
|
83
|
+
alignItems="center"
|
|
84
|
+
gap={ 2 }
|
|
85
|
+
borderRadius={ 6 }
|
|
86
|
+
paddingHorizontal={ 12 }
|
|
87
|
+
paddingVertical={ 6 }
|
|
88
|
+
flexShrink={ 0 }
|
|
89
|
+
borderWidth={ 1 }
|
|
90
|
+
borderStyle="solid"
|
|
91
|
+
cursor="pointer"
|
|
92
|
+
{ ...(shape as object) }
|
|
63
93
|
>
|
|
64
|
-
<
|
|
65
|
-
{formatExpDate(exp.date)}
|
|
66
|
-
</
|
|
67
|
-
<
|
|
68
|
-
'
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{exp.dte}d
|
|
74
|
-
</
|
|
75
|
-
</
|
|
94
|
+
<Text unstyled fontSize={ 12 } fontWeight="500" style={ { whiteSpace: 'nowrap' } as never }>
|
|
95
|
+
{ formatExpDate(exp.date) }
|
|
96
|
+
</Text>
|
|
97
|
+
<Text
|
|
98
|
+
fontVariant={ ['tabular-nums'] as never }
|
|
99
|
+
style={ { fontFamily: 'monospace' } as never }
|
|
100
|
+
fontSize={ 10 }
|
|
101
|
+
color={ dteColor as never }
|
|
102
|
+
>
|
|
103
|
+
{ exp.dte }d
|
|
104
|
+
</Text>
|
|
105
|
+
</YStack>
|
|
76
106
|
);
|
|
77
107
|
}
|
|
78
108
|
|
|
@@ -111,54 +141,42 @@ export const ExpirationSelector = React.forwardRef<HTMLDivElement, ExpirationSel
|
|
|
111
141
|
})).filter((g) => g.items.length > 0);
|
|
112
142
|
|
|
113
143
|
return (
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
{
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
{group.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
/>
|
|
144
|
+
<YStack ref={ ref as never } gap={ 8 } className={ className } { ...(rest as object) }>
|
|
145
|
+
{ grouped.map((group) => (
|
|
146
|
+
<YStack key={ group.type } gap={ 4 }>
|
|
147
|
+
<Text
|
|
148
|
+
fontSize={ 10 }
|
|
149
|
+
fontWeight="600"
|
|
150
|
+
textTransform="uppercase"
|
|
151
|
+
letterSpacing={ 0.5 }
|
|
152
|
+
paddingHorizontal={ 4 }
|
|
153
|
+
color={ 'var(--color-text-secondary)' as never }
|
|
154
|
+
>
|
|
155
|
+
{ group.label }
|
|
156
|
+
</Text>
|
|
157
|
+
<XStack gap={ 6 } overflowX="auto" overflowY="hidden" className="lux-no-scrollbar">
|
|
158
|
+
{ group.items.map((exp) => (
|
|
159
|
+
<div key={ exp.date } data-selected={ exp.date === selected || undefined }>
|
|
160
|
+
<ExpirationPill exp={ exp } isSelected={ exp.date === selected } onClick={ () => onSelect(exp.date) }/>
|
|
132
161
|
</div>
|
|
133
|
-
))}
|
|
134
|
-
</
|
|
135
|
-
</
|
|
136
|
-
))}
|
|
137
|
-
</
|
|
162
|
+
)) }
|
|
163
|
+
</XStack>
|
|
164
|
+
</YStack>
|
|
165
|
+
)) }
|
|
166
|
+
</YStack>
|
|
138
167
|
);
|
|
139
168
|
}
|
|
140
169
|
|
|
141
170
|
return (
|
|
142
|
-
<
|
|
143
|
-
ref={
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
<div
|
|
148
|
-
ref={scrollRef}
|
|
149
|
-
className="flex gap-1.5 overflow-x-auto no-scrollbar"
|
|
150
|
-
>
|
|
151
|
-
{expirations.map((exp) => (
|
|
152
|
-
<div key={exp.date} data-selected={exp.date === selected || undefined}>
|
|
153
|
-
<ExpirationPill
|
|
154
|
-
exp={exp}
|
|
155
|
-
isSelected={exp.date === selected}
|
|
156
|
-
onClick={() => onSelect(exp.date)}
|
|
157
|
-
/>
|
|
171
|
+
<XStack ref={ ref as never } alignItems="center" gap={ 4 } className={ className } { ...(rest as object) }>
|
|
172
|
+
<XStack ref={ scrollRef as never } gap={ 6 } overflowX="auto" overflowY="hidden" className="lux-no-scrollbar">
|
|
173
|
+
{ expirations.map((exp) => (
|
|
174
|
+
<div key={ exp.date } data-selected={ exp.date === selected || undefined }>
|
|
175
|
+
<ExpirationPill exp={ exp } isSelected={ exp.date === selected } onClick={ () => onSelect(exp.date) }/>
|
|
158
176
|
</div>
|
|
159
|
-
))}
|
|
160
|
-
</
|
|
161
|
-
</
|
|
177
|
+
)) }
|
|
178
|
+
</XStack>
|
|
179
|
+
</XStack>
|
|
162
180
|
);
|
|
163
181
|
},
|
|
164
182
|
);
|
package/src/field.tsx
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Label, Text, YStack } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
-
import { cn } from './utils';
|
|
5
|
-
|
|
6
4
|
import type { InputProps } from './input';
|
|
7
5
|
import type { InputGroupProps } from './input-group';
|
|
8
6
|
|
|
9
|
-
//
|
|
7
|
+
// A labelled form control: label, the control itself, and whichever of helper /
|
|
8
|
+
// error / optional text applies.
|
|
9
|
+
//
|
|
10
|
+
// The label is gui's `Label` — a real `<label htmlFor>`, which is the entire
|
|
11
|
+
// reason Radix's was imported. Everything else was Tailwind, and a good part of
|
|
12
|
+
// it was dead: the colours were written `text-[var(--color-x,theme(colors.gray.400))]`,
|
|
13
|
+
// and `theme()` is a Tailwind 3 function that v4 does not resolve inside an
|
|
14
|
+
// arbitrary value. So the fallback half of every one of those declarations was
|
|
15
|
+
// discarded and the class contributed nothing whenever the custom property was
|
|
16
|
+
// unset. The tokens are referenced directly now, and they are all defined.
|
|
17
|
+
|
|
10
18
|
const space = String.fromCharCode(32);
|
|
11
19
|
|
|
12
20
|
function getComponentDisplayName(type: string | React.JSXElementConstructor<unknown>): string | undefined {
|
|
@@ -27,75 +35,62 @@ export interface FieldProps {
|
|
|
27
35
|
readonly readOnly?: boolean;
|
|
28
36
|
readonly invalid?: boolean;
|
|
29
37
|
readonly floating?: boolean;
|
|
30
|
-
readonly bgColor?: string | Record<string, string>;
|
|
31
38
|
readonly id?: string;
|
|
32
39
|
readonly className?: string;
|
|
33
40
|
readonly style?: React.CSSProperties;
|
|
34
41
|
}
|
|
35
42
|
|
|
43
|
+
const ERROR = 'var(--color-text-error)' as never;
|
|
44
|
+
const MUTED = 'var(--color-text-secondary)' as never;
|
|
45
|
+
|
|
46
|
+
/** The `*`, the "(optional)", and the inline error that ride alongside a label. */
|
|
47
|
+
const Marks = ({ required, optionalText, errorText }: Pick<FieldProps, 'required' | 'optionalText' | 'errorText'>) => (
|
|
48
|
+
<>
|
|
49
|
+
{ required && <Text color={ ERROR } aria-hidden>*</Text> }
|
|
50
|
+
{ !required && optionalText && <Text fontSize={ 14 } color={ MUTED }>{ optionalText }</Text> }
|
|
51
|
+
{ errorText && <Text fontSize={ 14 } color={ ERROR }>-{ space }{ errorText }</Text> }
|
|
52
|
+
</>
|
|
53
|
+
);
|
|
54
|
+
|
|
36
55
|
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
37
56
|
function Field(props, ref) {
|
|
38
57
|
const {
|
|
39
|
-
label,
|
|
40
|
-
|
|
41
|
-
helperText,
|
|
42
|
-
errorText,
|
|
43
|
-
optionalText,
|
|
44
|
-
size,
|
|
45
|
-
required,
|
|
46
|
-
disabled,
|
|
47
|
-
readOnly,
|
|
48
|
-
invalid,
|
|
49
|
-
floating,
|
|
50
|
-
id,
|
|
51
|
-
className,
|
|
52
|
-
style,
|
|
58
|
+
label, children, helperText, errorText, optionalText, size,
|
|
59
|
+
required, disabled, readOnly, invalid, floating, id, className, style,
|
|
53
60
|
} = props;
|
|
54
61
|
|
|
62
|
+
const flags = {
|
|
63
|
+
'data-disabled': disabled || undefined,
|
|
64
|
+
'data-readonly': readOnly || undefined,
|
|
65
|
+
'data-invalid': invalid || undefined,
|
|
66
|
+
};
|
|
67
|
+
|
|
55
68
|
// A floating field cannot be without a label.
|
|
56
69
|
if (floating && label) {
|
|
57
|
-
const injectedProps = {
|
|
58
|
-
className: 'peer',
|
|
59
|
-
placeholder: ' ',
|
|
60
|
-
size,
|
|
61
|
-
floating,
|
|
62
|
-
disabled,
|
|
63
|
-
readOnly,
|
|
64
|
-
};
|
|
70
|
+
const injectedProps = { className: 'lux-field-control', placeholder: ' ', size, floating, disabled, readOnly };
|
|
65
71
|
|
|
66
72
|
const labelElement = (
|
|
67
|
-
<
|
|
68
|
-
className={ cn(
|
|
69
|
-
'absolute left-3 top-1/2 -translate-y-1/2',
|
|
70
|
-
'text-[var(--color-input-placeholder,theme(colors.gray.400))]',
|
|
71
|
-
'pointer-events-none select-none',
|
|
72
|
-
'origin-top-left transition-all duration-150',
|
|
73
|
-
'peer-focus:top-1 peer-focus:translate-y-0 peer-focus:scale-75',
|
|
74
|
-
'peer-[:not(:placeholder-shown)]:top-1 peer-[:not(:placeholder-shown)]:translate-y-0 peer-[:not(:placeholder-shown)]:scale-75',
|
|
75
|
-
) }
|
|
73
|
+
<Label
|
|
76
74
|
htmlFor={ id }
|
|
75
|
+
position="absolute"
|
|
76
|
+
left={ 12 }
|
|
77
|
+
top="50%"
|
|
78
|
+
pointerEvents="none"
|
|
79
|
+
userSelect="none"
|
|
80
|
+
flexDirection="row"
|
|
81
|
+
gap={ 4 }
|
|
82
|
+
color={ 'var(--color-input-placeholder)' as never }
|
|
83
|
+
// The float itself stays in CSS: it keys off `:placeholder-shown` and
|
|
84
|
+
// `:focus` of a SIBLING, which is a selector, not a prop.
|
|
85
|
+
className="lux-field-float"
|
|
77
86
|
>
|
|
78
87
|
{ label }
|
|
79
|
-
{ required
|
|
80
|
-
|
|
81
|
-
) }
|
|
82
|
-
{ !required && optionalText && (
|
|
83
|
-
<span className="ml-1 text-[var(--color-text-secondary,theme(colors.gray.400))] text-sm">
|
|
84
|
-
{ optionalText }
|
|
85
|
-
</span>
|
|
86
|
-
) }
|
|
87
|
-
{ errorText && (
|
|
88
|
-
<span className="ml-0.5 text-[var(--color-fg-error,theme(colors.red.500))] text-sm">
|
|
89
|
-
-{ space }{ errorText }
|
|
90
|
-
</span>
|
|
91
|
-
) }
|
|
92
|
-
</LabelPrimitive.Root>
|
|
88
|
+
<Marks required={ required } optionalText={ optionalText } errorText={ errorText }/>
|
|
89
|
+
</Label>
|
|
93
90
|
);
|
|
94
91
|
|
|
95
92
|
const helperTextElement = helperText ? (
|
|
96
|
-
<
|
|
97
|
-
{ helperText }
|
|
98
|
-
</p>
|
|
93
|
+
<Text marginTop={ 6 } fontSize={ 12 } color={ MUTED }>{ helperText }</Text>
|
|
99
94
|
) : null;
|
|
100
95
|
|
|
101
96
|
const child = React.Children.only<React.ReactElement<InputProps | InputGroupProps>>(children);
|
|
@@ -107,96 +102,45 @@ export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
|
107
102
|
injectedProps,
|
|
108
103
|
);
|
|
109
104
|
|
|
110
|
-
const groupInputElement = React.cloneElement(child,
|
|
111
|
-
{},
|
|
112
|
-
inputElement,
|
|
113
|
-
labelElement,
|
|
114
|
-
);
|
|
115
|
-
|
|
116
105
|
return (
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
id={ id }
|
|
120
|
-
className={ cn('relative w-full', className) }
|
|
121
|
-
style={ style }
|
|
122
|
-
data-disabled={ disabled || undefined }
|
|
123
|
-
data-readonly={ readOnly || undefined }
|
|
124
|
-
data-invalid={ invalid || undefined }
|
|
125
|
-
>
|
|
126
|
-
{ groupInputElement }
|
|
106
|
+
<YStack ref={ ref as never } id={ id } position="relative" width="100%" className={ className } style={ style } { ...flags }>
|
|
107
|
+
{ React.cloneElement(child, {}, inputElement, labelElement) }
|
|
127
108
|
{ helperTextElement }
|
|
128
|
-
</
|
|
109
|
+
</YStack>
|
|
129
110
|
);
|
|
130
111
|
}
|
|
131
112
|
|
|
132
|
-
const inputElement = React.cloneElement(child, injectedProps);
|
|
133
|
-
|
|
134
113
|
return (
|
|
135
|
-
<
|
|
136
|
-
|
|
137
|
-
id={ id }
|
|
138
|
-
className={ cn('relative w-full', className) }
|
|
139
|
-
style={ style }
|
|
140
|
-
data-disabled={ disabled || undefined }
|
|
141
|
-
data-readonly={ readOnly || undefined }
|
|
142
|
-
data-invalid={ invalid || undefined }
|
|
143
|
-
>
|
|
144
|
-
{ inputElement }
|
|
114
|
+
<YStack ref={ ref as never } id={ id } position="relative" width="100%" className={ className } style={ style } { ...flags }>
|
|
115
|
+
{ React.cloneElement(child, injectedProps) }
|
|
145
116
|
{ labelElement }
|
|
146
117
|
{ helperTextElement }
|
|
147
|
-
</
|
|
118
|
+
</YStack>
|
|
148
119
|
);
|
|
149
120
|
}
|
|
150
121
|
|
|
151
|
-
// Non-floating: standard field layout
|
|
152
|
-
const injectedProps = {
|
|
153
|
-
size,
|
|
154
|
-
};
|
|
155
122
|
const child = React.Children.only<React.ReactElement<InputProps | InputGroupProps>>(children);
|
|
156
|
-
const clonedChild = React.cloneElement(child, injectedProps);
|
|
157
123
|
|
|
158
124
|
return (
|
|
159
|
-
<
|
|
160
|
-
ref={ ref }
|
|
161
|
-
id={ id }
|
|
162
|
-
className={ cn('flex flex-col gap-1', className) }
|
|
163
|
-
style={ style }
|
|
164
|
-
data-disabled={ disabled || undefined }
|
|
165
|
-
data-readonly={ readOnly || undefined }
|
|
166
|
-
data-invalid={ invalid || undefined }
|
|
167
|
-
>
|
|
125
|
+
<YStack ref={ ref as never } id={ id } gap={ 4 } className={ className } style={ style } { ...flags }>
|
|
168
126
|
{ label && (
|
|
169
|
-
<
|
|
170
|
-
className={ cn(
|
|
171
|
-
'text-sm font-medium text-[var(--color-field-label,theme(colors.gray.700))]',
|
|
172
|
-
'dark:text-[var(--color-field-label,theme(colors.gray.300))]',
|
|
173
|
-
disabled && 'opacity-40',
|
|
174
|
-
) }
|
|
127
|
+
<Label
|
|
175
128
|
htmlFor={ id }
|
|
129
|
+
flexDirection="row"
|
|
130
|
+
gap={ 4 }
|
|
131
|
+
fontSize={ 14 }
|
|
132
|
+
fontWeight="500"
|
|
133
|
+
color={ 'var(--color-text-primary)' as never }
|
|
134
|
+
opacity={ disabled ? 0.4 : 1 }
|
|
176
135
|
>
|
|
177
136
|
{ label }
|
|
178
|
-
{ required
|
|
179
|
-
|
|
180
|
-
) }
|
|
181
|
-
{ !required && optionalText && (
|
|
182
|
-
<span className="ml-1 text-[var(--color-text-secondary,theme(colors.gray.400))] text-sm font-normal">
|
|
183
|
-
{ optionalText }
|
|
184
|
-
</span>
|
|
185
|
-
) }
|
|
186
|
-
</LabelPrimitive.Root>
|
|
187
|
-
) }
|
|
188
|
-
{ clonedChild }
|
|
189
|
-
{ helperText && (
|
|
190
|
-
<p className="text-xs text-[var(--color-text-secondary,theme(colors.gray.400))]">
|
|
191
|
-
{ helperText }
|
|
192
|
-
</p>
|
|
193
|
-
) }
|
|
194
|
-
{ errorText && (
|
|
195
|
-
<p className="text-xs text-[var(--color-fg-error,theme(colors.red.500))]">
|
|
196
|
-
{ errorText }
|
|
197
|
-
</p>
|
|
137
|
+
<Marks required={ required } optionalText={ optionalText }/>
|
|
138
|
+
</Label>
|
|
198
139
|
) }
|
|
199
|
-
|
|
140
|
+
{ React.cloneElement(child, { size }) }
|
|
141
|
+
{ helperText && <Text fontSize={ 12 } color={ MUTED }>{ helperText }</Text> }
|
|
142
|
+
{ errorText && <Text fontSize={ 12 } color={ ERROR }>{ errorText }</Text> }
|
|
143
|
+
</YStack>
|
|
200
144
|
);
|
|
201
145
|
},
|
|
202
146
|
);
|
package/src/greeks-display.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
|
// ---------------------------------------------------------------------------
|
|
8
7
|
// Types
|
|
9
8
|
// ---------------------------------------------------------------------------
|
|
@@ -36,12 +35,12 @@ interface GreekDef {
|
|
|
36
35
|
// on one achromatic ramp (tokens.css --chart-*, plus --foreground for the sixth),
|
|
37
36
|
// so the panel obeys the brand atom and reskins with the host.
|
|
38
37
|
const GREEKS: GreekDef[] = [
|
|
39
|
-
{ key: 'delta', label: 'Delta', symbol: '
|
|
40
|
-
{ key: 'gamma', label: 'Gamma', symbol: '
|
|
41
|
-
{ key: 'theta', label: 'Theta', symbol: '
|
|
42
|
-
{ key: 'vega', label: 'Vega', symbol: '
|
|
43
|
-
{ key: 'rho', label: 'Rho', symbol: '
|
|
44
|
-
{ key: 'iv', label: 'IV', symbol: '
|
|
38
|
+
{ key: 'delta', label: 'Delta', symbol: 'Δ', color: 'var(--foreground)', barColor: 'var(--foreground)', decimals: 4, maxMagnitude: 1 },
|
|
39
|
+
{ key: 'gamma', label: 'Gamma', symbol: 'Γ', color: 'var(--chart-1)', barColor: 'var(--chart-1)', decimals: 4, maxMagnitude: 0.1 },
|
|
40
|
+
{ key: 'theta', label: 'Theta', symbol: 'Θ', color: 'var(--chart-4)', barColor: 'var(--chart-4)', decimals: 4, maxMagnitude: 1 },
|
|
41
|
+
{ key: 'vega', label: 'Vega', symbol: 'ν', color: 'var(--chart-2)', barColor: 'var(--chart-2)', decimals: 4, maxMagnitude: 1 },
|
|
42
|
+
{ key: 'rho', label: 'Rho', symbol: 'ρ', color: 'var(--chart-3)', barColor: 'var(--chart-3)', decimals: 4, maxMagnitude: 1 },
|
|
43
|
+
{ key: 'iv', label: 'IV', symbol: 'σ', color: 'var(--chart-5)', barColor: 'var(--chart-5)', decimals: 1, maxMagnitude: 100 },
|
|
45
44
|
];
|
|
46
45
|
|
|
47
46
|
// ---------------------------------------------------------------------------
|
|
@@ -61,35 +60,45 @@ function GreekItem({ def, value, compact }: GreekItemProps) {
|
|
|
61
60
|
: value.toFixed(def.decimals);
|
|
62
61
|
|
|
63
62
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
<XStack alignItems="center" gap={ 6 } minWidth={ compact ? 0 : 100 }>
|
|
64
|
+
<Text fontSize={ 10 } fontWeight="600" flexShrink={ 0 } color={ def.color as never }>
|
|
65
|
+
{ def.symbol }
|
|
66
|
+
</Text>
|
|
67
|
+
{ !compact && (
|
|
68
|
+
<Text fontSize={ 10 } flexShrink={ 0 } width={ 34 } color={ 'var(--color-text-secondary)' as never }>
|
|
69
|
+
{ def.label }
|
|
70
|
+
</Text>
|
|
71
|
+
) }
|
|
72
|
+
<Text
|
|
73
|
+
fontSize={ 12 }
|
|
74
|
+
flexShrink={ 0 }
|
|
75
|
+
style={ { fontFamily: 'monospace' } as never }
|
|
76
|
+
fontVariant={ ['tabular-nums'] as never }
|
|
77
|
+
color={ (value < 0 ? 'var(--color-status-bad)' : 'var(--color-text-primary)') as never }
|
|
78
|
+
>
|
|
79
|
+
{ displayValue }
|
|
80
|
+
</Text>
|
|
81
|
+
{ !compact && (
|
|
82
|
+
<View
|
|
83
|
+
flex={ 1 }
|
|
84
|
+
height={ 4 }
|
|
85
|
+
borderRadius={ 9999 }
|
|
86
|
+
minWidth={ 24 }
|
|
87
|
+
maxWidth={ 48 }
|
|
88
|
+
overflow="hidden"
|
|
89
|
+
backgroundColor={ 'var(--color-progress-track)' as never }
|
|
90
|
+
>
|
|
91
|
+
<View
|
|
92
|
+
height="100%"
|
|
93
|
+
borderRadius={ 9999 }
|
|
94
|
+
transition="quick"
|
|
95
|
+
width={ `${ magnitude * 100 }%` as never }
|
|
96
|
+
opacity={ 0.7 }
|
|
97
|
+
backgroundColor={ def.barColor as never }
|
|
89
98
|
/>
|
|
90
|
-
</
|
|
91
|
-
)}
|
|
92
|
-
</
|
|
99
|
+
</View>
|
|
100
|
+
) }
|
|
101
|
+
</XStack>
|
|
93
102
|
);
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -118,24 +127,23 @@ export const GreeksDisplay = React.forwardRef<HTMLDivElement, GreeksDisplayProps
|
|
|
118
127
|
const activeGreeks = GREEKS.filter((g) => values[g.key] !== undefined);
|
|
119
128
|
|
|
120
129
|
return (
|
|
121
|
-
<
|
|
122
|
-
ref={ref}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)}
|
|
128
|
-
{...rest}
|
|
130
|
+
<XStack
|
|
131
|
+
ref={ ref as never }
|
|
132
|
+
flexWrap="wrap"
|
|
133
|
+
alignItems="center"
|
|
134
|
+
gap={ compact ? 12 : 16 }
|
|
135
|
+
className={ className }
|
|
136
|
+
{ ...(rest as object) }
|
|
129
137
|
>
|
|
130
|
-
{activeGreeks.map((g) => (
|
|
138
|
+
{ activeGreeks.map((g) => (
|
|
131
139
|
<GreekItem
|
|
132
|
-
key={g.key}
|
|
133
|
-
def={g}
|
|
134
|
-
value={values[g.key] as number}
|
|
135
|
-
compact={compact}
|
|
140
|
+
key={ g.key }
|
|
141
|
+
def={ g }
|
|
142
|
+
value={ values[g.key] as number }
|
|
143
|
+
compact={ compact }
|
|
136
144
|
/>
|
|
137
|
-
))}
|
|
138
|
-
</
|
|
145
|
+
)) }
|
|
146
|
+
</XStack>
|
|
139
147
|
);
|
|
140
148
|
},
|
|
141
149
|
);
|
package/src/heading.tsx
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
1
|
import { H1, H2, H3 } from '@hanzo/gui';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// gui's heading primitives — real h1/h2/h3 on web, sized Text on native, both
|
|
5
|
+
// inheriting the theme's heading font.
|
|
6
|
+
//
|
|
7
|
+
// The type ramp is props rather than classes, so it survives on native, where a
|
|
8
|
+
// className means nothing. `$lg` is min-width 1024 in the v5 media config — the
|
|
9
|
+
// same breakpoint Tailwind's `lg:` means, so the ramp is unchanged.
|
|
10
|
+
//
|
|
11
|
+
// It is `$lg`, not `$gtLg`. The `gt*` names belong to the v4 media config and
|
|
12
|
+
// this package builds on v5, whose keys are plain mobile-first min-widths. gui
|
|
13
|
+
// does not consume a media prop it has no key for; it forwards it, and React
|
|
14
|
+
// reports it as an unknown DOM attribute. A wrong breakpoint name is not a
|
|
15
|
+
// smaller heading — it is no responsive rule at all.
|
|
5
16
|
|
|
6
17
|
export interface HeadingProps extends React.ComponentPropsWithoutRef<'h1'> {
|
|
7
18
|
level?: '1' | '2' | '3';
|
|
8
|
-
// Legacy Chakra style-prop shims
|
|
9
|
-
mb?: number | string;
|
|
10
|
-
size?: string;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
// inheriting the theme's heading font. The Lux type ramp stays in the classes.
|
|
15
|
-
const LEVEL_TAG = { '1': H1, '2': H2, '3': H3 } as const;
|
|
21
|
+
const TAG = { '1': H1, '2': H2, '3': H3 } as const;
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Level 3: 14px/20px font-600 -> 18px/24px font-500
|
|
22
|
-
const LEVEL_CLASSES = {
|
|
23
|
-
'1': 'text-[18px] leading-[24px] font-medium lg:text-[32px] lg:leading-[40px] lg:tracking-[-0.5px]',
|
|
24
|
-
'2': 'text-[16px] leading-[24px] font-medium lg:text-[24px] lg:leading-[32px]',
|
|
25
|
-
'3': 'text-[14px] leading-[20px] font-semibold lg:text-[18px] lg:leading-[24px] lg:font-medium',
|
|
23
|
+
const RAMP = {
|
|
24
|
+
'1': { fontSize: 18, lineHeight: 24, fontWeight: '500', $lg: { fontSize: 32, lineHeight: 40, letterSpacing: -0.5 } },
|
|
25
|
+
'2': { fontSize: 16, lineHeight: 24, fontWeight: '500', $lg: { fontSize: 24, lineHeight: 32 } },
|
|
26
|
+
'3': { fontSize: 14, lineHeight: 20, fontWeight: '600', $lg: { fontSize: 18, lineHeight: 24, fontWeight: '500' } },
|
|
26
27
|
} as const;
|
|
27
28
|
|
|
28
|
-
const BASE_CLASSES = 'font-heading text-heading';
|
|
29
|
-
|
|
30
29
|
export const Heading = React.forwardRef<HTMLHeadingElement, HeadingProps>(
|
|
31
|
-
function Heading({ level,
|
|
32
|
-
const Tag = level ?
|
|
33
|
-
const levelClasses = level ? LEVEL_CLASSES[level] : undefined;
|
|
34
|
-
const mergedStyle = mb !== undefined ?
|
|
35
|
-
{ ...styleProp, marginBottom: typeof mb === 'number' ? `${ mb * 4 }px` : mb } :
|
|
36
|
-
styleProp;
|
|
30
|
+
function Heading({ level, ...rest }, ref) {
|
|
31
|
+
const Tag = level ? TAG[level] : H2;
|
|
37
32
|
|
|
38
33
|
return (
|
|
39
34
|
<Tag
|
|
40
35
|
ref={ ref as never }
|
|
41
36
|
unstyled
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
fontFamily="$heading"
|
|
38
|
+
color={ 'var(--color-heading)' as never }
|
|
39
|
+
{ ...(level ? RAMP[level] : null) }
|
|
44
40
|
{ ...(rest as object) }
|
|
45
41
|
/>
|
|
46
42
|
);
|