@decentrys/ui-sdk 0.1.0
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/LICENSE +21 -0
- package/README.md +51 -0
- package/dist/browser/decentrys-ui.js +1733 -0
- package/dist/browser/decentrys-ui.mjs +1681 -0
- package/dist/browser/react-dom-global.d.ts +6 -0
- package/dist/browser/react-dom-global.d.ts.map +1 -0
- package/dist/browser/react-dom-global.js +18 -0
- package/dist/browser/react-dom-global.js.map +1 -0
- package/dist/browser/react-global.d.ts +25 -0
- package/dist/browser/react-global.d.ts.map +1 -0
- package/dist/browser/react-global.js +39 -0
- package/dist/browser/react-global.js.map +1 -0
- package/dist/browser/react-jsx-runtime.d.ts +17 -0
- package/dist/browser/react-jsx-runtime.d.ts.map +1 -0
- package/dist/browser/react-jsx-runtime.js +27 -0
- package/dist/browser/react-jsx-runtime.js.map +1 -0
- package/dist/components/AddressRiskBadge.d.ts +39 -0
- package/dist/components/AddressRiskBadge.d.ts.map +1 -0
- package/dist/components/AddressRiskBadge.js +26 -0
- package/dist/components/AddressRiskBadge.js.map +1 -0
- package/dist/components/ApprovalWarning.d.ts +29 -0
- package/dist/components/ApprovalWarning.d.ts.map +1 -0
- package/dist/components/ApprovalWarning.js +18 -0
- package/dist/components/ApprovalWarning.js.map +1 -0
- package/dist/components/ContractFacts.d.ts +27 -0
- package/dist/components/ContractFacts.d.ts.map +1 -0
- package/dist/components/ContractFacts.js +15 -0
- package/dist/components/ContractFacts.js.map +1 -0
- package/dist/components/RiskDetailsModal.d.ts +35 -0
- package/dist/components/RiskDetailsModal.d.ts.map +1 -0
- package/dist/components/RiskDetailsModal.js +74 -0
- package/dist/components/RiskDetailsModal.js.map +1 -0
- package/dist/components/ThreatSignalList.d.ts +28 -0
- package/dist/components/ThreatSignalList.d.ts.map +1 -0
- package/dist/components/ThreatSignalList.js +28 -0
- package/dist/components/ThreatSignalList.js.map +1 -0
- package/dist/components/TokenSecurityPanel.d.ts +28 -0
- package/dist/components/TokenSecurityPanel.d.ts.map +1 -0
- package/dist/components/TokenSecurityPanel.js +26 -0
- package/dist/components/TokenSecurityPanel.js.map +1 -0
- package/dist/components/TransactionRiskBanner.d.ts +36 -0
- package/dist/components/TransactionRiskBanner.d.ts.map +1 -0
- package/dist/components/TransactionRiskBanner.js +20 -0
- package/dist/components/TransactionRiskBanner.js.map +1 -0
- package/dist/context.d.ts +51 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +47 -0
- package/dist/context.js.map +1 -0
- package/dist/copy.d.ts +72 -0
- package/dist/copy.d.ts.map +1 -0
- package/dist/copy.js +77 -0
- package/dist/copy.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/dist/presentation.d.ts +316 -0
- package/dist/presentation.d.ts.map +1 -0
- package/dist/presentation.js +533 -0
- package/dist/presentation.js.map +1 -0
- package/dist/primitives.d.ts +139 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/primitives.js +150 -0
- package/dist/primitives.js.map +1 -0
- package/dist/styles.d.ts +15 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +285 -0
- package/dist/styles.js.map +1 -0
- package/dist/theme.d.ts +52 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +90 -0
- package/dist/theme.js.map +1 -0
- package/package.json +70 -0
- package/src/browser/react-dom-global.ts +20 -0
- package/src/browser/react-global.ts +42 -0
- package/src/browser/react-jsx-runtime.ts +23 -0
- package/src/components/AddressRiskBadge.tsx +94 -0
- package/src/components/ApprovalWarning.tsx +144 -0
- package/src/components/ContractFacts.tsx +119 -0
- package/src/components/RiskDetailsModal.tsx +229 -0
- package/src/components/ThreatSignalList.tsx +76 -0
- package/src/components/TokenSecurityPanel.tsx +168 -0
- package/src/components/TransactionRiskBanner.tsx +123 -0
- package/src/context.tsx +89 -0
- package/src/copy.test.ts +214 -0
- package/src/copy.ts +151 -0
- package/src/index.ts +84 -0
- package/src/presentation.test.ts +521 -0
- package/src/presentation.ts +846 -0
- package/src/primitives.tsx +384 -0
- package/src/styles.ts +281 -0
- package/src/theme.test.ts +105 -0
- package/src/theme.ts +117 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theming has to be genuinely customisable, which means two things have to
|
|
3
|
+
* stay true and neither is obvious from reading either file alone:
|
|
4
|
+
*
|
|
5
|
+
* - Every value the stylesheet paints with is a token an integrator can
|
|
6
|
+
* override. A colour hard-coded into a rule is a colour they cannot change,
|
|
7
|
+
* and they will find that out inside their own wallet.
|
|
8
|
+
* - No token exists only in the dark-mode block. One that did would be
|
|
9
|
+
* undefined in light mode and silently paint nothing.
|
|
10
|
+
*
|
|
11
|
+
* Both are checked against the stylesheet text itself, so the token API and
|
|
12
|
+
* the CSS cannot drift apart without a test failing.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import { THEME_TOKENS, themeStyle, toneBgVar, toneVar, type DecentrysTheme } from './theme';
|
|
17
|
+
import { DECENTRYS_UI_CSS } from './styles';
|
|
18
|
+
|
|
19
|
+
function block(selector: string): string {
|
|
20
|
+
const start = DECENTRYS_UI_CSS.indexOf(selector);
|
|
21
|
+
expect(start, `${selector} missing from the stylesheet`).toBeGreaterThan(-1);
|
|
22
|
+
const open = DECENTRYS_UI_CSS.indexOf('{', start);
|
|
23
|
+
const close = DECENTRYS_UI_CSS.indexOf('}', open);
|
|
24
|
+
return DECENTRYS_UI_CSS.slice(open, close);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function definedTokens(css: string): Set<string> {
|
|
28
|
+
return new Set([...css.matchAll(/--dcy-([a-z0-9-]+)\s*:/g)].map((m) => m[1]));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('themeStyle', () => {
|
|
32
|
+
it('emits custom properties an integrator can override the defaults with', () => {
|
|
33
|
+
const style = themeStyle({ surface: '#0b0d10', text: '#e8ebef' }) as Record<string, string>;
|
|
34
|
+
expect(style['--dcy-surface']).toBe('#0b0d10');
|
|
35
|
+
expect(style['--dcy-text']).toBe('#e8ebef');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('leaves the caller’s own style properties in place', () => {
|
|
39
|
+
const style = themeStyle({ radius: '0px' }, { marginTop: 8 }) as Record<string, unknown>;
|
|
40
|
+
expect(style.marginTop).toBe(8);
|
|
41
|
+
expect(style['--dcy-radius']).toBe('0px');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns the base style untouched when there is no theme', () => {
|
|
45
|
+
expect(themeStyle(undefined, { marginTop: 8 })).toEqual({ marginTop: 8 });
|
|
46
|
+
expect(themeStyle(undefined)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('ignores empty values rather than blanking a token', () => {
|
|
50
|
+
const style = themeStyle({ surface: '' } as DecentrysTheme) as Record<string, string>;
|
|
51
|
+
expect('--dcy-surface' in style).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('references tones through variables, so a palette swap reaches everything', () => {
|
|
55
|
+
expect(toneVar('critical')).toBe('var(--dcy-tone-critical)');
|
|
56
|
+
expect(toneBgVar('capability')).toBe('var(--dcy-tone-capability-bg)');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('the stylesheet and the token API', () => {
|
|
61
|
+
it('declares exactly the tokens the theme type exposes', () => {
|
|
62
|
+
const declared = definedTokens(block('.dcy {'));
|
|
63
|
+
expect([...declared].sort()).toEqual([...THEME_TOKENS].sort());
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('defines no token that exists only in dark mode', () => {
|
|
67
|
+
const light = definedTokens(block('.dcy {'));
|
|
68
|
+
const darkStart = DECENTRYS_UI_CSS.indexOf('@media (prefers-color-scheme: dark)');
|
|
69
|
+
const dark = definedTokens(DECENTRYS_UI_CSS.slice(darkStart, DECENTRYS_UI_CSS.indexOf('}', DECENTRYS_UI_CSS.indexOf('{', DECENTRYS_UI_CSS.indexOf('.dcy {', darkStart)))));
|
|
70
|
+
for (const token of dark) expect(light.has(token), `--dcy-${token}`).toBe(true);
|
|
71
|
+
expect(dark.size).toBeGreaterThan(0);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('gives every tone both a foreground and a background token', () => {
|
|
75
|
+
const tones = [
|
|
76
|
+
'neutral', 'info', 'caution', 'elevated', 'high', 'critical',
|
|
77
|
+
'fact', 'capability', 'unknown', 'inactive',
|
|
78
|
+
];
|
|
79
|
+
for (const tone of tones) {
|
|
80
|
+
expect(THEME_TOKENS).toContain(`tone-${tone}`);
|
|
81
|
+
expect(THEME_TOKENS).toContain(`tone-${tone}-bg`);
|
|
82
|
+
expect(DECENTRYS_UI_CSS).toContain(`[data-dcy-tone="${tone}"]`);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('carries a visible focus style, since these ship into other people’s apps', () => {
|
|
87
|
+
expect(DECENTRYS_UI_CSS).toContain(':focus-visible');
|
|
88
|
+
expect(DECENTRYS_UI_CSS).toContain('outline: 2px solid var(--dcy-focus)');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('separates the kinds by rule style as well as by colour', () => {
|
|
92
|
+
expect(DECENTRYS_UI_CSS).toContain('[data-dcy-rule="solid"]');
|
|
93
|
+
expect(DECENTRYS_UI_CSS).toContain('[data-dcy-rule="dashed"]');
|
|
94
|
+
expect(DECENTRYS_UI_CSS).toContain('[data-dcy-rule="dotted"]');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('brings no styling dependency along with it', () => {
|
|
98
|
+
// These components land inside somebody else's build. A @import, a
|
|
99
|
+
// Tailwind directive or a webfont fetch would be a cost they did not agree
|
|
100
|
+
// to, and a CSP violation in half the wallets that embed this.
|
|
101
|
+
expect(DECENTRYS_UI_CSS).not.toContain('@import');
|
|
102
|
+
expect(DECENTRYS_UI_CSS).not.toContain('@tailwind');
|
|
103
|
+
expect(DECENTRYS_UI_CSS).not.toContain('url(');
|
|
104
|
+
});
|
|
105
|
+
});
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theming.
|
|
3
|
+
*
|
|
4
|
+
* These components ship into other people's wallets, so two things are true at
|
|
5
|
+
* once: they must look like something out of the box, and they must never
|
|
6
|
+
* force a Decentrys palette onto an application that has its own. Every visual
|
|
7
|
+
* value is therefore a CSS custom property with a default, and there are three
|
|
8
|
+
* escalating ways to override it:
|
|
9
|
+
*
|
|
10
|
+
* 1. `theme` — a token map applied inline on the component's own root, which
|
|
11
|
+
* beats every stylesheet rule including the dark-mode defaults.
|
|
12
|
+
* 2. `className` — the integrator's own class on the same root, for anything
|
|
13
|
+
* tokens do not reach.
|
|
14
|
+
* 3. `data-dcy-*` attributes, present on every meaningful element
|
|
15
|
+
* (`data-dcy-level`, `data-dcy-kind`, `data-dcy-tone`), so an integrator
|
|
16
|
+
* can target our internals from their own stylesheet without us exporting
|
|
17
|
+
* a class-name contract we would then have to keep.
|
|
18
|
+
*
|
|
19
|
+
* No styling dependency is used or needed. Adding Tailwind or a CSS-in-JS
|
|
20
|
+
* runtime to a package that lands inside someone else's build is a cost they
|
|
21
|
+
* did not agree to.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { CSSProperties } from 'react';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Tones.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately *not* a severity scale with `safe` at one end. `fact`,
|
|
30
|
+
* `capability` and `unknown` are separate tone families from the risk tones,
|
|
31
|
+
* because a fact and a threat signal rendering in the same colour is the exact
|
|
32
|
+
* failure this product exists to prevent — and `inactive` exists so a stale or
|
|
33
|
+
* low-confidence signal, which did not affect the assessment, cannot be shown
|
|
34
|
+
* in the colour of one that did.
|
|
35
|
+
*/
|
|
36
|
+
export type Tone =
|
|
37
|
+
| 'neutral'
|
|
38
|
+
| 'info'
|
|
39
|
+
| 'caution'
|
|
40
|
+
| 'elevated'
|
|
41
|
+
| 'high'
|
|
42
|
+
| 'critical'
|
|
43
|
+
| 'fact'
|
|
44
|
+
| 'capability'
|
|
45
|
+
| 'unknown'
|
|
46
|
+
| 'inactive';
|
|
47
|
+
|
|
48
|
+
export const THEME_TOKENS = [
|
|
49
|
+
'font',
|
|
50
|
+
'font-mono',
|
|
51
|
+
'radius',
|
|
52
|
+
'gap',
|
|
53
|
+
'surface',
|
|
54
|
+
'surface-raised',
|
|
55
|
+
'border',
|
|
56
|
+
'text',
|
|
57
|
+
'text-muted',
|
|
58
|
+
'text-faint',
|
|
59
|
+
'focus',
|
|
60
|
+
'tone-neutral',
|
|
61
|
+
'tone-neutral-bg',
|
|
62
|
+
'tone-info',
|
|
63
|
+
'tone-info-bg',
|
|
64
|
+
'tone-caution',
|
|
65
|
+
'tone-caution-bg',
|
|
66
|
+
'tone-elevated',
|
|
67
|
+
'tone-elevated-bg',
|
|
68
|
+
'tone-high',
|
|
69
|
+
'tone-high-bg',
|
|
70
|
+
'tone-critical',
|
|
71
|
+
'tone-critical-bg',
|
|
72
|
+
'tone-fact',
|
|
73
|
+
'tone-fact-bg',
|
|
74
|
+
'tone-capability',
|
|
75
|
+
'tone-capability-bg',
|
|
76
|
+
'tone-unknown',
|
|
77
|
+
'tone-unknown-bg',
|
|
78
|
+
'tone-inactive',
|
|
79
|
+
'tone-inactive-bg',
|
|
80
|
+
] as const;
|
|
81
|
+
|
|
82
|
+
export type ThemeToken = (typeof THEME_TOKENS)[number];
|
|
83
|
+
|
|
84
|
+
/** A partial map of token → CSS value. Anything omitted keeps its default. */
|
|
85
|
+
export type DecentrysTheme = Partial<Record<ThemeToken, string>>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Turn a theme into inline custom properties.
|
|
89
|
+
*
|
|
90
|
+
* Inline custom properties win over the stylesheet's defaults *and* over the
|
|
91
|
+
* `prefers-color-scheme` block, which is the behaviour an integrator with a
|
|
92
|
+
* fixed brand palette needs — otherwise their light theme would flip to our
|
|
93
|
+
* dark one on a user's OS setting.
|
|
94
|
+
*/
|
|
95
|
+
export function themeStyle(
|
|
96
|
+
theme?: DecentrysTheme,
|
|
97
|
+
base?: CSSProperties,
|
|
98
|
+
): CSSProperties | undefined {
|
|
99
|
+
if (!theme) return base;
|
|
100
|
+
const style: Record<string, string> = {};
|
|
101
|
+
for (const [token, value] of Object.entries(theme)) {
|
|
102
|
+
if (typeof value === 'string' && value.length > 0) style[`--dcy-${token}`] = value;
|
|
103
|
+
}
|
|
104
|
+
// Custom properties are not part of the CSSProperties index signature; the
|
|
105
|
+
// cast is the standard, and only, way to express them in React's types.
|
|
106
|
+
return { ...(base ?? {}), ...style } as CSSProperties;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The CSS variable reference for a tone's foreground, e.g. for a border. */
|
|
110
|
+
export function toneVar(tone: Tone): string {
|
|
111
|
+
return `var(--dcy-tone-${tone})`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The CSS variable reference for a tone's background wash. */
|
|
115
|
+
export function toneBgVar(tone: Tone): string {
|
|
116
|
+
return `var(--dcy-tone-${tone}-bg)`;
|
|
117
|
+
}
|