@defisaver/ui 0.0.1
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/README.md +104 -0
- package/dist/components/Panel/Panel.d.ts +30 -0
- package/dist/components/Panel/Panel.js +277 -0
- package/dist/components/Panel/index.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +343 -0
- package/dist/tokens/colors.stylex.d.ts +8 -0
- package/dist/tokens/radius.stylex.d.ts +9 -0
- package/dist/tokens/spacing.stylex.d.ts +26 -0
- package/dist/tokens/typography.stylex.d.ts +5 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# @defisaver/ui
|
|
2
|
+
|
|
3
|
+
DeFi Saver design system — React components written in TypeScript and styled with [StyleX](https://stylexjs.com), **pre-compiled** so consumer apps need no StyleX tooling at all.
|
|
4
|
+
|
|
5
|
+
> Local-only for now: the package is `private` and consumed via a packed tarball. npm publishing is a documented follow-up (see Roadmap).
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
StyleX needs a compiler, and defisaver-app's esbuild pipeline doesn't have one. So this repo compiles StyleX at **library build time** (`@stylexjs/rollup-plugin`): `dist/` contains plain ESM JavaScript with the atomic class names already inlined, plus a single static `dist/styles.css`. Consumers just do:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Panel, PanelHeader, PanelTitle, PanelFooter } from '@defisaver/ui';
|
|
13
|
+
import '@defisaver/ui/styles.css'; // once, at the app entry
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
That works identically under esbuild, Vite, and Next.js — no Babel, no plugins.
|
|
17
|
+
|
|
18
|
+
### Customizing components from the app
|
|
19
|
+
|
|
20
|
+
Library styles are emitted inside `@layer`, and unlayered CSS beats layered CSS regardless of specificity. So the escape hatch is the one the app already knows: pass a `className` and write plain (S)CSS — a single class selector is enough, and media queries work like anywhere else:
|
|
21
|
+
|
|
22
|
+
```scss
|
|
23
|
+
// ChartPanel.module.scss — wins over the library defaults, no !important
|
|
24
|
+
.chartHeader {
|
|
25
|
+
min-height: 44px;
|
|
26
|
+
justify-content: flex-start;
|
|
27
|
+
|
|
28
|
+
@media (max-width: 750px) {
|
|
29
|
+
flex-direction: column;
|
|
30
|
+
align-items: flex-start;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<PanelHeader className={styles.chartHeader}>…</PanelHeader>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Only the properties you declare are overridden; everything else keeps the library value. The flip side: the library also yields to careless broad selectors (`.someParent span { … }`), same as any app-local component — keep overrides scoped to a class on the component itself.
|
|
40
|
+
|
|
41
|
+
**A repeated override is a bug report against the design system.** If several call sites override the same property (say, three headers all forcing `min-height: 44px`), that's not customization — it's a missing variant or token. Request it here instead of copying the override.
|
|
42
|
+
|
|
43
|
+
### Design tokens
|
|
44
|
+
|
|
45
|
+
Tokens live in `src/tokens/*.stylex.ts` (`colors`, `space`, `radius`, `text`). The **spacing and radius scales are fully defined** — they're stable primitives, and as `defineConsts` they're inlined at compile time (zero CSS cost when unused). **Colors and typography are deliberately minimal**: a token is added the moment a component being ported needs it, never speculatively. The old app's full token inventory was audited (see git history / `~/.claude/plans` analysis) but not imported wholesale — most of it is legacy design debt, and every unused `defineVars` entry would ship as dead CSS to consumers. **Figma is the intended long-term source of truth for the token set** (sync pipeline TBD).
|
|
46
|
+
|
|
47
|
+
Each value defers to defisaver-app's custom property with a standalone fallback, e.g. `var(--surface, #1F272E)` — inside the app, components follow `theme.scss` including `[data-theme]` switching with zero migration; standalone, the app's dark-theme fallbacks apply. Names mirror the app's (`--text-color-secondary` → `colors.textSecondary`) so porting stays mechanical.
|
|
48
|
+
|
|
49
|
+
`src/tokens/tokens.test.ts` compiles every token file through a real `stylex.create()` as a regression guard.
|
|
50
|
+
|
|
51
|
+
StyleX constraint: `defineVars`/`defineConsts` must live in `*.stylex.ts` files containing nothing else, named exports only.
|
|
52
|
+
|
|
53
|
+
## Commands
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run dev # Storybook on :6006 (dev playground + docs)
|
|
57
|
+
npm run test # Vitest + React Testing Library
|
|
58
|
+
npm run lint # ESLint (org config + @stylexjs plugin), autofixes
|
|
59
|
+
npm run typecheck # tsc --noEmit
|
|
60
|
+
npm run build # Rollup (pre-compile StyleX, emit styles.css) + d.ts
|
|
61
|
+
npm run pack:local # build + npm pack → defisaver-ui-x.y.z.tgz
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Consuming from defisaver-app (local, for now)
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# here
|
|
68
|
+
npm run pack:local
|
|
69
|
+
|
|
70
|
+
# in defisaver-app/client
|
|
71
|
+
npm i ../../defisaver-ui/defisaver-ui-0.0.1.tgz
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Then import components and add `import '@defisaver/ui/styles.css'` once at the app entry.
|
|
75
|
+
|
|
76
|
+
## Adding a component
|
|
77
|
+
|
|
78
|
+
1. Create `src/components/<Name>/` with `<Name>.tsx`, `<Name>.stories.tsx`, `<Name>.test.tsx`, `index.ts` (stories/tests are excluded from the build).
|
|
79
|
+
2. Style with `stylex.create()`, using tokens from `src/tokens/` — add missing tokens there with `var(--app-var, fallback)` values, mirroring `defisaver-app/client/src/common/theme.scss`. Raw color literals in component styles **fail lint** (`propLimits` on `@stylexjs/valid-styles`) — a missing color means "add a token", never "inline a hex".
|
|
80
|
+
3. Export it from `src/index.ts`.
|
|
81
|
+
4. Verify: `npm run lint && npm run typecheck && npm test && npm run build`.
|
|
82
|
+
|
|
83
|
+
When porting from `defisaver-app/client/src/elements/`, keep the public prop API identical so migration is a drop-in import swap.
|
|
84
|
+
|
|
85
|
+
## Repo layout
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
├── tokens/ # *.stylex.ts — defineConsts/defineVars design tokens
|
|
90
|
+
├── components/
|
|
91
|
+
│ └── Panel/ # component + stories + tests, co-located
|
|
92
|
+
└── index.ts # public barrel export
|
|
93
|
+
.storybook/ # Storybook 10, react-vite, uses vite.config.ts
|
|
94
|
+
rollup.config.mjs # publish build (pre-compiled JS + styles.css)
|
|
95
|
+
vite.config.ts # internal toolchain: Storybook + Vitest (StyleX unplugin)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Roadmap (deferred, in rough order)
|
|
99
|
+
|
|
100
|
+
- Migrate components from `defisaver-app/client/src/elements/` in dependency order (Icon → primitives → composites)
|
|
101
|
+
- GitHub Actions CI (lint / typecheck / test / build / build-storybook)
|
|
102
|
+
- Changesets versioning + npm publishing under `@defisaver` (drop `"private": true`)
|
|
103
|
+
- `./tokens.stylex` source subpath export for StyleX-enabled consumers (future Next.js site)
|
|
104
|
+
- `createTheme`-based ThemeProvider so themes work without the app's `theme.scss`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
2
|
+
export type PanelSize = 's' | 'm';
|
|
3
|
+
interface PanelSectionProps extends ComponentPropsWithoutRef<'div'> {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
interface PanelTitleProps extends ComponentPropsWithoutRef<'span'> {
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
interface UncontrolledCollapseProps {
|
|
10
|
+
collapsible?: boolean;
|
|
11
|
+
defaultCollapsed?: boolean;
|
|
12
|
+
collapsed?: never;
|
|
13
|
+
onToggle?: never;
|
|
14
|
+
}
|
|
15
|
+
interface ControlledCollapseProps {
|
|
16
|
+
collapsed: boolean;
|
|
17
|
+
onToggle: () => void;
|
|
18
|
+
collapsible?: never;
|
|
19
|
+
defaultCollapsed?: never;
|
|
20
|
+
}
|
|
21
|
+
type PanelRootProps = Omit<ComponentPropsWithoutRef<'div'>, 'onToggle'> & {
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
size?: PanelSize;
|
|
24
|
+
} & (UncontrolledCollapseProps | ControlledCollapseProps);
|
|
25
|
+
export declare const Panel: import("react").ForwardRefExoticComponent<PanelRootProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
26
|
+
export declare const PanelHeader: import("react").ForwardRefExoticComponent<PanelSectionProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
27
|
+
export declare const PanelTitle: import("react").ForwardRefExoticComponent<PanelTitleProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
28
|
+
export declare const PanelBody: import("react").ForwardRefExoticComponent<PanelSectionProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
29
|
+
export declare const PanelFooter: import("react").ForwardRefExoticComponent<PanelSectionProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, forwardRef, useState, useCallback, useId, useMemo, useContext } from 'react';
|
|
3
|
+
import * as stylex from '@stylexjs/stylex';
|
|
4
|
+
|
|
5
|
+
const PanelContext = createContext({
|
|
6
|
+
size: 'm',
|
|
7
|
+
collapsible: false,
|
|
8
|
+
collapsed: false,
|
|
9
|
+
onToggle: () => {},
|
|
10
|
+
labelId: ''
|
|
11
|
+
});
|
|
12
|
+
const styles = {
|
|
13
|
+
header: {
|
|
14
|
+
kOIVth: "dsui-l10ott",
|
|
15
|
+
kmkexE: "dsui-1f1tjq8 dsui-9kvfbb",
|
|
16
|
+
kGNEyG: "dsui-6s0dn4",
|
|
17
|
+
kB7OPa: "dsui-9f619",
|
|
18
|
+
k1xSpc: "dsui-78zum5",
|
|
19
|
+
kjj79g: "dsui-1qughib",
|
|
20
|
+
kwRFfy: "dsui-shju75",
|
|
21
|
+
kZCmMZ: "dsui-67n0tv",
|
|
22
|
+
kVAEAm: "dsui-1n2onr6",
|
|
23
|
+
kL6WhQ: "dsui-1dy3hxy",
|
|
24
|
+
kfdmCh: "dsui-1q0q8m5",
|
|
25
|
+
kt9PQ7: "dsui-so031l dsui-1t1lzn6",
|
|
26
|
+
kIxVMA: "dsui-1vc0b6c",
|
|
27
|
+
ksF3WI: "dsui-16hifh2",
|
|
28
|
+
$$css: true
|
|
29
|
+
},
|
|
30
|
+
headerCollapsibleS: {
|
|
31
|
+
kZCmMZ: "dsui-izuyw3",
|
|
32
|
+
$$css: true
|
|
33
|
+
},
|
|
34
|
+
headerCollapsibleM: {
|
|
35
|
+
kZCmMZ: "dsui-ciflc3",
|
|
36
|
+
$$css: true
|
|
37
|
+
},
|
|
38
|
+
headerCollapsed: {
|
|
39
|
+
kmkexE: "dsui-1gj7el4 dsui-9kvfbb",
|
|
40
|
+
kL6WhQ: "dsui-16stqrj",
|
|
41
|
+
$$css: true
|
|
42
|
+
},
|
|
43
|
+
headerS: {
|
|
44
|
+
k8WAf4: "dsui-8p8ka2",
|
|
45
|
+
kAzted: "dsui-1gg8mnh",
|
|
46
|
+
$$css: true
|
|
47
|
+
},
|
|
48
|
+
headerM: {
|
|
49
|
+
k8WAf4: "dsui-1qmqf70",
|
|
50
|
+
kAzted: "dsui-bktkl8",
|
|
51
|
+
$$css: true
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
// stylex.props() produces the atomic class list (and sometimes inline vars);
|
|
55
|
+
// fold the caller's className/style in after it so external overrides
|
|
56
|
+
// survive the merge.
|
|
57
|
+
const mergeExternal = (sx, className, style) => ({
|
|
58
|
+
...sx,
|
|
59
|
+
className: [sx.className, className].filter(Boolean).join(' '),
|
|
60
|
+
...(sx.style || style ? {
|
|
61
|
+
style: {
|
|
62
|
+
...sx.style,
|
|
63
|
+
...style
|
|
64
|
+
}
|
|
65
|
+
} : null)
|
|
66
|
+
});
|
|
67
|
+
// Private to Panel — a visual detail of the component, not a public icon.
|
|
68
|
+
// Rendered with currentColor so it follows the toggle's token color.
|
|
69
|
+
// Default (expanded) points down; collapsed rotates to point right.
|
|
70
|
+
const Chevron = ({
|
|
71
|
+
size,
|
|
72
|
+
collapsed
|
|
73
|
+
}) => {
|
|
74
|
+
const px = size === 'm' ? 16 : 14;
|
|
75
|
+
return jsx("svg", {
|
|
76
|
+
width: px,
|
|
77
|
+
height: px,
|
|
78
|
+
viewBox: "0 0 12 12",
|
|
79
|
+
fill: "none",
|
|
80
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
81
|
+
"aria-hidden": "true",
|
|
82
|
+
...{
|
|
83
|
+
0: {
|
|
84
|
+
className: "dsui-1vlsjmz dsui-9kvfbb"
|
|
85
|
+
},
|
|
86
|
+
1: {
|
|
87
|
+
className: "dsui-1mz1xk5 dsui-9kvfbb dsui-9tu13d"
|
|
88
|
+
}
|
|
89
|
+
}[!!collapsed << 0],
|
|
90
|
+
children: jsx("path", {
|
|
91
|
+
d: "M9 5L6.09642 7.90358C6.04317 7.95683 5.95683 7.95683 5.90358 7.90358L3 5",
|
|
92
|
+
stroke: "currentColor",
|
|
93
|
+
strokeLinecap: "round"
|
|
94
|
+
})
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
const Panel = forwardRef(({
|
|
98
|
+
className,
|
|
99
|
+
style,
|
|
100
|
+
children,
|
|
101
|
+
size = 'm',
|
|
102
|
+
collapsible = false,
|
|
103
|
+
defaultCollapsed,
|
|
104
|
+
collapsed: controlledCollapsed,
|
|
105
|
+
onToggle,
|
|
106
|
+
...rest
|
|
107
|
+
}, ref) => {
|
|
108
|
+
const [ownCollapsed, setOwnCollapsed] = useState(defaultCollapsed ?? false);
|
|
109
|
+
const toggleOwn = useCallback(() => setOwnCollapsed(c => !c), []);
|
|
110
|
+
const labelId = useId();
|
|
111
|
+
const isControlled = controlledCollapsed !== undefined;
|
|
112
|
+
const context = useMemo(() => ({
|
|
113
|
+
size,
|
|
114
|
+
collapsible: isControlled || collapsible || defaultCollapsed !== undefined,
|
|
115
|
+
collapsed: isControlled ? controlledCollapsed : ownCollapsed,
|
|
116
|
+
onToggle: onToggle ?? toggleOwn,
|
|
117
|
+
labelId
|
|
118
|
+
}), [size, isControlled, collapsible, defaultCollapsed, controlledCollapsed, ownCollapsed, onToggle, toggleOwn, labelId]);
|
|
119
|
+
return jsx(PanelContext.Provider, {
|
|
120
|
+
value: context,
|
|
121
|
+
children: jsx("div", {
|
|
122
|
+
ref: ref,
|
|
123
|
+
...rest,
|
|
124
|
+
...mergeExternal({
|
|
125
|
+
className: "dsui-glcc71 dsui-zba1ul dsui-1y0btm7 dsui-mkeg23 dsui-b3r6kr dsui-1vg3ier dsui-78zum5 dsui-dt5ytf dsui-h8yej3"
|
|
126
|
+
}, className, style),
|
|
127
|
+
children: children
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
Panel.displayName = 'Panel';
|
|
132
|
+
const PanelHeader = forwardRef(({
|
|
133
|
+
className,
|
|
134
|
+
style,
|
|
135
|
+
children,
|
|
136
|
+
...rest
|
|
137
|
+
}, ref) => {
|
|
138
|
+
const {
|
|
139
|
+
size,
|
|
140
|
+
collapsible,
|
|
141
|
+
collapsed,
|
|
142
|
+
onToggle,
|
|
143
|
+
labelId
|
|
144
|
+
} = useContext(PanelContext);
|
|
145
|
+
return jsxs("div", {
|
|
146
|
+
ref: ref,
|
|
147
|
+
...rest,
|
|
148
|
+
...mergeExternal(stylex.props(styles.header, size === 'm' ? styles.headerM : styles.headerS, collapsible && (size === 'm' ? styles.headerCollapsibleM : styles.headerCollapsibleS), collapsed && styles.headerCollapsed), className, style),
|
|
149
|
+
children: [collapsible &&
|
|
150
|
+
// Named by the title text when a PanelTitle is present (labelledby
|
|
151
|
+
// pointing at nothing is skipped), otherwise the aria-label fallback
|
|
152
|
+
// applies — so title-less headers still get a usable name.
|
|
153
|
+
jsx("button", {
|
|
154
|
+
type: "button",
|
|
155
|
+
"aria-expanded": !collapsed,
|
|
156
|
+
"aria-label": "Toggle panel",
|
|
157
|
+
"aria-labelledby": labelId,
|
|
158
|
+
onClick: onToggle,
|
|
159
|
+
...{
|
|
160
|
+
0: {
|
|
161
|
+
className: "dsui-1717udv dsui-ng3xce dsui-wdigwf dsui-9kvfbb dsui-6s0dn4 dsui-jbqb8w dsui-uuh3xk dsui-83l8j1 dsui-1ypdohk dsui-3nfvp2 dsui-rotz4w dsui-l56j7k dsui-10l6tqk dsui-1hsy5kr dsui-1adtzmj dsui-3sa99s dsui-wa60dl dsui-1cpjm7i dsui-1hmns74 dsui-7z7f22 dsui-1qx5ct2 dsui-w4jnvo dsui-1xcb4hf"
|
|
162
|
+
},
|
|
163
|
+
1: {
|
|
164
|
+
className: "dsui-1717udv dsui-ng3xce dsui-wdigwf dsui-9kvfbb dsui-6s0dn4 dsui-jbqb8w dsui-uuh3xk dsui-83l8j1 dsui-1ypdohk dsui-3nfvp2 dsui-rotz4w dsui-l56j7k dsui-10l6tqk dsui-1hsy5kr dsui-1adtzmj dsui-3sa99s dsui-wa60dl dsui-1cpjm7i dsui-1hmns74 dsui-3s33r9 dsui-1fgtraw dsui-gd8bvy dsui-1rd3ygt"
|
|
165
|
+
}
|
|
166
|
+
}[!!(size === 'm') << 0],
|
|
167
|
+
children: jsx(Chevron, {
|
|
168
|
+
size: size,
|
|
169
|
+
collapsed: collapsed
|
|
170
|
+
})
|
|
171
|
+
}), children]
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
PanelHeader.displayName = 'PanelHeader';
|
|
175
|
+
const PanelTitle = forwardRef(({
|
|
176
|
+
className,
|
|
177
|
+
style,
|
|
178
|
+
children,
|
|
179
|
+
...rest
|
|
180
|
+
}, ref) => {
|
|
181
|
+
const {
|
|
182
|
+
size,
|
|
183
|
+
labelId
|
|
184
|
+
} = useContext(PanelContext);
|
|
185
|
+
return jsx("span", {
|
|
186
|
+
ref: ref,
|
|
187
|
+
...rest,
|
|
188
|
+
...mergeExternal({
|
|
189
|
+
0: {
|
|
190
|
+
className: "dsui-xyeboe dsui-6s0dn4 dsui-83l8j1 dsui-3nfvp2 dsui-1kblbho dsui-sx9v3k dsui-1wfe3co"
|
|
191
|
+
},
|
|
192
|
+
1: {
|
|
193
|
+
className: "dsui-xyeboe dsui-6s0dn4 dsui-83l8j1 dsui-3nfvp2 dsui-1kblbho dsui-1aor607 dsui-1fc57z9"
|
|
194
|
+
}
|
|
195
|
+
}[!!(size === 'm') << 0], className, style),
|
|
196
|
+
children: jsx("span", {
|
|
197
|
+
id: labelId,
|
|
198
|
+
children: children
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
PanelTitle.displayName = 'PanelTitle';
|
|
203
|
+
// Folds (rather than unmounts) while the panel is collapsed — see
|
|
204
|
+
// styles.fold for the mechanism. Content stays mounted, so child state
|
|
205
|
+
// (inputs, scroll positions) survives a collapse, like native <details>;
|
|
206
|
+
// content that must truly unmount can be conditionally rendered by the
|
|
207
|
+
// consumer in controlled mode. The consumer-facing div is the innermost
|
|
208
|
+
// one, so className/style/ref behave exactly as before the fold existed.
|
|
209
|
+
const PanelBody = forwardRef(({
|
|
210
|
+
children,
|
|
211
|
+
...rest
|
|
212
|
+
}, ref) => {
|
|
213
|
+
const {
|
|
214
|
+
collapsed
|
|
215
|
+
} = useContext(PanelContext);
|
|
216
|
+
return jsx("div", {
|
|
217
|
+
...{
|
|
218
|
+
0: {
|
|
219
|
+
className: "dsui-b3r6kr dsui-px34qp dsui-9kvfbb dsui-rvj5dj dsui-1tu4anv dsui-npuxes"
|
|
220
|
+
},
|
|
221
|
+
1: {
|
|
222
|
+
className: "dsui-b3r6kr dsui-rvj5dj dsui-1j9udwn dsui-9kvfbb dsui-ihq33y dsui-lshs6z"
|
|
223
|
+
}
|
|
224
|
+
}[!!collapsed << 0],
|
|
225
|
+
children: jsx("div", {
|
|
226
|
+
...{
|
|
227
|
+
className: "dsui-2lwn1j"
|
|
228
|
+
},
|
|
229
|
+
children: jsx("div", {
|
|
230
|
+
ref: ref,
|
|
231
|
+
...rest,
|
|
232
|
+
children: children
|
|
233
|
+
})
|
|
234
|
+
})
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
PanelBody.displayName = 'PanelBody';
|
|
238
|
+
const PanelFooter = forwardRef(({
|
|
239
|
+
className,
|
|
240
|
+
style,
|
|
241
|
+
children,
|
|
242
|
+
...rest
|
|
243
|
+
}, ref) => {
|
|
244
|
+
// A collapsed panel shows only its header — footer actions (pagination
|
|
245
|
+
// etc.) operate on the hidden body, so the footer folds in sync with it.
|
|
246
|
+
// Its top border rides on the inner content div, where the fold clips it
|
|
247
|
+
// away only as the row closes completely.
|
|
248
|
+
const {
|
|
249
|
+
collapsed
|
|
250
|
+
} = useContext(PanelContext);
|
|
251
|
+
return jsx("div", {
|
|
252
|
+
...{
|
|
253
|
+
0: {
|
|
254
|
+
className: "dsui-b3r6kr dsui-px34qp dsui-9kvfbb dsui-rvj5dj dsui-1tu4anv dsui-npuxes"
|
|
255
|
+
},
|
|
256
|
+
1: {
|
|
257
|
+
className: "dsui-b3r6kr dsui-rvj5dj dsui-1j9udwn dsui-9kvfbb dsui-ihq33y dsui-lshs6z"
|
|
258
|
+
}
|
|
259
|
+
}[!!collapsed << 0],
|
|
260
|
+
children: jsx("div", {
|
|
261
|
+
...{
|
|
262
|
+
className: "dsui-2lwn1j"
|
|
263
|
+
},
|
|
264
|
+
children: jsx("div", {
|
|
265
|
+
ref: ref,
|
|
266
|
+
...rest,
|
|
267
|
+
...mergeExternal({
|
|
268
|
+
className: "dsui-l10ott dsui-h5mxk0 dsui-114gu5o dsui-6s0dn4 dsui-9f619 dsui-78zum5 dsui-1qughib dsui-1gzt5ks dsui-13fuv20 dsui-178xt8z dsui-u0aao5"
|
|
269
|
+
}, className, style),
|
|
270
|
+
children: children
|
|
271
|
+
})
|
|
272
|
+
})
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
PanelFooter.displayName = 'PanelFooter';
|
|
276
|
+
|
|
277
|
+
export { Panel, PanelBody, PanelFooter, PanelHeader, PanelTitle };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Panel, PanelHeader, PanelTitle, PanelBody, PanelFooter, } from './Panel';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Panel, PanelHeader, PanelTitle, PanelBody, PanelFooter, } from './components/Panel';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Panel, PanelBody, PanelFooter, PanelHeader, PanelTitle } from './components/Panel/Panel.js';
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
@layer priority1 {
|
|
2
|
+
:root, .dsui-1cp4su5 {
|
|
3
|
+
--dsui-1vymh5z: var(--surface, #1f272e);
|
|
4
|
+
--dsui-1ov11o: var(--surface-shade, #181f25);
|
|
5
|
+
--dsui-z4f854: var(--surface-border-surface, #252f37);
|
|
6
|
+
--dsui-1c3ib43: var(--text-color-secondary, #b2c1cc);
|
|
7
|
+
--dsui-1ew4bd3: var(--interaction-white-5-hover, #ffffff0d);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@layer priority2 {
|
|
12
|
+
.dsui-1717udv {
|
|
13
|
+
padding: 0;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@layer priority3 {
|
|
18
|
+
.dsui-glcc71 {
|
|
19
|
+
border-color: var(--dsui-z4f854);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dsui-7z7f22 {
|
|
23
|
+
border-radius: var(--border-radius-medium, 6px);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.dsui-zba1ul {
|
|
27
|
+
border-radius: var(--border-radius-xl, 12px);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dsui-3s33r9 {
|
|
31
|
+
border-radius: var(--border-radius-large, 8px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dsui-ng3xce {
|
|
35
|
+
border-style: none;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.dsui-1y0btm7 {
|
|
39
|
+
border-style: solid;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.dsui-mkeg23 {
|
|
43
|
+
border-width: 1px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.dsui-xyeboe {
|
|
47
|
+
gap: var(--space-1-5, 6px);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.dsui-l10ott {
|
|
51
|
+
gap: var(--space-2, 8px);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.dsui-b3r6kr {
|
|
55
|
+
overflow: hidden;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.dsui-h5mxk0 {
|
|
59
|
+
padding-block: var(--space-1, 4px);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.dsui-8p8ka2 {
|
|
63
|
+
padding-block: var(--space-2, 8px);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.dsui-1qmqf70 {
|
|
67
|
+
padding-block: var(--space-2-5, 10px);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.dsui-114gu5o {
|
|
71
|
+
padding-inline: var(--space-2, 8px);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.dsui-wdigwf {
|
|
75
|
+
transition: background-color .2s, scale .15s ease-out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.dsui-1f1tjq8 {
|
|
79
|
+
transition: border-bottom-color .25s cubic-bezier(.165, .84, .44, 1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.dsui-1gj7el4 {
|
|
83
|
+
transition: border-bottom-color .2s cubic-bezier(.165, .84, .44, 1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.dsui-px34qp {
|
|
87
|
+
transition: grid-template-rows .25s cubic-bezier(.165, .84, .44, 1), visibility .25s;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.dsui-1j9udwn {
|
|
91
|
+
transition: grid-template-rows .2s cubic-bezier(.165, .84, .44, 1), visibility .2s;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.dsui-1vlsjmz {
|
|
95
|
+
transition: transform .25s cubic-bezier(.165, .84, .44, 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.dsui-1mz1xk5 {
|
|
99
|
+
transition: transform .2s cubic-bezier(.165, .84, .44, 1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@media (prefers-reduced-motion: reduce) {
|
|
103
|
+
.dsui-9kvfbb.dsui-9kvfbb {
|
|
104
|
+
transition: none;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
@layer priority4 {
|
|
110
|
+
.dsui-6s0dn4 {
|
|
111
|
+
align-items: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.dsui-jbqb8w {
|
|
115
|
+
background-color: #0000;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.dsui-1vg3ier {
|
|
119
|
+
background-color: var(--dsui-1ov11o);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.dsui-9f619 {
|
|
123
|
+
box-sizing: border-box;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.dsui-83l8j1 {
|
|
127
|
+
color: var(--dsui-1c3ib43);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.dsui-1ypdohk {
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.dsui-78zum5 {
|
|
135
|
+
display: flex;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.dsui-rvj5dj {
|
|
139
|
+
display: grid;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.dsui-3nfvp2 {
|
|
143
|
+
display: inline-flex;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.dsui-dt5ytf {
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.dsui-sx9v3k {
|
|
151
|
+
font-size: var(--text-size-small, 12px);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.dsui-1aor607 {
|
|
155
|
+
font-size: var(--text-size-regular, 14px);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.dsui-1kblbho {
|
|
159
|
+
font-weight: var(--text-weight-semi-bold, 500);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.dsui-ihq33y {
|
|
163
|
+
grid-template-rows: 0fr;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.dsui-1tu4anv {
|
|
167
|
+
grid-template-rows: 1fr;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.dsui-rotz4w {
|
|
171
|
+
inset-inline-start: 8px;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.dsui-l56j7k {
|
|
175
|
+
justify-content: center;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.dsui-1qughib {
|
|
179
|
+
justify-content: space-between;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.dsui-1wfe3co {
|
|
183
|
+
line-height: 12px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.dsui-1fc57z9 {
|
|
187
|
+
line-height: 20px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.dsui-shju75 {
|
|
191
|
+
padding-inline-end: var(--space-2, 8px);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.dsui-izuyw3 {
|
|
195
|
+
padding-inline-start: 34px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.dsui-ciflc3 {
|
|
199
|
+
padding-inline-start: 42px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.dsui-67n0tv {
|
|
203
|
+
padding-inline-start: var(--space-3, 12px);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.dsui-10l6tqk {
|
|
207
|
+
position: absolute;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.dsui-1n2onr6 {
|
|
211
|
+
position: relative;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.dsui-1hsy5kr {
|
|
215
|
+
scale: 1;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.dsui-9tu13d {
|
|
219
|
+
transform: rotate(-90deg);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.dsui-3sa99s {
|
|
223
|
+
translate: 0 -50%;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.dsui-lshs6z {
|
|
227
|
+
visibility: hidden;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.dsui-npuxes {
|
|
231
|
+
visibility: visible;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.dsui-uuh3xk:hover {
|
|
235
|
+
background-color: var(--dsui-1ew4bd3);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.dsui-1adtzmj:active {
|
|
239
|
+
scale: .96;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@layer priority5 {
|
|
244
|
+
.dsui-16stqrj {
|
|
245
|
+
border-bottom-color: #0000;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.dsui-1dy3hxy {
|
|
249
|
+
border-bottom-color: var(--dsui-z4f854);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.dsui-1q0q8m5 {
|
|
253
|
+
border-bottom-style: solid;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.dsui-so031l {
|
|
257
|
+
border-bottom-width: 1px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.dsui-1gzt5ks {
|
|
261
|
+
border-top-color: var(--dsui-z4f854);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.dsui-1vc0b6c {
|
|
265
|
+
border-top-left-radius: calc(var(--border-radius-xl, 12px) - 1px);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.dsui-16hifh2 {
|
|
269
|
+
border-top-right-radius: calc(var(--border-radius-xl, 12px) - 1px);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.dsui-13fuv20 {
|
|
273
|
+
border-top-style: solid;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.dsui-178xt8z {
|
|
277
|
+
border-top-width: 1px;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.dsui-1qx5ct2 {
|
|
281
|
+
height: 20px;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.dsui-1fgtraw {
|
|
285
|
+
height: 28px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.dsui-2lwn1j {
|
|
289
|
+
min-height: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.dsui-u0aao5 {
|
|
293
|
+
min-height: 36px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.dsui-1gg8mnh {
|
|
297
|
+
min-height: 44px;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.dsui-bktkl8 {
|
|
301
|
+
min-height: 56px;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.dsui-wa60dl {
|
|
305
|
+
top: 50%;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.dsui-h8yej3 {
|
|
309
|
+
width: 100%;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.dsui-w4jnvo {
|
|
313
|
+
width: 20px;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.dsui-gd8bvy {
|
|
317
|
+
width: 28px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.dsui-1t1lzn6:last-child {
|
|
321
|
+
border-bottom-width: 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@layer priority6 {
|
|
326
|
+
.dsui-1xcb4hf:before {
|
|
327
|
+
inset: -10px;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.dsui-1rd3ygt:before {
|
|
331
|
+
inset: -6px;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@layer priority7 {
|
|
336
|
+
.dsui-1cpjm7i:before {
|
|
337
|
+
content: "";
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.dsui-1hmns74:before {
|
|
341
|
+
position: absolute;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const space: {
|
|
2
|
+
px1: string;
|
|
3
|
+
px2: string;
|
|
4
|
+
px4: string;
|
|
5
|
+
px6: string;
|
|
6
|
+
px8: string;
|
|
7
|
+
px10: string;
|
|
8
|
+
px12: string;
|
|
9
|
+
px16: string;
|
|
10
|
+
px20: string;
|
|
11
|
+
px24: string;
|
|
12
|
+
px28: string;
|
|
13
|
+
px32: string;
|
|
14
|
+
px36: string;
|
|
15
|
+
px40: string;
|
|
16
|
+
px44: string;
|
|
17
|
+
px48: string;
|
|
18
|
+
px52: string;
|
|
19
|
+
px56: string;
|
|
20
|
+
px60: string;
|
|
21
|
+
px64: string;
|
|
22
|
+
px68: string;
|
|
23
|
+
px72: string;
|
|
24
|
+
px76: string;
|
|
25
|
+
px80: string;
|
|
26
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@defisaver/ui",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "DeFi Saver design system — React components styled with StyleX, pre-compiled so consumers need no StyleX tooling.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"**/*.css"
|
|
8
|
+
],
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./styles.css": "./dist/styles.css",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "rollup -c && tsc -p tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist",
|
|
22
|
+
"dev": "storybook dev -p 6006",
|
|
23
|
+
"build-storybook": "storybook build",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"lint": "eslint . --fix",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"pack:local": "npm run build && npm pack"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=22.0.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@stylexjs/stylex": "^0.19.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@defisaver/eslint-config": "^2.1.0",
|
|
38
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
39
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
40
|
+
"@storybook/addon-vitest": "^10.4.6",
|
|
41
|
+
"@storybook/react-vite": "^10.4.6",
|
|
42
|
+
"@stylexjs/eslint-plugin": "^0.19.0",
|
|
43
|
+
"@stylexjs/rollup-plugin": "^0.19.0",
|
|
44
|
+
"@stylexjs/unplugin": "^0.19.0",
|
|
45
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
46
|
+
"@testing-library/react": "^16.3.2",
|
|
47
|
+
"@testing-library/user-event": "^14.6.1",
|
|
48
|
+
"@types/node": "^22.20.0",
|
|
49
|
+
"@types/react": "^18.3.31",
|
|
50
|
+
"@types/react-dom": "^18.3.7",
|
|
51
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
52
|
+
"@vitest/browser-playwright": "^4.1.9",
|
|
53
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
54
|
+
"eslint": "^9.39.4",
|
|
55
|
+
"jsdom": "^29.1.1",
|
|
56
|
+
"playwright": "^1.61.1",
|
|
57
|
+
"prettier": "^3.9.4",
|
|
58
|
+
"react": "^18.3.1",
|
|
59
|
+
"react-dom": "^18.3.1",
|
|
60
|
+
"rollup": "^4.62.2",
|
|
61
|
+
"storybook": "^10.4.6",
|
|
62
|
+
"tslib": "^2.8.1",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"vite": "^8.1.3",
|
|
65
|
+
"vitest": "^4.1.9"
|
|
66
|
+
}
|
|
67
|
+
}
|