@luxfi/ui 7.3.2 → 7.4.2
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 +2 -2
- package/dist/alert.js +1 -1
- package/dist/avatar.cjs +7 -6
- package/dist/avatar.js +7 -5
- package/dist/badge.cjs +36 -38
- package/dist/badge.js +31 -33
- package/dist/bank.cjs +13 -13
- package/dist/bank.d.cts +31 -24
- package/dist/bank.d.ts +31 -24
- package/dist/bank.js +1 -1
- package/dist/button.cjs +3 -3
- package/dist/button.js +1 -1
- 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 +950 -0
- package/dist/chrome.d.cts +93 -0
- package/dist/chrome.d.ts +93 -0
- package/dist/chrome.js +921 -0
- package/dist/close-button.cjs +2 -2
- package/dist/close-button.js +1 -1
- package/dist/dialog.cjs +13 -14
- package/dist/dialog.d.cts +58 -5
- package/dist/dialog.d.ts +58 -5
- package/dist/dialog.js +1 -2
- package/dist/drawer.cjs +87 -21
- 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 +206 -0
- package/dist/iam.d.cts +65 -0
- package/dist/iam.d.ts +65 -0
- package/dist/iam.js +201 -0
- package/dist/icon-button.cjs +2 -2
- package/dist/icon-button.js +1 -1
- 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 +420 -0
- package/dist/identity.d.cts +64 -0
- package/dist/identity.d.ts +64 -0
- package/dist/identity.js +398 -0
- package/dist/index.cjs +1699 -860
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +1618 -802
- package/dist/input.cjs +13 -3
- package/dist/input.d.cts +6 -0
- package/dist/input.d.ts +6 -0
- package/dist/input.js +13 -3
- package/dist/lux.config.cjs +77 -0
- package/dist/lux.config.d.cts +433 -0
- package/dist/lux.config.d.ts +433 -0
- package/dist/lux.config.js +69 -0
- package/dist/menu.cjs +4 -4
- package/dist/menu.js +4 -4
- package/dist/next.cjs +99 -0
- package/dist/next.d.cts +29 -0
- package/dist/next.d.ts +29 -0
- package/dist/next.js +96 -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/popover.cjs +8 -9
- package/dist/popover.js +1 -2
- package/dist/progress.cjs +7 -6
- package/dist/progress.js +7 -5
- package/dist/provider.cjs +199 -5
- package/dist/provider.d.cts +16 -3
- package/dist/provider.d.ts +16 -3
- package/dist/provider.js +195 -6
- 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 +38 -41
- package/dist/tag.js +31 -34
- package/dist/textarea.cjs +13 -3
- package/dist/textarea.d.cts +2 -0
- package/dist/textarea.d.ts +2 -0
- package/dist/textarea.js +13 -3
- package/dist/tooltip.cjs +31 -33
- package/dist/tooltip.js +25 -27
- 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 +40 -0
- package/dist/vite.d.cts +29 -0
- package/dist/vite.d.ts +29 -0
- package/dist/vite.js +38 -0
- package/dist/white-label.cjs +132 -0
- package/dist/white-label.d.cts +59 -0
- package/dist/white-label.d.ts +59 -0
- package/dist/white-label.js +124 -0
- package/package.json +95 -17
- package/src/avatar.tsx +22 -16
- package/src/bank.tsx +2 -2
- package/src/button.tsx +1 -1
- package/src/checkbox.tsx +15 -10
- package/src/chrome.tsx +459 -0
- package/src/close-button.tsx +1 -1
- package/src/dialog.tsx +1 -1
- package/src/drawer.tsx +83 -10
- 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/icon-button.tsx +1 -1
- package/src/icons.ts +18 -0
- package/src/identity.tsx +318 -0
- package/src/index.ts +50 -1
- package/src/input.tsx +24 -4
- package/src/lux.config.ts +82 -0
- package/src/menu.tsx +4 -4
- package/src/next.ts +142 -0
- package/src/option-chain.tsx +1 -1
- package/src/option-position.tsx +2 -1
- package/src/pnl-diagram.tsx +11 -6
- package/src/popover.tsx +1 -1
- package/src/progress.tsx +15 -8
- package/src/provider.tsx +92 -12
- 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 +18 -4
- package/src/tooltip.tsx +4 -23
- package/src/use-narrow.ts +40 -0
- package/src/vite.ts +78 -0
- package/src/white-label.ts +240 -0
- package/tokens.css +544 -319
- package/src/tokens.css +0 -438
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createGui } from '@hanzo/gui';
|
|
4
|
+
import { defaultConfig } from '@hanzogui/config/v5';
|
|
5
|
+
import { animations } from '@hanzogui/config/v5-css';
|
|
6
|
+
|
|
7
|
+
// Branding = VALUE, not place: the brand hex table lives in ./white-label,
|
|
8
|
+
// beside the identity rows it colours, so reading a brand never drags the gui
|
|
9
|
+
// engine along. Re-exported here because this module is where the themes are
|
|
10
|
+
// built from it.
|
|
11
|
+
export { LUX_BRAND, PARS_GOLD, type LuxBrand } from './white-label';
|
|
12
|
+
import { LUX_BRAND, PARS_GOLD } from './white-label';
|
|
13
|
+
|
|
14
|
+
// Shared true-black neutral base: take @hanzogui's resolved dark theme and
|
|
15
|
+
// force a pure-black canvas so every Lux surface sits on the same ground.
|
|
16
|
+
const TRUE_BLACK = '#000000';
|
|
17
|
+
|
|
18
|
+
const darkBase = {
|
|
19
|
+
...defaultConfig.themes.dark,
|
|
20
|
+
background: TRUE_BLACK,
|
|
21
|
+
backgroundHover: '#0A0A0A',
|
|
22
|
+
backgroundPress: '#141414',
|
|
23
|
+
backgroundFocus: '#0A0A0A',
|
|
24
|
+
} as typeof defaultConfig.themes.dark;
|
|
25
|
+
|
|
26
|
+
// A brand child-theme row = the true-black base with the brand accent woven
|
|
27
|
+
// into the accent-carrying keys. `<Theme name="lux">` under a dark parent
|
|
28
|
+
// resolves to `dark_lux`.
|
|
29
|
+
const brandTheme = (
|
|
30
|
+
accent: string,
|
|
31
|
+
extra: Record<string, string> = {},
|
|
32
|
+
): typeof darkBase =>
|
|
33
|
+
({
|
|
34
|
+
...darkBase,
|
|
35
|
+
accentBackground: accent,
|
|
36
|
+
accentColor: '#FFFFFF',
|
|
37
|
+
borderColorHover: accent,
|
|
38
|
+
colorHover: accent,
|
|
39
|
+
...extra,
|
|
40
|
+
}) as typeof darkBase;
|
|
41
|
+
|
|
42
|
+
export const luxThemes = {
|
|
43
|
+
dark: darkBase,
|
|
44
|
+
dark_lux: brandTheme(LUX_BRAND.lux),
|
|
45
|
+
dark_zoo: brandTheme(LUX_BRAND.zoo),
|
|
46
|
+
dark_hanzo: brandTheme(LUX_BRAND.hanzo),
|
|
47
|
+
dark_pars: brandTheme(LUX_BRAND.pars, { accentColor: PARS_GOLD }),
|
|
48
|
+
dark_bootnode: brandTheme(LUX_BRAND.bootnode),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// One engine, tokens-not-forks: reuse @hanzogui's v5 defaultConfig
|
|
52
|
+
// (tokens / fonts / media / shorthands) and only add the Lux child-theme rows
|
|
53
|
+
// over it. The lone settings override — `onlyAllowShorthands: false` — keeps the
|
|
54
|
+
// harvested exchange primitives (which author with longhand style props like
|
|
55
|
+
// `borderRadius` / `paddingHorizontal`) valid; v5 defaults to shorthands-only.
|
|
56
|
+
// `@hanzogui/config/v5` ships WITHOUT an animation driver — "users must provide
|
|
57
|
+
// their own". Sheet is not merely unanimated without one, it throws
|
|
58
|
+
// (`Sheet requires an animation driver to be set`), and every `transition` prop
|
|
59
|
+
// on every other primitive is inert. Feeding the CSS driver here is what earns
|
|
60
|
+
// the motion — one wiring for the whole fleet, not one per app.
|
|
61
|
+
// Native surfaces can swap `@hanzogui/config/v5-css` for `/v5-rn` without
|
|
62
|
+
// touching a component: the transition token names are identical.
|
|
63
|
+
export const config = createGui({
|
|
64
|
+
...defaultConfig,
|
|
65
|
+
animations,
|
|
66
|
+
settings: {
|
|
67
|
+
...defaultConfig.settings,
|
|
68
|
+
onlyAllowShorthands: false,
|
|
69
|
+
},
|
|
70
|
+
themes: {
|
|
71
|
+
...defaultConfig.themes,
|
|
72
|
+
...luxThemes,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type Conf = typeof config;
|
|
77
|
+
|
|
78
|
+
declare module '@hanzo/gui' {
|
|
79
|
+
interface GuiCustomConfig extends Conf {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default config;
|
package/src/menu.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import { Check, ChevronRight } from 'lucide-react';
|
|
6
6
|
|
|
7
7
|
import { cn } from './utils';
|
|
8
8
|
|
|
@@ -356,7 +356,7 @@ export const MenuCheckboxItem = React.forwardRef<
|
|
|
356
356
|
>
|
|
357
357
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
358
358
|
<DropdownMenu.ItemIndicator>
|
|
359
|
-
<
|
|
359
|
+
<Check className="h-4 w-4"/>
|
|
360
360
|
</DropdownMenu.ItemIndicator>
|
|
361
361
|
</span>
|
|
362
362
|
{ children }
|
|
@@ -414,7 +414,7 @@ export const MenuRadioItem = React.forwardRef<
|
|
|
414
414
|
>
|
|
415
415
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
416
416
|
<DropdownMenu.ItemIndicator>
|
|
417
|
-
<
|
|
417
|
+
<Check className="h-4 w-4"/>
|
|
418
418
|
</DropdownMenu.ItemIndicator>
|
|
419
419
|
</span>
|
|
420
420
|
<span>{ children }</span>
|
|
@@ -464,7 +464,7 @@ export const MenuTriggerItem = React.forwardRef<
|
|
|
464
464
|
>
|
|
465
465
|
{ startIcon }
|
|
466
466
|
<span className="flex-1">{ children }</span>
|
|
467
|
-
<
|
|
467
|
+
<ChevronRight className="h-4 w-4"/>
|
|
468
468
|
</DropdownMenu.SubTrigger>
|
|
469
469
|
</DropdownMenu.Sub>
|
|
470
470
|
);
|
package/src/next.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// The bundler wiring the gui engine needs on Next, as ONE wrapper.
|
|
2
|
+
//
|
|
3
|
+
// The Next twin of `@luxfi/ui/vite`. @hanzo/gui is authored against React
|
|
4
|
+
// Native primitives, so a Next build has to transpile the engine (it ships
|
|
5
|
+
// untranspiled ESM), alias `react-native` → `react-native-web` in BOTH
|
|
6
|
+
// bundlers Next now uses, and define the platform flags the engine reads at
|
|
7
|
+
// module scope.
|
|
8
|
+
//
|
|
9
|
+
// const { withLuxUi } = require('@luxfi/ui/next')
|
|
10
|
+
// module.exports = withLuxUi({ /* your config */ })
|
|
11
|
+
//
|
|
12
|
+
// Every Next surface needed the identical incantation and every one that
|
|
13
|
+
// hand-rolled it transpiled a DIFFERENT subset of the engine — which is how
|
|
14
|
+
// one app renders a Sheet and the next throws `createContext of undefined` on
|
|
15
|
+
// the same component. The list below is the umbrella's own export list, in one
|
|
16
|
+
// place, so it cannot drift per app.
|
|
17
|
+
//
|
|
18
|
+
// Node-only (a build-time module) and dependency-free: it takes and returns a
|
|
19
|
+
// plain object, so it does not force `next` into anyone's type graph.
|
|
20
|
+
|
|
21
|
+
/** Every package `@hanzo/gui` re-exports — the engine, in full. */
|
|
22
|
+
const GUI_PACKAGES = [
|
|
23
|
+
'@hanzo/gui',
|
|
24
|
+
'@hanzogui/accordion',
|
|
25
|
+
'@hanzogui/adapt',
|
|
26
|
+
'@hanzogui/alert-dialog',
|
|
27
|
+
'@hanzogui/animate',
|
|
28
|
+
'@hanzogui/animate-presence',
|
|
29
|
+
'@hanzogui/avatar',
|
|
30
|
+
'@hanzogui/button',
|
|
31
|
+
'@hanzogui/card',
|
|
32
|
+
'@hanzogui/checkbox',
|
|
33
|
+
'@hanzogui/collapsible',
|
|
34
|
+
'@hanzogui/component-helpers',
|
|
35
|
+
'@hanzogui/compose-refs',
|
|
36
|
+
'@hanzogui/config',
|
|
37
|
+
'@hanzogui/context-menu',
|
|
38
|
+
'@hanzogui/core',
|
|
39
|
+
'@hanzogui/create-context',
|
|
40
|
+
'@hanzogui/create-menu',
|
|
41
|
+
'@hanzogui/dialog',
|
|
42
|
+
'@hanzogui/element',
|
|
43
|
+
'@hanzogui/elements',
|
|
44
|
+
'@hanzogui/font-size',
|
|
45
|
+
'@hanzogui/form',
|
|
46
|
+
'@hanzogui/group',
|
|
47
|
+
'@hanzogui/image',
|
|
48
|
+
'@hanzogui/input',
|
|
49
|
+
'@hanzogui/label',
|
|
50
|
+
'@hanzogui/list-item',
|
|
51
|
+
'@hanzogui/menu',
|
|
52
|
+
'@hanzogui/popover',
|
|
53
|
+
'@hanzogui/popper',
|
|
54
|
+
'@hanzogui/portal',
|
|
55
|
+
'@hanzogui/progress',
|
|
56
|
+
'@hanzogui/radio-group',
|
|
57
|
+
'@hanzogui/react-native-media-driver',
|
|
58
|
+
'@hanzogui/scroll-view',
|
|
59
|
+
'@hanzogui/select',
|
|
60
|
+
'@hanzogui/separator',
|
|
61
|
+
'@hanzogui/shapes',
|
|
62
|
+
'@hanzogui/sheet',
|
|
63
|
+
'@hanzogui/slider',
|
|
64
|
+
'@hanzogui/spacer',
|
|
65
|
+
'@hanzogui/spinner',
|
|
66
|
+
'@hanzogui/stacks',
|
|
67
|
+
'@hanzogui/switch',
|
|
68
|
+
'@hanzogui/tabs',
|
|
69
|
+
'@hanzogui/text',
|
|
70
|
+
'@hanzogui/theme',
|
|
71
|
+
'@hanzogui/toast',
|
|
72
|
+
'@hanzogui/toggle-group',
|
|
73
|
+
'@hanzogui/tooltip',
|
|
74
|
+
'@hanzogui/use-controllable-state',
|
|
75
|
+
'@hanzogui/use-debounce',
|
|
76
|
+
'@hanzogui/use-force-update',
|
|
77
|
+
'@hanzogui/use-window-dimensions',
|
|
78
|
+
'@hanzogui/visually-hidden',
|
|
79
|
+
'@hanzogui/web',
|
|
80
|
+
'@luxfi/ui',
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
/** The engine branches on these at module scope. */
|
|
84
|
+
const DEFINES: Record<string, string> = {
|
|
85
|
+
'process.env.EXPO_OS': JSON.stringify('web'),
|
|
86
|
+
'process.env.IS_WEB': JSON.stringify('true'),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
interface WebpackConfig {
|
|
90
|
+
resolve?: { alias?: Record<string, unknown> };
|
|
91
|
+
plugins?: Array<unknown>;
|
|
92
|
+
[key: string]: unknown;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The structural slice of a Next config this wrapper touches. */
|
|
96
|
+
export interface LuxUiNextConfig {
|
|
97
|
+
transpilePackages?: Array<string>;
|
|
98
|
+
turbopack?: { resolveAlias?: Record<string, string>; [key: string]: unknown };
|
|
99
|
+
env?: Record<string, string>;
|
|
100
|
+
webpack?: (config: WebpackConfig, context: unknown) => WebpackConfig;
|
|
101
|
+
[key: string]: unknown;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Wrap a Next config so `@luxfi/ui` and the gui engine build.
|
|
106
|
+
*
|
|
107
|
+
* Additive: an existing `transpilePackages`, `turbopack.resolveAlias`,
|
|
108
|
+
* `env` and `webpack()` are all preserved and extended, never replaced.
|
|
109
|
+
*/
|
|
110
|
+
export function withLuxUi(config: LuxUiNextConfig = {}): LuxUiNextConfig {
|
|
111
|
+
const userWebpack = config.webpack;
|
|
112
|
+
return {
|
|
113
|
+
...config,
|
|
114
|
+
transpilePackages: [ ...new Set([ ...(config.transpilePackages ?? []), ...GUI_PACKAGES ]) ],
|
|
115
|
+
turbopack: {
|
|
116
|
+
...config.turbopack,
|
|
117
|
+
resolveAlias: {
|
|
118
|
+
'react-native': 'react-native-web',
|
|
119
|
+
...config.turbopack?.resolveAlias,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
// Next inlines `env` into the client bundle, which is how the engine's
|
|
123
|
+
// module-scope platform checks get their answer under Turbopack too.
|
|
124
|
+
env: { EXPO_OS: 'web', IS_WEB: 'true', ...config.env },
|
|
125
|
+
webpack(webpackConfig: WebpackConfig, context: unknown) {
|
|
126
|
+
const next = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
|
|
127
|
+
next.resolve = {
|
|
128
|
+
...next.resolve,
|
|
129
|
+
alias: { ...next.resolve?.alias, 'react-native$': 'react-native-web' },
|
|
130
|
+
};
|
|
131
|
+
// `context` is Next's `{ webpack }` bag — use ITS webpack instance so the
|
|
132
|
+
// DefinePlugin is the one running the build.
|
|
133
|
+
const webpackModule = (context as { webpack?: { DefinePlugin?: new (d: Record<string, string>) => unknown } })?.webpack;
|
|
134
|
+
if (webpackModule?.DefinePlugin) {
|
|
135
|
+
next.plugins = [ ...(next.plugins ?? []), new webpackModule.DefinePlugin(DEFINES) ];
|
|
136
|
+
}
|
|
137
|
+
return next;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export { GUI_PACKAGES };
|
package/src/option-chain.tsx
CHANGED
|
@@ -226,7 +226,7 @@ export const OptionChain = React.forwardRef<HTMLDivElement, OptionChainProps>(
|
|
|
226
226
|
<td
|
|
227
227
|
className={cn(
|
|
228
228
|
'px-2 py-1 text-center font-mono tabular-nums text-xs font-semibold',
|
|
229
|
-
isATM ? 'text-
|
|
229
|
+
isATM ? 'text-[var(--foreground)] bg-[var(--secondary)]' : 'text-[var(--muted-foreground)]',
|
|
230
230
|
)}
|
|
231
231
|
>
|
|
232
232
|
{fmt(row.strike)}
|
package/src/option-position.tsx
CHANGED
|
@@ -77,7 +77,8 @@ export const OptionPositionCard = React.forwardRef<HTMLDivElement, OptionPositio
|
|
|
77
77
|
{/* Symbol badge */}
|
|
78
78
|
<span className={cn(
|
|
79
79
|
'inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-semibold',
|
|
80
|
-
|
|
80
|
+
// Call vs put is a rank difference, not a hue difference.
|
|
81
|
+
isCall ? 'bg-[var(--secondary)] text-[var(--foreground)]' : 'bg-[var(--muted)] text-[var(--muted-foreground)]',
|
|
81
82
|
)}>
|
|
82
83
|
{position.underlying}
|
|
83
84
|
<span className="font-mono tabular-nums">{position.strike}</span>
|
package/src/pnl-diagram.tsx
CHANGED
|
@@ -66,7 +66,12 @@ function buildPath(points: Array<{ x: number; y: number }>, vb: ViewBox, width:
|
|
|
66
66
|
// Leg colors
|
|
67
67
|
// ---------------------------------------------------------------------------
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// Legs are told apart by position on the achromatic chart ramp, not by hue —
|
|
70
|
+
// and they resolve through tokens.css, so a host reskin moves them too.
|
|
71
|
+
const LEG_COLORS = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)'];
|
|
72
|
+
// Breakeven markers ride the same ramp; the ring is the page ground.
|
|
73
|
+
const BREAKEVEN_COLOR = 'var(--chart-1)';
|
|
74
|
+
const BREAKEVEN_RING = 'var(--background)';
|
|
70
75
|
|
|
71
76
|
// ---------------------------------------------------------------------------
|
|
72
77
|
// PnlDiagram
|
|
@@ -323,10 +328,10 @@ export const PnlDiagram = React.forwardRef<HTMLDivElement, PnlDiagramProps>(
|
|
|
323
328
|
{/* Breakeven markers */}
|
|
324
329
|
{breakevens.map((be, i) => (
|
|
325
330
|
<g key={`be-${i}`}>
|
|
326
|
-
<circle cx={scaleX(be)} cy={zeroY} r="3" fill=
|
|
331
|
+
<circle cx={scaleX(be)} cy={zeroY} r="3" fill={BREAKEVEN_COLOR} stroke={BREAKEVEN_RING} strokeWidth="1" />
|
|
327
332
|
<text
|
|
328
333
|
x={scaleX(be)} y={PADDING.top + chartH + 14}
|
|
329
|
-
fill=
|
|
334
|
+
fill={BREAKEVEN_COLOR} fontSize="8" fontFamily="monospace" textAnchor="middle"
|
|
330
335
|
>
|
|
331
336
|
{be.toFixed(1)}
|
|
332
337
|
</text>
|
|
@@ -366,7 +371,7 @@ export const PnlDiagram = React.forwardRef<HTMLDivElement, PnlDiagramProps>(
|
|
|
366
371
|
cy={scaleY(hoverPnl)}
|
|
367
372
|
r="3"
|
|
368
373
|
fill={hoverPnl >= 0 ? '#22c55e' : '#ef4444'}
|
|
369
|
-
stroke=
|
|
374
|
+
stroke={BREAKEVEN_RING}
|
|
370
375
|
strokeWidth="1"
|
|
371
376
|
/>
|
|
372
377
|
{/* Tooltip */}
|
|
@@ -376,7 +381,7 @@ export const PnlDiagram = React.forwardRef<HTMLDivElement, PnlDiagramProps>(
|
|
|
376
381
|
width="90"
|
|
377
382
|
height="28"
|
|
378
383
|
rx="3"
|
|
379
|
-
fill=
|
|
384
|
+
fill={BREAKEVEN_RING}
|
|
380
385
|
stroke="#3f3f46"
|
|
381
386
|
strokeWidth="1"
|
|
382
387
|
/>
|
|
@@ -445,7 +450,7 @@ export const PnlDiagram = React.forwardRef<HTMLDivElement, PnlDiagramProps>(
|
|
|
445
450
|
))}
|
|
446
451
|
{breakevens.length > 0 && (
|
|
447
452
|
<div className="flex items-center gap-1.5">
|
|
448
|
-
<div className="h-1.5 w-1.5 rounded-full
|
|
453
|
+
<div className="h-1.5 w-1.5 rounded-full" style={{ background: BREAKEVEN_COLOR }} />
|
|
449
454
|
<span className="text-[10px] text-zinc-500">
|
|
450
455
|
BE: {breakevens.map((b) => b.toFixed(1)).join(', ')}
|
|
451
456
|
</span>
|
package/src/popover.tsx
CHANGED
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
|
@@ -1,30 +1,110 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { GuiProvider } from '@hanzo/gui';
|
|
4
|
+
import {
|
|
5
|
+
QueryClient,
|
|
6
|
+
QueryClientProvider,
|
|
7
|
+
useQueryClient,
|
|
8
|
+
useQuery,
|
|
9
|
+
useMutation,
|
|
10
|
+
useInfiniteQuery,
|
|
11
|
+
} from '@tanstack/react-query';
|
|
5
12
|
import React from 'react';
|
|
6
13
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// before useEffect fires. 7.3.1 used useEffect which was too late (Err0).
|
|
10
|
-
if (typeof window !== 'undefined') {
|
|
11
|
-
createGui({ settings: { autocompleteSpecificTokens: 'except-special' } });
|
|
12
|
-
}
|
|
14
|
+
import config from './lux.config';
|
|
15
|
+
import { currentHost, resolveWhiteLabel, type WhiteLabel } from './white-label';
|
|
13
16
|
|
|
14
17
|
const defaultQueryClient = new QueryClient({
|
|
15
18
|
defaultOptions: { queries: { refetchOnWindowFocus: false, retry: 1, staleTime: 30_000 } },
|
|
16
19
|
});
|
|
17
20
|
|
|
21
|
+
// --- Tailwind-Preflight baseline reset -------------------------------------
|
|
22
|
+
// @hanzo/gui's GuiProvider scopes its subtree in a Tamagui `font_body` context
|
|
23
|
+
// whose shared CSS rule pins an ABSOLUTE line-height (23px). That cascades into
|
|
24
|
+
// consumer text authored at an arbitrary size with no line-height utility of its
|
|
25
|
+
// own (e.g. `text-[11px]` labels), making each line too tall and reflowing dense
|
|
26
|
+
// layouts. Restore Tailwind Preflight's `line-height: 1.5` baseline at
|
|
27
|
+
// `body .font_body` specificity — proportional, so any element carrying its own
|
|
28
|
+
// text-* / leading utility is unaffected. Shipping it from AppProvider means
|
|
29
|
+
// every Lux surface inherits the fix with no per-app CSS pin. Web-only (there is
|
|
30
|
+
// no `document` on native), injected once via useInsertionEffect (React's
|
|
31
|
+
// primitive for style insertion — runs before the browser paints, so no reflow
|
|
32
|
+
// flash), idempotent by element id.
|
|
33
|
+
const FONT_BODY_RESET_ID = 'lux-ui-font-body-reset';
|
|
34
|
+
const FONT_BODY_RESET_CSS = 'body .font_body{line-height:1.5}';
|
|
35
|
+
|
|
36
|
+
const useFontBodyReset = (): void => {
|
|
37
|
+
React.useInsertionEffect(() => {
|
|
38
|
+
if (typeof document === 'undefined') return;
|
|
39
|
+
if (document.getElementById(FONT_BODY_RESET_ID)) return;
|
|
40
|
+
const style = document.createElement('style');
|
|
41
|
+
style.id = FONT_BODY_RESET_ID;
|
|
42
|
+
style.textContent = FONT_BODY_RESET_CSS;
|
|
43
|
+
document.head.appendChild(style);
|
|
44
|
+
}, []);
|
|
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
|
+
|
|
18
73
|
interface AppProviderProps {
|
|
19
74
|
children: React.ReactNode;
|
|
20
75
|
queryClient?: QueryClient;
|
|
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
|
+
*/
|
|
86
|
+
defaultTheme?: string;
|
|
21
87
|
}
|
|
22
88
|
|
|
23
|
-
|
|
89
|
+
// AppProvider wraps GuiProvider (the @hanzogui engine, fed the Lux config) +
|
|
90
|
+
// tanstack QueryClientProvider. One provider for every Lux surface.
|
|
91
|
+
export const AppProvider = ({
|
|
92
|
+
children,
|
|
93
|
+
queryClient,
|
|
94
|
+
host,
|
|
95
|
+
defaultTheme,
|
|
96
|
+
}: AppProviderProps): React.ReactElement => {
|
|
97
|
+
useFontBodyReset();
|
|
98
|
+
const whiteLabel = useHostWhiteLabel(host);
|
|
99
|
+
const theme = defaultTheme ?? whiteLabel.theme;
|
|
24
100
|
return (
|
|
25
|
-
<
|
|
26
|
-
{
|
|
27
|
-
|
|
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>
|
|
28
108
|
);
|
|
29
109
|
};
|
|
30
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
|
},
|