@luxfi/ui 7.4.0 → 7.4.3
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/avatar.cjs +7 -6
- package/dist/avatar.js +7 -5
- package/dist/badge.cjs +33 -35
- package/dist/badge.js +33 -35
- package/dist/bank.d.cts +3 -3
- package/dist/bank.d.ts +3 -3
- package/dist/checkbox.cjs +4 -4
- package/dist/checkbox.d.cts +3 -2
- package/dist/checkbox.d.ts +3 -2
- package/dist/checkbox.js +5 -4
- package/dist/chrome.cjs +960 -0
- package/dist/chrome.d.cts +93 -0
- package/dist/chrome.d.ts +93 -0
- package/dist/chrome.js +931 -0
- package/dist/drawer.cjs +86 -20
- package/dist/drawer.js +85 -19
- package/dist/expiration-selector.cjs +3 -2
- package/dist/expiration-selector.js +3 -2
- package/dist/greeks-display.cjs +6 -6
- package/dist/greeks-display.js +6 -6
- package/dist/gui.cjs +13 -0
- package/dist/gui.d.cts +2 -0
- package/dist/gui.d.ts +2 -0
- package/dist/gui.js +2 -0
- package/dist/heading.cjs +4 -2
- package/dist/heading.js +4 -2
- package/dist/iam.cjs +216 -0
- package/dist/iam.d.cts +65 -0
- package/dist/iam.d.ts +65 -0
- package/dist/iam.js +211 -0
- package/dist/icons.cjs +13 -0
- package/dist/icons.d.cts +1 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +2 -0
- package/dist/identity.cjs +430 -0
- package/dist/identity.d.cts +64 -0
- package/dist/identity.d.ts +64 -0
- package/dist/identity.js +408 -0
- package/dist/index.cjs +1610 -836
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +1587 -831
- package/dist/input.cjs +3 -1
- package/dist/input.d.cts +3 -5
- package/dist/input.d.ts +3 -5
- package/dist/input.js +3 -1
- package/dist/lux.config.cjs +23 -2
- package/dist/lux.config.d.cts +28 -10
- package/dist/lux.config.d.ts +28 -10
- package/dist/lux.config.js +23 -2
- package/dist/menu.cjs +4 -4
- package/dist/menu.js +4 -4
- package/dist/next.cjs +132 -0
- package/dist/next.d.cts +32 -0
- package/dist/next.d.ts +32 -0
- package/dist/next.js +129 -0
- package/dist/option-chain.cjs +1 -1
- package/dist/option-chain.js +1 -1
- package/dist/option-position.cjs +2 -1
- package/dist/option-position.js +2 -1
- package/dist/pnl-diagram.cjs +8 -6
- package/dist/pnl-diagram.js +8 -6
- package/dist/progress.cjs +7 -6
- package/dist/progress.js +7 -5
- package/dist/provider.cjs +146 -4
- package/dist/provider.d.cts +15 -3
- package/dist/provider.d.ts +15 -3
- package/dist/provider.js +146 -5
- package/dist/radio.cjs +9 -7
- package/dist/radio.js +10 -7
- package/dist/separator.cjs +3 -1
- package/dist/separator.js +3 -1
- package/dist/slider.cjs +28 -16
- package/dist/slider.js +28 -15
- package/dist/strategy-builder.cjs +2 -1
- package/dist/strategy-builder.js +2 -1
- package/dist/switch.cjs +11 -12
- package/dist/switch.js +12 -12
- package/dist/tag.cjs +34 -36
- package/dist/tag.js +33 -35
- package/dist/textarea.cjs +3 -1
- package/dist/textarea.js +3 -1
- package/dist/tooltip.cjs +28 -30
- package/dist/tooltip.js +27 -29
- package/dist/use-narrow.cjs +46 -0
- package/dist/use-narrow.d.cts +12 -0
- package/dist/use-narrow.d.ts +12 -0
- package/dist/use-narrow.js +20 -0
- package/dist/vite.cjs +109 -0
- package/dist/vite.d.cts +29 -0
- package/dist/vite.d.ts +29 -0
- package/dist/vite.js +107 -0
- package/dist/white-label.cjs +142 -0
- package/dist/white-label.d.cts +59 -0
- package/dist/white-label.d.ts +59 -0
- package/dist/white-label.js +134 -0
- package/package.json +85 -19
- package/src/avatar.tsx +22 -16
- package/src/checkbox.tsx +15 -10
- package/src/chrome.tsx +459 -0
- package/src/drawer.tsx +83 -10
- package/src/engine.ts +130 -0
- package/src/expiration-selector.tsx +3 -2
- package/src/greeks-display.tsx +9 -6
- package/src/gui.ts +17 -0
- package/src/heading.tsx +12 -8
- package/src/iam.ts +170 -0
- package/src/icons.ts +18 -0
- package/src/identity.tsx +318 -0
- package/src/index.ts +44 -1
- package/src/input.tsx +13 -9
- package/src/lux.config.ts +16 -15
- package/src/menu.tsx +4 -4
- package/src/next.ts +89 -0
- package/src/option-chain.tsx +1 -1
- package/src/option-position.tsx +2 -1
- package/src/pnl-diagram.tsx +11 -6
- package/src/progress.tsx +15 -8
- package/src/provider.tsx +48 -7
- package/src/radio.tsx +15 -11
- package/src/separator.tsx +8 -3
- package/src/slider.tsx +35 -19
- package/src/strategy-builder.tsx +3 -2
- package/src/switch.tsx +17 -16
- package/src/textarea.tsx +8 -4
- package/src/tooltip.tsx +15 -24
- package/src/use-narrow.ts +40 -0
- package/src/vite.ts +80 -0
- package/src/white-label.ts +254 -0
- package/tokens.css +613 -319
- package/src/tokens.css +0 -438
package/src/progress.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Progress as GuiProgress } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
4
|
import { cn } from './utils';
|
|
@@ -81,28 +81,35 @@ export const Progress = React.forwardRef<HTMLDivElement, ProgressProps>(
|
|
|
81
81
|
style={ rootStyle }
|
|
82
82
|
{ ...rest }
|
|
83
83
|
>
|
|
84
|
-
<
|
|
84
|
+
<GuiProgress
|
|
85
85
|
value={ percent }
|
|
86
86
|
max={ 100 }
|
|
87
|
+
unstyled
|
|
87
88
|
className={ cn(
|
|
88
89
|
'w-full overflow-hidden rounded-full',
|
|
89
90
|
'bg-[var(--color-progress-track)]',
|
|
90
91
|
SIZE_CLASSES[size],
|
|
91
92
|
trackProps?.className,
|
|
92
93
|
) }
|
|
93
|
-
style={ trackStyle }
|
|
94
|
+
style={ trackStyle as never }
|
|
94
95
|
>
|
|
95
|
-
|
|
96
|
+
{ /* gui drives the fill off `value` — it sizes the indicator to 200%
|
|
97
|
+
and translates it — and eases it with the config's transition
|
|
98
|
+
driver. Geometry from the primitive, paint from the Lux classes,
|
|
99
|
+
so the bar is a consequence of state rather than a hand-written
|
|
100
|
+
width + CSS transition. */ }
|
|
101
|
+
<GuiProgress.Indicator
|
|
102
|
+
unstyled
|
|
103
|
+
transition="slow"
|
|
96
104
|
className={ cn(
|
|
97
|
-
'h-full rounded-full
|
|
105
|
+
'h-full rounded-full',
|
|
98
106
|
!color && 'bg-[var(--color-progress-indicator)]',
|
|
99
107
|
) }
|
|
100
108
|
style={{
|
|
101
|
-
width: `${ percent }%`,
|
|
102
109
|
...(color && { backgroundColor: `var(--color-${ color.replace('.', '-') }, ${ color })` }),
|
|
103
|
-
}}
|
|
110
|
+
} as never}
|
|
104
111
|
/>
|
|
105
|
-
</
|
|
112
|
+
</GuiProgress>
|
|
106
113
|
</div>
|
|
107
114
|
);
|
|
108
115
|
},
|
package/src/provider.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import React from 'react';
|
|
13
13
|
|
|
14
14
|
import config from './lux.config';
|
|
15
|
+
import { currentHost, resolveWhiteLabel, type WhiteLabel } from './white-label';
|
|
15
16
|
|
|
16
17
|
const defaultQueryClient = new QueryClient({
|
|
17
18
|
defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 1, staleTime: 30_000 } },
|
|
@@ -43,10 +44,45 @@ const useFontBodyReset = (): void => {
|
|
|
43
44
|
}, []);
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
// --- White-label context ---------------------------------------------------
|
|
48
|
+
// Resolved once at the root and read anywhere below. Brand, theme, IAM client
|
|
49
|
+
// and issuer all come from the Host header — see ./white-label.
|
|
50
|
+
const WhiteLabelContext = React.createContext<WhiteLabel>(resolveWhiteLabel(''));
|
|
51
|
+
|
|
52
|
+
/** The white-label identity for the host this page is being served under. */
|
|
53
|
+
export const useWhiteLabel = (): WhiteLabel => React.useContext(WhiteLabelContext);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Host used for the FIRST render, then upgraded to the browser's real host.
|
|
57
|
+
*
|
|
58
|
+
* Server-rendered surfaces pass `req.headers.host` in, so first paint is already
|
|
59
|
+
* correct and hydration matches. Surfaces that do not pass one render the
|
|
60
|
+
* default row on the server and swap to the host-resolved row in an effect —
|
|
61
|
+
* deterministic on both sides, so there is no hydration mismatch either way.
|
|
62
|
+
*/
|
|
63
|
+
const useHostWhiteLabel = (host: string | undefined): WhiteLabel => {
|
|
64
|
+
const [ resolvedHost, setResolvedHost ] = React.useState(host ?? '');
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
if (host !== undefined) return;
|
|
67
|
+
const browserHost = currentHost();
|
|
68
|
+
if (browserHost) setResolvedHost(browserHost);
|
|
69
|
+
}, [ host ]);
|
|
70
|
+
return React.useMemo(() => resolveWhiteLabel(resolvedHost), [ resolvedHost ]);
|
|
71
|
+
};
|
|
72
|
+
|
|
46
73
|
interface AppProviderProps {
|
|
47
74
|
children: React.ReactNode;
|
|
48
75
|
queryClient?: QueryClient;
|
|
49
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Host header value, for server rendering. Omit in the browser and the host
|
|
78
|
+
* is read from `location`. Never bake a brand into a deployment — pass the
|
|
79
|
+
* request's host and the same image serves every org.
|
|
80
|
+
*/
|
|
81
|
+
host?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Escape hatch: pin the gui child-theme row (`dark_lux` | `dark_zoo` |
|
|
84
|
+
* `dark_hanzo` | `dark_pars`). Leave unset so the theme follows the host.
|
|
85
|
+
*/
|
|
50
86
|
defaultTheme?: string;
|
|
51
87
|
}
|
|
52
88
|
|
|
@@ -55,15 +91,20 @@ interface AppProviderProps {
|
|
|
55
91
|
export const AppProvider = ({
|
|
56
92
|
children,
|
|
57
93
|
queryClient,
|
|
58
|
-
|
|
94
|
+
host,
|
|
95
|
+
defaultTheme,
|
|
59
96
|
}: AppProviderProps): React.ReactElement => {
|
|
60
97
|
useFontBodyReset();
|
|
98
|
+
const whiteLabel = useHostWhiteLabel(host);
|
|
99
|
+
const theme = defaultTheme ?? whiteLabel.theme;
|
|
61
100
|
return (
|
|
62
|
-
<
|
|
63
|
-
<
|
|
64
|
-
{
|
|
65
|
-
|
|
66
|
-
|
|
101
|
+
<WhiteLabelContext.Provider value={whiteLabel}>
|
|
102
|
+
<GuiProvider config={config} defaultTheme={theme as never}>
|
|
103
|
+
<QueryClientProvider client={queryClient ?? defaultQueryClient}>
|
|
104
|
+
{children}
|
|
105
|
+
</QueryClientProvider>
|
|
106
|
+
</GuiProvider>
|
|
107
|
+
</WhiteLabelContext.Provider>
|
|
67
108
|
);
|
|
68
109
|
};
|
|
69
110
|
|
package/src/radio.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
// chakra() HOC removed — pure Radix + Tailwind
|
|
1
|
+
import { RadioGroup as GuiRadioGroup } from '@hanzo/gui';
|
|
3
2
|
import * as React from 'react';
|
|
4
3
|
|
|
5
4
|
import { cn } from './utils';
|
|
@@ -60,7 +59,8 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
60
59
|
disabled = false,
|
|
61
60
|
readOnly = false,
|
|
62
61
|
orientation = 'vertical',
|
|
63
|
-
loop
|
|
62
|
+
// `loop` was a Radix-only knob; gui's roving focus always wraps.
|
|
63
|
+
loop: _loop = true,
|
|
64
64
|
defaultValue,
|
|
65
65
|
value,
|
|
66
66
|
onValueChange,
|
|
@@ -79,13 +79,15 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
79
79
|
|
|
80
80
|
return (
|
|
81
81
|
<RadioSizeContext.Provider value={ size }>
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
{ /* gui's RadioGroup — role=radiogroup / role=radio and roving focus
|
|
83
|
+
from its headless core, real Pressables on native. */ }
|
|
84
|
+
<GuiRadioGroup
|
|
85
|
+
ref={ ref as never }
|
|
86
|
+
unstyled
|
|
84
87
|
name={ name }
|
|
85
88
|
required={ required }
|
|
86
89
|
disabled={ disabled || readOnly }
|
|
87
90
|
orientation={ orientation }
|
|
88
|
-
loop={ loop }
|
|
89
91
|
defaultValue={ defaultValue }
|
|
90
92
|
value={ value ?? undefined }
|
|
91
93
|
onValueChange={ handleValueChange }
|
|
@@ -94,10 +96,10 @@ const RadioGroupBase = React.forwardRef<HTMLDivElement, RadioGroupProps>(
|
|
|
94
96
|
orientation === 'vertical' ? 'flex-col gap-3' : 'flex-row gap-6',
|
|
95
97
|
className,
|
|
96
98
|
) }
|
|
97
|
-
{ ...rest }
|
|
99
|
+
{ ...(rest as object) }
|
|
98
100
|
>
|
|
99
101
|
{ children }
|
|
100
|
-
</
|
|
102
|
+
</GuiRadioGroup>
|
|
101
103
|
</RadioSizeContext.Provider>
|
|
102
104
|
);
|
|
103
105
|
},
|
|
@@ -133,7 +135,8 @@ const RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
|
133
135
|
data-disabled={ disabled || undefined }
|
|
134
136
|
{ ...rest }
|
|
135
137
|
>
|
|
136
|
-
<
|
|
138
|
+
<GuiRadioGroup.Item
|
|
139
|
+
unstyled
|
|
137
140
|
value={ value }
|
|
138
141
|
disabled={ disabled }
|
|
139
142
|
className={ cn(
|
|
@@ -145,13 +148,14 @@ const RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(
|
|
|
145
148
|
sizeClasses.control,
|
|
146
149
|
) }
|
|
147
150
|
>
|
|
148
|
-
<
|
|
151
|
+
<GuiRadioGroup.Indicator
|
|
152
|
+
unstyled
|
|
149
153
|
className={ cn(
|
|
150
154
|
'block rounded-full bg-gray-800 dark:bg-white',
|
|
151
155
|
sizeClasses.indicator,
|
|
152
156
|
) }
|
|
153
157
|
/>
|
|
154
|
-
</
|
|
158
|
+
</GuiRadioGroup.Item>
|
|
155
159
|
{ /* Hidden native input for form compatibility and ref forwarding */ }
|
|
156
160
|
<input
|
|
157
161
|
ref={ ref }
|
package/src/separator.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Separator as GuiSeparator } from '@hanzo/gui';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
|
|
3
4
|
import { cn } from './utils';
|
|
@@ -32,6 +33,9 @@ const VERTICAL_SIZE_CLASSES: Record<Size, string> = {
|
|
|
32
33
|
lg: 'border-l-[3px]',
|
|
33
34
|
};
|
|
34
35
|
|
|
36
|
+
// gui's Separator wearing the Lux border scale. `vertical` is the primitive's
|
|
37
|
+
// own orientation variant, so it lays out correctly on native too; the Tailwind
|
|
38
|
+
// classes only carry border weight and dash pattern.
|
|
35
39
|
export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
|
|
36
40
|
function Separator(
|
|
37
41
|
{ orientation = 'horizontal', variant = 'solid', size = 'sm', className, ...rest },
|
|
@@ -40,8 +44,9 @@ export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
|
|
|
40
44
|
const isVertical = orientation === 'vertical';
|
|
41
45
|
|
|
42
46
|
return (
|
|
43
|
-
<
|
|
44
|
-
ref={ ref }
|
|
47
|
+
<GuiSeparator
|
|
48
|
+
ref={ ref as never }
|
|
49
|
+
vertical={ isVertical }
|
|
45
50
|
role="separator"
|
|
46
51
|
aria-orientation={ orientation }
|
|
47
52
|
className={ cn(
|
|
@@ -52,7 +57,7 @@ export const Separator = React.forwardRef<HTMLHRElement, SeparatorProps>(
|
|
|
52
57
|
isVertical ? 'self-stretch h-auto w-0' : 'w-full',
|
|
53
58
|
className,
|
|
54
59
|
) }
|
|
55
|
-
{ ...rest }
|
|
60
|
+
{ ...(rest as object) }
|
|
56
61
|
/>
|
|
57
62
|
);
|
|
58
63
|
},
|
package/src/slider.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Slider as GuiSlider } from '@hanzo/gui';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
4
|
import { cn } from './utils';
|
|
@@ -20,9 +20,10 @@ export interface SliderProps {
|
|
|
20
20
|
readonly className?: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// gui's Slider: pointer, touch and keyboard from one implementation, with the
|
|
24
|
+
// drag handled as a pan gesture by the engine rather than by mouse events — so it
|
|
25
|
+
// works under a finger, not only under a cursor. `role=slider` and the arrow-key
|
|
26
|
+
// contract come from the primitive.
|
|
26
27
|
export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
27
28
|
function Slider(props, ref) {
|
|
28
29
|
const {
|
|
@@ -35,7 +36,7 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
35
36
|
max = 100,
|
|
36
37
|
step,
|
|
37
38
|
disabled,
|
|
38
|
-
orientation,
|
|
39
|
+
orientation = 'horizontal',
|
|
39
40
|
name,
|
|
40
41
|
onValueChange,
|
|
41
42
|
onValueCommit,
|
|
@@ -51,6 +52,17 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
51
52
|
|
|
52
53
|
const hasMarkLabel = Boolean(marks?.some((mark) => mark.label));
|
|
53
54
|
|
|
55
|
+
// gui signals the end of a drag without echoing the value, so keep the last
|
|
56
|
+
// one to satisfy the Chakra-shaped `onValueCommit(value)` contract.
|
|
57
|
+
const latest = React.useRef<Array<number>>(value ?? defaultValue ?? []);
|
|
58
|
+
const handleValueChange = React.useCallback((next: Array<number>) => {
|
|
59
|
+
latest.current = next;
|
|
60
|
+
onValueChange?.(next);
|
|
61
|
+
}, [ onValueChange ]);
|
|
62
|
+
const handleSlideEnd = React.useCallback(() => {
|
|
63
|
+
onValueCommit?.(latest.current);
|
|
64
|
+
}, [ onValueCommit ]);
|
|
65
|
+
|
|
54
66
|
return (
|
|
55
67
|
<div className={ cn('flex flex-col gap-1', className) }>
|
|
56
68
|
{ label && !showValue && (
|
|
@@ -69,8 +81,9 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
69
81
|
</div>
|
|
70
82
|
) }
|
|
71
83
|
|
|
72
|
-
<
|
|
73
|
-
ref={ ref }
|
|
84
|
+
<GuiSlider
|
|
85
|
+
ref={ ref as never }
|
|
86
|
+
unstyled
|
|
74
87
|
className={ cn(
|
|
75
88
|
'relative flex w-full touch-none select-none items-center',
|
|
76
89
|
hasMarkLabel && 'mb-6',
|
|
@@ -83,23 +96,26 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
83
96
|
disabled={ disabled }
|
|
84
97
|
orientation={ orientation }
|
|
85
98
|
name={ name }
|
|
86
|
-
onValueChange={
|
|
87
|
-
|
|
99
|
+
onValueChange={ handleValueChange }
|
|
100
|
+
onSlideEnd={ handleSlideEnd }
|
|
88
101
|
>
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`h-[${ TRACK_HEIGHT }px]`,
|
|
93
|
-
) }
|
|
102
|
+
<GuiSlider.Track
|
|
103
|
+
unstyled
|
|
104
|
+
className="relative grow h-1.5 rounded-full bg-border-divider"
|
|
94
105
|
>
|
|
95
|
-
<
|
|
96
|
-
|
|
106
|
+
<GuiSlider.TrackActive
|
|
107
|
+
unstyled
|
|
108
|
+
className="absolute h-full rounded-full bg-link-primary"
|
|
109
|
+
/>
|
|
110
|
+
</GuiSlider.Track>
|
|
97
111
|
|
|
98
112
|
{ resolvedValue?.map((_, index) => (
|
|
99
|
-
<
|
|
113
|
+
<GuiSlider.Thumb
|
|
100
114
|
key={ index }
|
|
115
|
+
index={ index }
|
|
116
|
+
unstyled
|
|
101
117
|
className={ cn(
|
|
102
|
-
|
|
118
|
+
'block h-5 w-5',
|
|
103
119
|
'rounded-full border-2 border-link-primary bg-white',
|
|
104
120
|
'shadow-sm transition-colors',
|
|
105
121
|
'hover:border-link-primary-hover',
|
|
@@ -108,7 +124,7 @@ export const Slider = React.forwardRef<HTMLSpanElement, SliderProps>(
|
|
|
108
124
|
) }
|
|
109
125
|
/>
|
|
110
126
|
)) }
|
|
111
|
-
</
|
|
127
|
+
</GuiSlider>
|
|
112
128
|
|
|
113
129
|
<SliderMarks marks={ marks } min={ min } max={ max }/>
|
|
114
130
|
</div>
|
package/src/strategy-builder.tsx
CHANGED
|
@@ -207,9 +207,10 @@ function LegRow({ leg, index, strikes, expirations, removable, onChange, onRemov
|
|
|
207
207
|
type="button"
|
|
208
208
|
className={cn(
|
|
209
209
|
'px-2 py-0.5 rounded text-xs font-medium transition-colors',
|
|
210
|
+
// Call vs put is a rank difference, not a hue difference.
|
|
210
211
|
leg.type === 'call'
|
|
211
|
-
? 'bg-
|
|
212
|
-
: 'bg-
|
|
212
|
+
? 'bg-[var(--secondary)] text-[var(--foreground)]'
|
|
213
|
+
: 'bg-[var(--muted)] text-[var(--muted-foreground)]',
|
|
213
214
|
)}
|
|
214
215
|
onClick={() => update({ type: leg.type === 'call' ? 'put' : 'call' })}
|
|
215
216
|
>
|
package/src/switch.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
// chakra() HOC removed — pure Radix + Tailwind
|
|
1
|
+
import { Switch as GuiSwitch } from '@hanzo/gui';
|
|
3
2
|
import * as React from 'react';
|
|
4
3
|
|
|
5
4
|
import { cn } from './utils';
|
|
@@ -7,20 +6,23 @@ import { cn } from './utils';
|
|
|
7
6
|
const NOOP = () => { /* noop */ };
|
|
8
7
|
|
|
9
8
|
// ─── Size mappings ────────────────────────────────────────────────────
|
|
9
|
+
// Paint and box size only. Thumb travel is NOT a hardcoded translate any more:
|
|
10
|
+
// gui measures the track and the knob and moves the knob by the difference, so
|
|
11
|
+
// the switch stays correct at any size and on native.
|
|
10
12
|
const SIZE_CLASSES = {
|
|
11
13
|
sm: {
|
|
12
14
|
root: 'h-4 w-7',
|
|
13
|
-
thumb: 'h-3 w-3
|
|
15
|
+
thumb: 'h-3 w-3',
|
|
14
16
|
label: 'text-xs',
|
|
15
17
|
},
|
|
16
18
|
md: {
|
|
17
19
|
root: 'h-5 w-9',
|
|
18
|
-
thumb: 'h-4 w-4
|
|
20
|
+
thumb: 'h-4 w-4',
|
|
19
21
|
label: 'text-sm',
|
|
20
22
|
},
|
|
21
23
|
lg: {
|
|
22
24
|
root: 'h-6 w-11',
|
|
23
|
-
thumb: 'h-5 w-5
|
|
25
|
+
thumb: 'h-5 w-5',
|
|
24
26
|
label: 'text-base',
|
|
25
27
|
},
|
|
26
28
|
} as const;
|
|
@@ -72,11 +74,8 @@ const SwitchBase = React.forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
72
74
|
setInternalChecked(nextChecked);
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
// We call with a single object arg; if the consumer ignores it, that's fine.
|
|
78
|
-
(onCheckedChange as (details: { checked: boolean }) => void)({ checked: nextChecked });
|
|
79
|
-
}
|
|
77
|
+
// Chakra-shaped callback ({ checked }) — the contract consumers already use.
|
|
78
|
+
onCheckedChange?.({ checked: nextChecked });
|
|
80
79
|
},
|
|
81
80
|
[ isControlled, onCheckedChange ],
|
|
82
81
|
);
|
|
@@ -97,7 +96,8 @@ const SwitchBase = React.forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
97
96
|
data-disabled={ disabled || undefined }
|
|
98
97
|
{ ...rest }
|
|
99
98
|
>
|
|
100
|
-
<
|
|
99
|
+
<GuiSwitch
|
|
100
|
+
unstyled
|
|
101
101
|
checked={ checkedState }
|
|
102
102
|
onCheckedChange={ handleCheckedChange }
|
|
103
103
|
disabled={ disabled }
|
|
@@ -115,11 +115,12 @@ const SwitchBase = React.forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
115
115
|
{ checkedState ? trackLabel.on : trackLabel.off }
|
|
116
116
|
</span>
|
|
117
117
|
) }
|
|
118
|
-
<
|
|
118
|
+
<GuiSwitch.Thumb
|
|
119
|
+
unstyled
|
|
120
|
+
data-thumb
|
|
121
|
+
transition="quicker"
|
|
119
122
|
className={ cn(
|
|
120
123
|
'block rounded-full bg-white shadow-sm',
|
|
121
|
-
'transition-transform duration-200',
|
|
122
|
-
'translate-x-0.5',
|
|
123
124
|
sizeClasses.thumb,
|
|
124
125
|
) }
|
|
125
126
|
>
|
|
@@ -128,8 +129,8 @@ const SwitchBase = React.forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
128
129
|
{ checkedState ? thumbLabel.on : thumbLabel.off }
|
|
129
130
|
</span>
|
|
130
131
|
) }
|
|
131
|
-
</
|
|
132
|
-
</
|
|
132
|
+
</GuiSwitch.Thumb>
|
|
133
|
+
</GuiSwitch>
|
|
133
134
|
{ /* Hidden native input for form compat and ref forwarding */ }
|
|
134
135
|
<input
|
|
135
136
|
ref={ ref }
|
package/src/textarea.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { TextArea as GuiTextArea } from '@hanzo/gui';
|
|
1
2
|
import React from 'react';
|
|
2
3
|
|
|
3
4
|
import { cn } from './utils';
|
|
@@ -38,6 +39,8 @@ export interface TextareaProps extends Omit<React.ComponentPropsWithRef<'textare
|
|
|
38
39
|
readonly onChangeText?: (text: string) => void;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
// gui's TextArea — multiline TextInput on native, `<textarea>` on web — wearing
|
|
43
|
+
// the Lux size/variant classes.
|
|
41
44
|
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
42
45
|
({ size = 'md', variant = 'outline', className, onChange, onChangeText, ...rest }, ref) => {
|
|
43
46
|
const handleChange = React.useCallback(
|
|
@@ -48,11 +51,12 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
48
51
|
[ onChange, onChangeText ],
|
|
49
52
|
);
|
|
50
53
|
return (
|
|
51
|
-
<
|
|
52
|
-
ref={ ref }
|
|
54
|
+
<GuiTextArea
|
|
55
|
+
ref={ ref as never }
|
|
56
|
+
unstyled
|
|
53
57
|
className={ cn(TEXTAREA_BASE, TEXTAREA_SIZE_CLASSES[size], TEXTAREA_VARIANT_CLASSES[variant], className) }
|
|
54
|
-
{ ...rest }
|
|
55
|
-
onChange={ handleChange }
|
|
58
|
+
{ ...(rest as object) }
|
|
59
|
+
onChange={ handleChange as never }
|
|
56
60
|
/>
|
|
57
61
|
);
|
|
58
62
|
},
|
package/src/tooltip.tsx
CHANGED
|
@@ -5,28 +5,9 @@ import {
|
|
|
5
5
|
import { useClickAway } from '@uidotdev/usehooks';
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
|
|
8
|
+
import { useIsTouch } from './use-narrow';
|
|
8
9
|
import { cn } from './utils';
|
|
9
10
|
|
|
10
|
-
// Inline useIsMobile hook (matches lg breakpoint = 1024px)
|
|
11
|
-
const LG_BREAKPOINT = 1024;
|
|
12
|
-
|
|
13
|
-
function useIsMobile(): boolean {
|
|
14
|
-
const [ isMobile, setIsMobile ] = React.useState(() => {
|
|
15
|
-
if (typeof window === 'undefined') return false;
|
|
16
|
-
return window.innerWidth < LG_BREAKPOINT;
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
React.useEffect(() => {
|
|
20
|
-
const mql = window.matchMedia(`(max-width: ${ LG_BREAKPOINT - 1 }px)`);
|
|
21
|
-
const handler = (e: MediaQueryListEvent) => setIsMobile(e.matches);
|
|
22
|
-
setIsMobile(mql.matches);
|
|
23
|
-
mql.addEventListener('change', handler);
|
|
24
|
-
return () => mql.removeEventListener('change', handler);
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
return isMobile;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
11
|
/** Map placement string to @hanzogui Popper placement prop. */
|
|
31
12
|
function mapPlacement(p: string | undefined): string | undefined {
|
|
32
13
|
if (!p) return undefined;
|
|
@@ -86,7 +67,7 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
|
86
67
|
const [ open, setOpen ] = React.useState<boolean>(defaultOpen);
|
|
87
68
|
const timeoutRef = React.useRef<number | null>(null);
|
|
88
69
|
|
|
89
|
-
const isMobile =
|
|
70
|
+
const isMobile = useIsTouch();
|
|
90
71
|
|
|
91
72
|
const handleOpenChange = React.useCallback((nextOpen: boolean) => {
|
|
92
73
|
setOpen(nextOpen);
|
|
@@ -101,9 +82,12 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
|
101
82
|
}, nextOpen ? openDelay : closeDelay);
|
|
102
83
|
}, [ closeDelay, openDelay, onOpenChange ]);
|
|
103
84
|
|
|
85
|
+
// `useClickAway` keeps the LATEST callback in a ref and returns a ref whose
|
|
86
|
+
// identity never changes, so this may read `open` directly — and must, so a
|
|
87
|
+
// closed tooltip does not arm a close timer on every document click.
|
|
104
88
|
const handleClickAway = React.useCallback(() => {
|
|
105
|
-
handleOpenChangeManual(false);
|
|
106
|
-
}, [ handleOpenChangeManual ]);
|
|
89
|
+
if (open) handleOpenChangeManual(false);
|
|
90
|
+
}, [ open, handleOpenChangeManual ]);
|
|
107
91
|
|
|
108
92
|
const triggerRef = useClickAway<HTMLButtonElement>(handleClickAway);
|
|
109
93
|
|
|
@@ -151,7 +135,14 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
|
151
135
|
unstyled
|
|
152
136
|
>
|
|
153
137
|
<GuiTooltip.Trigger
|
|
154
|
-
ref
|
|
138
|
+
// ONE ref, always the same one. Swapping between `triggerRef` and
|
|
139
|
+
// `undefined` on open/close changes the composed-ref identity, so
|
|
140
|
+
// React detaches and re-attaches the popper's reference element on
|
|
141
|
+
// every hover — two extra `refs.setReference()` calls per open, each
|
|
142
|
+
// of which throws uncaught when the Popper context is not the one
|
|
143
|
+
// this anchor is reading (see ./engine). The click-away handler
|
|
144
|
+
// already no-ops while closed, so the ref never needed to move.
|
|
145
|
+
ref={ triggerRef }
|
|
155
146
|
asChild
|
|
156
147
|
{...(isMobile ? { onPress: handleTriggerClick } : {})}
|
|
157
148
|
{ ...triggerProps as any }
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
// ONE viewport hook for the whole design system.
|
|
6
|
+
//
|
|
7
|
+
// Three components used to carry their own copy of the same `matchMedia`
|
|
8
|
+
// dance at three different breakpoints, so "is this phone-sized?" had three
|
|
9
|
+
// answers. It has one now, and the breakpoints are named values rather than
|
|
10
|
+
// magic numbers buried in a component.
|
|
11
|
+
//
|
|
12
|
+
// SSR-safe by construction: the first render is always `false` on both the
|
|
13
|
+
// server and the client (there is no viewport during prerender, and guessing
|
|
14
|
+
// one produces a hydration mismatch), then the real answer arrives in an
|
|
15
|
+
// effect before paint.
|
|
16
|
+
|
|
17
|
+
/** Below this the UI is a phone: menus become sheets, drawers drag. */
|
|
18
|
+
export const NARROW_PX = 640;
|
|
19
|
+
|
|
20
|
+
/** Below this the pointer is a finger: hover-only affordances are suppressed. */
|
|
21
|
+
export const TOUCH_PX = 1024;
|
|
22
|
+
|
|
23
|
+
/** True while the viewport is narrower than `px`. */
|
|
24
|
+
export function useBelow(px: number): boolean {
|
|
25
|
+
const [ below, setBelow ] = React.useState(false);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
const mql = window.matchMedia(`(max-width: ${ px - 1 }px)`);
|
|
28
|
+
const apply = () => setBelow(mql.matches);
|
|
29
|
+
apply();
|
|
30
|
+
mql.addEventListener('change', apply);
|
|
31
|
+
return () => mql.removeEventListener('change', apply);
|
|
32
|
+
}, [ px ]);
|
|
33
|
+
return below;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Phone-sized: present menus as sheets. */
|
|
37
|
+
export const useIsNarrow = (): boolean => useBelow(NARROW_PX);
|
|
38
|
+
|
|
39
|
+
/** Touch-sized: no hover, so hover-only affordances must not be the only path. */
|
|
40
|
+
export const useIsTouch = (): boolean => useBelow(TOUCH_PX);
|
package/src/vite.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// The bundler wiring the gui engine needs on web, as ONE plugin.
|
|
2
|
+
//
|
|
3
|
+
// @hanzo/gui is authored against React Native primitives, so a web bundle has to
|
|
4
|
+
// alias `react-native` → `react-native-web`, pre-bundle the CJS-only pieces, keep
|
|
5
|
+
// exactly one physical copy of React and of the engine, and define the platform
|
|
6
|
+
// flags the engine reads at module scope. Every Lux surface needs the identical
|
|
7
|
+
// six-part incantation, and every surface that hand-rolled it got a different
|
|
8
|
+
// subset — which is how you end up debugging `createContext of undefined` in one
|
|
9
|
+
// app and a duplicate-React theme context in another.
|
|
10
|
+
//
|
|
11
|
+
// import { luxUi } from '@luxfi/ui/vite'
|
|
12
|
+
// export default defineConfig({ plugins: [react(), luxUi()] })
|
|
13
|
+
//
|
|
14
|
+
// Node-only (it is a build-time module) and dependency-free: it returns a plain
|
|
15
|
+
// Vite plugin object, so it does not force `vite` into anyone's runtime graph.
|
|
16
|
+
|
|
17
|
+
import { GUI_DEFINES, GUI_PACKAGES } from './engine';
|
|
18
|
+
|
|
19
|
+
interface LuxUiViteOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Extra packages to keep to a single physical copy. React, the gui engine and
|
|
22
|
+
* TanStack Query are always deduped.
|
|
23
|
+
*/
|
|
24
|
+
readonly dedupe?: ReadonlyArray<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Extra packages to pre-bundle. The engine, react-native-web and the CJS-only
|
|
27
|
+
* colour helper are always included.
|
|
28
|
+
*/
|
|
29
|
+
readonly include?: ReadonlyArray<string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Minimal structural shape of a Vite plugin — avoids a hard `vite` type dep. */
|
|
33
|
+
interface LuxUiVitePlugin {
|
|
34
|
+
readonly name: string;
|
|
35
|
+
readonly config: () => {
|
|
36
|
+
define: Record<string, string>;
|
|
37
|
+
resolve: { alias: Record<string, string>; dedupe: Array<string> };
|
|
38
|
+
optimizeDeps: { include: Array<string> };
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The WHOLE engine, not a hand-picked three. `@hanzogui/popper` and
|
|
43
|
+
// `@hanzogui/popover` publish the contexts a Tooltip trigger reads, and they
|
|
44
|
+
// were the two missing from the old list — so a consumer using this plugin
|
|
45
|
+
// could still get two poppers and the `setReference` crash it causes. One list,
|
|
46
|
+
// shared with the Next wrapper. See ./engine.
|
|
47
|
+
const DEDUPE = [
|
|
48
|
+
'react',
|
|
49
|
+
'react-dom',
|
|
50
|
+
'@tanstack/react-query',
|
|
51
|
+
...GUI_PACKAGES,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const INCLUDE = [
|
|
55
|
+
'react-native-web',
|
|
56
|
+
'@react-native/normalize-color',
|
|
57
|
+
'@hanzo/gui',
|
|
58
|
+
'@hanzogui/core',
|
|
59
|
+
'@luxfi/ui',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export function luxUi(options: LuxUiViteOptions = {}): LuxUiVitePlugin {
|
|
63
|
+
return {
|
|
64
|
+
name: '@luxfi/ui',
|
|
65
|
+
config: () => ({
|
|
66
|
+
// The engine branches on these at module scope; without them it takes the
|
|
67
|
+
// native path in a browser bundle. (./engine)
|
|
68
|
+
define: { ...GUI_DEFINES },
|
|
69
|
+
resolve: {
|
|
70
|
+
alias: { 'react-native': 'react-native-web' },
|
|
71
|
+
dedupe: [ ...new Set([ ...DEDUPE, ...(options.dedupe ?? []) ]) ],
|
|
72
|
+
},
|
|
73
|
+
optimizeDeps: {
|
|
74
|
+
include: [ ...new Set([ ...INCLUDE, ...(options.include ?? []) ]) ],
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type { LuxUiViteOptions };
|