@luxfi/ui 7.4.2 → 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/badge.cjs +3 -3
- package/dist/badge.js +3 -3
- package/dist/chrome.cjs +19 -9
- package/dist/chrome.js +19 -9
- package/dist/iam.cjs +19 -9
- package/dist/iam.js +19 -9
- package/dist/identity.cjs +19 -9
- package/dist/identity.js +19 -9
- package/dist/index.cjs +22 -12
- package/dist/index.js +22 -12
- package/dist/next.cjs +38 -5
- package/dist/next.d.cts +4 -1
- package/dist/next.d.ts +4 -1
- package/dist/next.js +38 -5
- package/dist/provider.cjs +19 -9
- package/dist/provider.js +19 -9
- package/dist/tag.cjs +3 -3
- package/dist/tag.js +3 -3
- package/dist/tooltip.cjs +3 -3
- package/dist/tooltip.js +3 -3
- package/dist/vite.cjs +77 -8
- package/dist/vite.js +77 -8
- package/dist/white-label.cjs +19 -9
- package/dist/white-label.d.cts +3 -3
- package/dist/white-label.d.ts +3 -3
- package/dist/white-label.js +19 -9
- package/package.json +1 -1
- package/src/engine.ts +130 -0
- package/src/next.ts +17 -70
- package/src/tooltip.tsx +13 -3
- package/src/vite.ts +10 -8
- package/src/white-label.ts +32 -18
- package/tokens.css +75 -6
package/package.json
CHANGED
package/src/engine.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// The gui engine, named once.
|
|
2
|
+
//
|
|
3
|
+
// @hanzo/gui is a facade over ~55 `@hanzogui/*` packages, and several of them
|
|
4
|
+
// (`popper`, `popover`, `core`, `floating`) publish React contexts at MODULE
|
|
5
|
+
// scope. Two physical copies of such a package are two different context
|
|
6
|
+
// objects, so a provider from copy A is invisible to a consumer from copy B and
|
|
7
|
+
// the consumer silently falls back to the context's default value.
|
|
8
|
+
//
|
|
9
|
+
// That is not a theoretical hazard, it is the shape of the loudest bug this
|
|
10
|
+
// package has shipped: `PopperAnchor` reads `refs` off the Popper context and
|
|
11
|
+
// calls `refs.setReference(node)` from a ref callback with no guard, so when the
|
|
12
|
+
// context is the empty default the browser logs
|
|
13
|
+
//
|
|
14
|
+
// Cannot read properties of undefined (reading 'setReference')
|
|
15
|
+
//
|
|
16
|
+
// once per tooltip trigger, uncaught (it runs inside `startTransition`, so no
|
|
17
|
+
// error boundary sees it). A page with a tooltip per table row logs it hundreds
|
|
18
|
+
// of times. The gui engine itself diagnoses the same root cause out loud when
|
|
19
|
+
// the duplication reaches its config lookup: "Can't find Hanzo GUI
|
|
20
|
+
// configuration … due to having mis-matched versions of Hanzo GUI dependencies,
|
|
21
|
+
// or bundlers somehow duplicating them."
|
|
22
|
+
//
|
|
23
|
+
// So the engine list is a VALUE, declared once here, and both bundler wrappers
|
|
24
|
+
// (`@luxfi/ui/vite`, `@luxfi/ui/next`) dedupe against it. A consumer that uses
|
|
25
|
+
// the wrapper cannot end up with two copies, and does not have to hand-write a
|
|
26
|
+
// per-app `pnpm.overrides` block pinning three dozen packages to stop it.
|
|
27
|
+
|
|
28
|
+
/** Every package `@hanzo/gui` re-exports — the engine, in full. */
|
|
29
|
+
export const GUI_PACKAGES: ReadonlyArray<string> = [
|
|
30
|
+
'@hanzo/gui',
|
|
31
|
+
'@hanzogui/accordion',
|
|
32
|
+
'@hanzogui/adapt',
|
|
33
|
+
'@hanzogui/alert-dialog',
|
|
34
|
+
'@hanzogui/animate',
|
|
35
|
+
'@hanzogui/animate-presence',
|
|
36
|
+
'@hanzogui/avatar',
|
|
37
|
+
'@hanzogui/button',
|
|
38
|
+
'@hanzogui/card',
|
|
39
|
+
'@hanzogui/checkbox',
|
|
40
|
+
'@hanzogui/collapsible',
|
|
41
|
+
'@hanzogui/component-helpers',
|
|
42
|
+
'@hanzogui/compose-refs',
|
|
43
|
+
'@hanzogui/config',
|
|
44
|
+
'@hanzogui/context-menu',
|
|
45
|
+
'@hanzogui/core',
|
|
46
|
+
'@hanzogui/create-context',
|
|
47
|
+
'@hanzogui/create-menu',
|
|
48
|
+
'@hanzogui/dialog',
|
|
49
|
+
'@hanzogui/dismissable',
|
|
50
|
+
'@hanzogui/element',
|
|
51
|
+
'@hanzogui/elements',
|
|
52
|
+
'@hanzogui/floating',
|
|
53
|
+
'@hanzogui/focus-scope',
|
|
54
|
+
'@hanzogui/font-size',
|
|
55
|
+
'@hanzogui/form',
|
|
56
|
+
'@hanzogui/get-token',
|
|
57
|
+
'@hanzogui/group',
|
|
58
|
+
'@hanzogui/helpers',
|
|
59
|
+
'@hanzogui/image',
|
|
60
|
+
'@hanzogui/input',
|
|
61
|
+
'@hanzogui/label',
|
|
62
|
+
'@hanzogui/list-item',
|
|
63
|
+
'@hanzogui/menu',
|
|
64
|
+
'@hanzogui/popover',
|
|
65
|
+
'@hanzogui/popper',
|
|
66
|
+
'@hanzogui/portal',
|
|
67
|
+
'@hanzogui/progress',
|
|
68
|
+
'@hanzogui/radio-group',
|
|
69
|
+
'@hanzogui/react-native-media-driver',
|
|
70
|
+
'@hanzogui/remove-scroll',
|
|
71
|
+
'@hanzogui/scroll-view',
|
|
72
|
+
'@hanzogui/select',
|
|
73
|
+
'@hanzogui/separator',
|
|
74
|
+
'@hanzogui/shapes',
|
|
75
|
+
'@hanzogui/sheet',
|
|
76
|
+
'@hanzogui/slider',
|
|
77
|
+
'@hanzogui/spacer',
|
|
78
|
+
'@hanzogui/spinner',
|
|
79
|
+
'@hanzogui/stacks',
|
|
80
|
+
'@hanzogui/switch',
|
|
81
|
+
'@hanzogui/tabs',
|
|
82
|
+
'@hanzogui/text',
|
|
83
|
+
'@hanzogui/theme',
|
|
84
|
+
'@hanzogui/toast',
|
|
85
|
+
'@hanzogui/toggle-group',
|
|
86
|
+
'@hanzogui/tooltip',
|
|
87
|
+
'@hanzogui/use-controllable-state',
|
|
88
|
+
'@hanzogui/use-debounce',
|
|
89
|
+
'@hanzogui/use-force-update',
|
|
90
|
+
'@hanzogui/use-presence',
|
|
91
|
+
'@hanzogui/use-window-dimensions',
|
|
92
|
+
'@hanzogui/visually-hidden',
|
|
93
|
+
'@hanzogui/web',
|
|
94
|
+
'@hanzogui/z-index-stack',
|
|
95
|
+
'@luxfi/ui',
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The engine branches on these at module scope; without them a browser bundle
|
|
100
|
+
* takes the native path.
|
|
101
|
+
*/
|
|
102
|
+
export const GUI_DEFINES: Readonly<Record<string, string>> = {
|
|
103
|
+
'process.env.EXPO_OS': JSON.stringify('web'),
|
|
104
|
+
'process.env.IS_WEB': JSON.stringify('true'),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Exact-match aliases pinning every engine package to ONE entry file, for
|
|
109
|
+
* bundlers that have no `dedupe` of their own (webpack).
|
|
110
|
+
*
|
|
111
|
+
* Keys carry webpack's `$` exact-match suffix so only the bare specifier is
|
|
112
|
+
* redirected — a deep import keeps resolving normally. Packages that are not
|
|
113
|
+
* physically installed are skipped rather than throwing: the list is the
|
|
114
|
+
* engine's full surface and no single app pulls all of it.
|
|
115
|
+
*/
|
|
116
|
+
export function guiAliases(from: string): Record<string, string> {
|
|
117
|
+
// Deferred so this module stays importable from a browser bundle (the
|
|
118
|
+
// wrappers are build-time only, but `engine.ts` is plain values).
|
|
119
|
+
const { createRequire } = require('node:module') as typeof import('node:module');
|
|
120
|
+
const require_ = createRequire(`${ from.replace(/\/?$/, '/') }noop.js`);
|
|
121
|
+
const alias: Record<string, string> = {};
|
|
122
|
+
for (const pkg of GUI_PACKAGES) {
|
|
123
|
+
try {
|
|
124
|
+
alias[`${ pkg }$`] = require_.resolve(pkg);
|
|
125
|
+
} catch {
|
|
126
|
+
// Not installed in this app — nothing to dedupe.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return alias;
|
|
130
|
+
}
|
package/src/next.ts
CHANGED
|
@@ -18,73 +18,7 @@
|
|
|
18
18
|
// Node-only (a build-time module) and dependency-free: it takes and returns a
|
|
19
19
|
// plain object, so it does not force `next` into anyone's type graph.
|
|
20
20
|
|
|
21
|
-
|
|
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
|
-
};
|
|
21
|
+
import { guiAliases, GUI_DEFINES, GUI_PACKAGES } from './engine';
|
|
88
22
|
|
|
89
23
|
interface WebpackConfig {
|
|
90
24
|
resolve?: { alias?: Record<string, unknown> };
|
|
@@ -94,6 +28,8 @@ interface WebpackConfig {
|
|
|
94
28
|
|
|
95
29
|
/** The structural slice of a Next config this wrapper touches. */
|
|
96
30
|
export interface LuxUiNextConfig {
|
|
31
|
+
/** App root, for resolving the engine's single copy. Defaults to `cwd`. */
|
|
32
|
+
dir?: string;
|
|
97
33
|
transpilePackages?: Array<string>;
|
|
98
34
|
turbopack?: { resolveAlias?: Record<string, string>; [key: string]: unknown };
|
|
99
35
|
env?: Record<string, string>;
|
|
@@ -109,8 +45,16 @@ export interface LuxUiNextConfig {
|
|
|
109
45
|
*/
|
|
110
46
|
export function withLuxUi(config: LuxUiNextConfig = {}): LuxUiNextConfig {
|
|
111
47
|
const userWebpack = config.webpack;
|
|
48
|
+
// ONE physical copy of every engine package. Webpack has no `resolve.dedupe`,
|
|
49
|
+
// so the equivalent is an exact-match alias per package, resolved from the app
|
|
50
|
+
// root — see ./engine for why two copies is a crash and not a size problem.
|
|
51
|
+
const engine = guiAliases(config.dir ?? process.cwd());
|
|
52
|
+
// `dir` is OURS, not Next's — leaving it on the returned object makes Next
|
|
53
|
+
// print `Unrecognized key(s) in object: 'dir'` on every boot of every surface
|
|
54
|
+
// that passes it. Consume it here.
|
|
55
|
+
const { dir: _dir, ...rest } = config;
|
|
112
56
|
return {
|
|
113
|
-
...
|
|
57
|
+
...rest,
|
|
114
58
|
transpilePackages: [ ...new Set([ ...(config.transpilePackages ?? []), ...GUI_PACKAGES ]) ],
|
|
115
59
|
turbopack: {
|
|
116
60
|
...config.turbopack,
|
|
@@ -126,13 +70,15 @@ export function withLuxUi(config: LuxUiNextConfig = {}): LuxUiNextConfig {
|
|
|
126
70
|
const next = userWebpack ? userWebpack(webpackConfig, context) : webpackConfig;
|
|
127
71
|
next.resolve = {
|
|
128
72
|
...next.resolve,
|
|
129
|
-
|
|
73
|
+
// The app's own aliases win — an app pinning a package on purpose is not
|
|
74
|
+
// something the umbrella should override.
|
|
75
|
+
alias: { 'react-native$': 'react-native-web', ...engine, ...next.resolve?.alias },
|
|
130
76
|
};
|
|
131
77
|
// `context` is Next's `{ webpack }` bag — use ITS webpack instance so the
|
|
132
78
|
// DefinePlugin is the one running the build.
|
|
133
79
|
const webpackModule = (context as { webpack?: { DefinePlugin?: new (d: Record<string, string>) => unknown } })?.webpack;
|
|
134
80
|
if (webpackModule?.DefinePlugin) {
|
|
135
|
-
next.plugins = [ ...(next.plugins ?? []), new webpackModule.DefinePlugin(
|
|
81
|
+
next.plugins = [ ...(next.plugins ?? []), new webpackModule.DefinePlugin({ ...GUI_DEFINES }) ];
|
|
136
82
|
}
|
|
137
83
|
return next;
|
|
138
84
|
},
|
|
@@ -140,3 +86,4 @@ export function withLuxUi(config: LuxUiNextConfig = {}): LuxUiNextConfig {
|
|
|
140
86
|
}
|
|
141
87
|
|
|
142
88
|
export { GUI_PACKAGES };
|
|
89
|
+
|
package/src/tooltip.tsx
CHANGED
|
@@ -82,9 +82,12 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
|
82
82
|
}, nextOpen ? openDelay : closeDelay);
|
|
83
83
|
}, [ closeDelay, openDelay, onOpenChange ]);
|
|
84
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.
|
|
85
88
|
const handleClickAway = React.useCallback(() => {
|
|
86
|
-
handleOpenChangeManual(false);
|
|
87
|
-
}, [ handleOpenChangeManual ]);
|
|
89
|
+
if (open) handleOpenChangeManual(false);
|
|
90
|
+
}, [ open, handleOpenChangeManual ]);
|
|
88
91
|
|
|
89
92
|
const triggerRef = useClickAway<HTMLButtonElement>(handleClickAway);
|
|
90
93
|
|
|
@@ -132,7 +135,14 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
|
|
|
132
135
|
unstyled
|
|
133
136
|
>
|
|
134
137
|
<GuiTooltip.Trigger
|
|
135
|
-
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 }
|
|
136
146
|
asChild
|
|
137
147
|
{...(isMobile ? { onPress: handleTriggerClick } : {})}
|
|
138
148
|
{ ...triggerProps as any }
|
package/src/vite.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
// Node-only (it is a build-time module) and dependency-free: it returns a plain
|
|
15
15
|
// Vite plugin object, so it does not force `vite` into anyone's runtime graph.
|
|
16
16
|
|
|
17
|
+
import { GUI_DEFINES, GUI_PACKAGES } from './engine';
|
|
18
|
+
|
|
17
19
|
interface LuxUiViteOptions {
|
|
18
20
|
/**
|
|
19
21
|
* Extra packages to keep to a single physical copy. React, the gui engine and
|
|
@@ -37,13 +39,16 @@ interface LuxUiVitePlugin {
|
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
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.
|
|
40
47
|
const DEDUPE = [
|
|
41
48
|
'react',
|
|
42
49
|
'react-dom',
|
|
43
50
|
'@tanstack/react-query',
|
|
44
|
-
|
|
45
|
-
'@hanzogui/core',
|
|
46
|
-
'@hanzogui/web',
|
|
51
|
+
...GUI_PACKAGES,
|
|
47
52
|
];
|
|
48
53
|
|
|
49
54
|
const INCLUDE = [
|
|
@@ -59,11 +64,8 @@ export function luxUi(options: LuxUiViteOptions = {}): LuxUiVitePlugin {
|
|
|
59
64
|
name: '@luxfi/ui',
|
|
60
65
|
config: () => ({
|
|
61
66
|
// The engine branches on these at module scope; without them it takes the
|
|
62
|
-
// native path in a browser bundle.
|
|
63
|
-
define: {
|
|
64
|
-
'process.env.EXPO_OS': JSON.stringify('web'),
|
|
65
|
-
'process.env.IS_WEB': JSON.stringify('true'),
|
|
66
|
-
},
|
|
67
|
+
// native path in a browser bundle. (./engine)
|
|
68
|
+
define: { ...GUI_DEFINES },
|
|
67
69
|
resolve: {
|
|
68
70
|
alias: { 'react-native': 'react-native-web' },
|
|
69
71
|
dedupe: [ ...new Set([ ...DEDUPE, ...(options.dedupe ?? []) ]) ],
|
package/src/white-label.ts
CHANGED
|
@@ -68,10 +68,17 @@ const ORGS: Readonly<Record<Org, OrgIdentity>> = {
|
|
|
68
68
|
org: 'zoo',
|
|
69
69
|
name: 'Zoo',
|
|
70
70
|
domain: 'zoo.ngo',
|
|
71
|
-
// Zoo's
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
71
|
+
// Zoo's issuer is `zoolabs.id` — the one row where it is not `<org>.id`,
|
|
72
|
+
// and the one every hand-rolled branding map gets wrong. `id.zoo.network`
|
|
73
|
+
// ANSWERS, which is why it keeps being copied around, but it is an alias:
|
|
74
|
+
// its own discovery document advertises
|
|
75
|
+
// issuer https://zoolabs.id
|
|
76
|
+
// authorization_endpoint https://zoolabs.id/v1/iam/oauth/authorize
|
|
77
|
+
// jwks_uri https://zoolabs.id/v1/iam/.well-known/jwks
|
|
78
|
+
// so a client configured on the alias validates `iss` against a string the
|
|
79
|
+
// IdP never emits and rejects every token it is given — the exact shape of
|
|
80
|
+
// the lux.id outage. `zoo.id` is not an IdP at all (no discovery document).
|
|
81
|
+
iamDomain: 'zoolabs.id',
|
|
75
82
|
accent: LUX_BRAND.zoo,
|
|
76
83
|
accentForeground: '#FFFFFF',
|
|
77
84
|
},
|
|
@@ -105,24 +112,31 @@ const ORGS: Readonly<Record<Org, OrgIdentity>> = {
|
|
|
105
112
|
// SUFFIX on a label boundary, so `mpc.lux.cloud` and `lux.cloud` both hit `lux`
|
|
106
113
|
// while `notlux.cloud` does not.
|
|
107
114
|
//
|
|
108
|
-
// The third slot
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
115
|
+
// The third slot PINS the registered app slug for every host under the domain.
|
|
116
|
+
// Most brands register one client per app and the slug is derivable from the
|
|
117
|
+
// hostname — `lux.cloud` → `cloud` → `lux-cloud`, `mpc.lux.network` → `lux-mpc`,
|
|
118
|
+
// both verified 302 against the live authorize endpoint. Some register ONE
|
|
119
|
+
// client for the whole brand, and then the derivation is a guess that 400s:
|
|
120
|
+
// `bootno.de` is `bootnode-platform`, and Pars is `pars-app` on every host
|
|
121
|
+
// (`pars-cloud` answers 400 where `pars-app` answers 302). A client id is a
|
|
122
|
+
// registration, not a guess — so where it is not derivable it is written down.
|
|
112
123
|
const DOMAIN_ORG: ReadonlyArray<readonly [string, Org, string?]> = [
|
|
113
124
|
[ 'lux.network', 'lux' ],
|
|
114
125
|
[ 'lux.cloud', 'lux' ],
|
|
115
126
|
[ 'lux.id', 'lux' ],
|
|
116
127
|
[ 'lux.finance', 'lux' ],
|
|
128
|
+
[ 'lux.market', 'lux' ],
|
|
129
|
+
[ 'lux.exchange', 'lux' ],
|
|
117
130
|
[ 'zoo.ngo', 'zoo' ],
|
|
118
131
|
[ 'zoo.network', 'zoo' ],
|
|
119
132
|
[ 'zoo.cloud', 'zoo' ],
|
|
120
|
-
[ '
|
|
133
|
+
[ 'zoolabs.id', 'zoo' ],
|
|
121
134
|
[ 'hanzo.ai', 'hanzo' ],
|
|
122
135
|
[ 'hanzo.cloud', 'hanzo' ],
|
|
123
136
|
[ 'hanzo.id', 'hanzo' ],
|
|
124
|
-
[ 'pars.id', 'pars' ],
|
|
125
|
-
[ '
|
|
137
|
+
[ 'pars.id', 'pars', 'app' ],
|
|
138
|
+
[ 'pars.network', 'pars', 'app' ],
|
|
139
|
+
[ 'parsdao.org', 'pars', 'app' ],
|
|
126
140
|
[ 'bootno.de', 'bootnode', 'platform' ],
|
|
127
141
|
];
|
|
128
142
|
|
|
@@ -170,15 +184,15 @@ function matchDomain(host: string): readonly [string, Org, string?] | undefined
|
|
|
170
184
|
* `mpc.lux.cloud` → `mpc` (leftmost label)
|
|
171
185
|
* `lux.cloud` → `cloud` (apex: the service name, not the org)
|
|
172
186
|
* `www.lux.network`→ `network` (www is chrome, not an app)
|
|
187
|
+
* `bootno.de` → `platform` (pinned: a registration, not a guess)
|
|
173
188
|
* `localhost` → `local`
|
|
174
189
|
*/
|
|
175
|
-
function appSlug(host: string, domain: string,
|
|
190
|
+
function appSlug(host: string, domain: string, pinned?: string): string {
|
|
191
|
+
if (pinned) return pinned;
|
|
176
192
|
const sub = host === domain ? '' : host.slice(0, -(domain.length + 1));
|
|
177
193
|
const leftmost = sub.split('.')[0] ?? '';
|
|
178
194
|
if (leftmost && leftmost !== 'www') return leftmost;
|
|
179
|
-
// Apex (or www) — the
|
|
180
|
-
// domain's own service label.
|
|
181
|
-
if (apexApp) return apexApp;
|
|
195
|
+
// Apex (or www) — the domain's own service label.
|
|
182
196
|
const label = domain.split('.')[0] ?? '';
|
|
183
197
|
const tld = domain.split('.').slice(1).join('.');
|
|
184
198
|
// `lux.network` → `network`, `lux.cloud` → `cloud`, `pars.id` → `id`.
|
|
@@ -203,9 +217,9 @@ function isLocal(host: string): boolean {
|
|
|
203
217
|
* Pure and total: an unknown or missing host falls back to the Lux row rather
|
|
204
218
|
* than throwing, so a preview URL or a health probe never blanks the UI.
|
|
205
219
|
*
|
|
206
|
-
* resolveWhiteLabel('mpc.lux.
|
|
207
|
-
* resolveWhiteLabel('zoo.
|
|
208
|
-
* resolveWhiteLabel('
|
|
220
|
+
* resolveWhiteLabel('mpc.lux.network') // org lux, clientId lux-mpc, issuer https://lux.id
|
|
221
|
+
* resolveWhiteLabel('zoo.cloud') // org zoo, clientId zoo-cloud, issuer https://zoolabs.id
|
|
222
|
+
* resolveWhiteLabel('cloud.pars.network') // org pars, clientId pars-app, issuer https://pars.id
|
|
209
223
|
*/
|
|
210
224
|
export function resolveWhiteLabel(host?: string | null): WhiteLabel {
|
|
211
225
|
const h = normalizeHost(host ?? '');
|
package/tokens.css
CHANGED
|
@@ -306,9 +306,77 @@
|
|
|
306
306
|
--color-gray-700: var(--lux-n-700);
|
|
307
307
|
--color-gray-800: var(--lux-n-800);
|
|
308
308
|
--color-gray-900: var(--lux-n-900);
|
|
309
|
-
|
|
310
|
-
--color-
|
|
311
|
-
|
|
309
|
+
--color-white: var(--lux-n-0);
|
|
310
|
+
--color-black: var(--lux-n-1000);
|
|
311
|
+
/* The two state ramps — the only rungs in this file that carry a hue.
|
|
312
|
+
Monotone, like the warm ramps, and likewise not flipped in `.dark`. */
|
|
313
|
+
--color-green-50: #F0FFF4;
|
|
314
|
+
--color-green-100: #C6F6D5;
|
|
315
|
+
--color-green-200: #9AE6B4;
|
|
316
|
+
--color-green-500: var(--lux-state-success);
|
|
317
|
+
--color-green-800: #22543D;
|
|
318
|
+
--color-red-50: #FFF5F5;
|
|
319
|
+
--color-red-100: #FED7D7;
|
|
320
|
+
--color-red-200: #FEB2B2;
|
|
321
|
+
--color-red-500: var(--lux-state-error);
|
|
322
|
+
--color-red-600: #C53030;
|
|
323
|
+
--color-red-800: #822727;
|
|
324
|
+
/* ------------------------------------------------------------
|
|
325
|
+
THE WARM RAMPS, DEFANGED.
|
|
326
|
+
Tailwind v4 compiles `bg-orange-100` to `background-color:
|
|
327
|
+
var(--color-orange-100)`, so redefining the rung here retints every
|
|
328
|
+
warm utility on every surface that imports this file — no component
|
|
329
|
+
edit, no rebuild. The ramps stay MONOTONE (low rung = light, high rung
|
|
330
|
+
= dark) in both modes, exactly like Tailwind's, so an author's
|
|
331
|
+
`bg-orange-50 dark:bg-yellow-900` still reads pale-on-light and
|
|
332
|
+
dark-on-dark. They are therefore NOT overridden in `.dark` below.
|
|
333
|
+
A rung is a rung; a warning is `--color-status-warn`.
|
|
334
|
+
------------------------------------------------------------ */
|
|
335
|
+
--color-orange-50: var(--lux-n-50);
|
|
336
|
+
--color-orange-100: var(--lux-n-100);
|
|
337
|
+
--color-orange-200: var(--lux-n-200);
|
|
338
|
+
--color-orange-300: var(--lux-n-300);
|
|
339
|
+
--color-orange-400: var(--lux-n-400);
|
|
340
|
+
--color-orange-500: var(--lux-n-500);
|
|
341
|
+
--color-orange-600: var(--lux-n-600);
|
|
342
|
+
--color-orange-700: var(--lux-n-700);
|
|
343
|
+
--color-orange-800: var(--lux-n-800);
|
|
344
|
+
--color-orange-900: var(--lux-n-900);
|
|
345
|
+
--color-orange-950: var(--lux-n-950);
|
|
346
|
+
--color-amber-50: var(--color-orange-50);
|
|
347
|
+
--color-amber-100: var(--color-orange-100);
|
|
348
|
+
--color-amber-200: var(--color-orange-200);
|
|
349
|
+
--color-amber-300: var(--color-orange-300);
|
|
350
|
+
--color-amber-400: var(--color-orange-400);
|
|
351
|
+
--color-amber-500: var(--color-orange-500);
|
|
352
|
+
--color-amber-600: var(--color-orange-600);
|
|
353
|
+
--color-amber-700: var(--color-orange-700);
|
|
354
|
+
--color-amber-800: var(--color-orange-800);
|
|
355
|
+
--color-amber-900: var(--color-orange-900);
|
|
356
|
+
--color-amber-950: var(--color-orange-950);
|
|
357
|
+
--color-yellow-50: var(--color-orange-50);
|
|
358
|
+
--color-yellow-100: var(--color-orange-100);
|
|
359
|
+
--color-yellow-200: var(--color-orange-200);
|
|
360
|
+
--color-yellow-300: var(--color-orange-300);
|
|
361
|
+
--color-yellow-400: var(--color-orange-400);
|
|
362
|
+
--color-yellow-500: var(--color-orange-500);
|
|
363
|
+
--color-yellow-600: var(--color-orange-600);
|
|
364
|
+
--color-yellow-700: var(--color-orange-700);
|
|
365
|
+
--color-yellow-800: var(--color-orange-800);
|
|
366
|
+
--color-yellow-900: var(--color-orange-900);
|
|
367
|
+
--color-yellow-950: var(--color-orange-950);
|
|
368
|
+
/* Tailwind's warm neutral. Lux has exactly one neutral, and it is not warm. */
|
|
369
|
+
--color-stone-50: var(--lux-n-50);
|
|
370
|
+
--color-stone-100: var(--lux-n-100);
|
|
371
|
+
--color-stone-200: var(--lux-n-200);
|
|
372
|
+
--color-stone-300: var(--lux-n-300);
|
|
373
|
+
--color-stone-400: var(--lux-n-400);
|
|
374
|
+
--color-stone-500: var(--lux-n-500);
|
|
375
|
+
--color-stone-600: var(--lux-n-600);
|
|
376
|
+
--color-stone-700: var(--lux-n-700);
|
|
377
|
+
--color-stone-800: var(--lux-n-800);
|
|
378
|
+
--color-stone-900: var(--lux-n-900);
|
|
379
|
+
--color-stone-950: var(--lux-n-950);
|
|
312
380
|
--color-blackAlpha-50: var(--lux-ink-04);
|
|
313
381
|
--color-blackAlpha-100: var(--lux-ink-06);
|
|
314
382
|
--color-blackAlpha-200: var(--lux-ink-08);
|
|
@@ -615,9 +683,10 @@
|
|
|
615
683
|
/* --- Selection --- */
|
|
616
684
|
--color-selection-bg: var(--lux-n-700);
|
|
617
685
|
|
|
618
|
-
/*
|
|
619
|
-
|
|
620
|
-
|
|
686
|
+
/* The warm ramps are NOT redefined here on purpose — see the light block.
|
|
687
|
+
They are monotone in both modes so `dark:bg-yellow-900` still means
|
|
688
|
+
"dark". A call site that needs ink that FLIPS with the mode wants
|
|
689
|
+
--color-status-warn, which is a semantic token, not a palette rung. */
|
|
621
690
|
|
|
622
691
|
/* ------------------------------------------------------------
|
|
623
692
|
SHADCN / TAILWIND ALIASES (dark)
|