@zerodev/react-ui 0.0.2 → 0.0.4
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 +1 -0
- package/dist/_types/components/ArrowCardPair/index.d.ts +10 -0
- package/dist/_types/components/ArrowCardPair/index.d.ts.map +1 -0
- package/dist/_types/components/Badge/index.d.ts +1 -1
- package/dist/_types/components/Badge/index.d.ts.map +1 -1
- package/dist/_types/components/BottomSheet/index.d.ts +17 -0
- package/dist/_types/components/BottomSheet/index.d.ts.map +1 -0
- package/dist/_types/components/Button/index.d.ts +1 -1
- package/dist/_types/components/Button/index.d.ts.map +1 -1
- package/dist/_types/components/Callout/index.d.ts +1 -1
- package/dist/_types/components/Callout/index.d.ts.map +1 -1
- package/dist/_types/components/DataRow/index.d.ts +33 -0
- package/dist/_types/components/DataRow/index.d.ts.map +1 -0
- package/dist/_types/components/Icon/index.d.ts +1 -1
- package/dist/_types/components/Icon/index.d.ts.map +1 -1
- package/dist/_types/components/IconButton/index.d.ts +1 -1
- package/dist/_types/components/IconButton/index.d.ts.map +1 -1
- package/dist/_types/components/Input/index.d.ts +1 -1
- package/dist/_types/components/Input/index.d.ts.map +1 -1
- package/dist/_types/components/ListItem/index.d.ts +32 -15
- package/dist/_types/components/ListItem/index.d.ts.map +1 -1
- package/dist/_types/components/Pill/index.d.ts +25 -0
- package/dist/_types/components/Pill/index.d.ts.map +1 -0
- package/dist/_types/components/PoweredBy/index.d.ts +6 -0
- package/dist/_types/components/PoweredBy/index.d.ts.map +1 -0
- package/dist/_types/components/Screen/EllipticalRadial.d.ts +23 -0
- package/dist/_types/components/Screen/EllipticalRadial.d.ts.map +1 -0
- package/dist/_types/components/Screen/MultiRadialBackground.d.ts +4 -0
- package/dist/_types/components/Screen/MultiRadialBackground.d.ts.map +1 -0
- package/dist/_types/components/Screen/index.d.ts +15 -0
- package/dist/_types/components/Screen/index.d.ts.map +1 -0
- package/dist/_types/components/Select/index.d.ts +28 -0
- package/dist/_types/components/Select/index.d.ts.map +1 -0
- package/dist/_types/components/Switch/index.d.ts +1 -1
- package/dist/_types/components/Switch/index.d.ts.map +1 -1
- package/dist/_types/components/Text/index.d.ts +1 -1
- package/dist/_types/components/Text/index.d.ts.map +1 -1
- package/dist/_types/components/TokenListItem/index.d.ts +33 -0
- package/dist/_types/components/TokenListItem/index.d.ts.map +1 -0
- package/dist/_types/components/TopNav/index.d.ts +20 -0
- package/dist/_types/components/TopNav/index.d.ts.map +1 -0
- package/dist/_types/components/WrappedPressable/index.d.ts +1 -1
- package/dist/_types/components/WrappedPressable/index.d.ts.map +1 -1
- package/dist/_types/components/Wrapper/index.d.ts +3 -2
- package/dist/_types/components/Wrapper/index.d.ts.map +1 -1
- package/dist/_types/components/ZeroDevLogo/index.d.ts +12 -0
- package/dist/_types/components/ZeroDevLogo/index.d.ts.map +1 -0
- package/dist/_types/index.d.ts +11 -1
- package/dist/_types/index.d.ts.map +1 -1
- package/dist/index.cjs +111 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +6164 -1199
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +2 -2
- package/package.json +7 -5
- package/src/components/ArrowCardPair/index.tsx +148 -0
- package/src/components/BottomSheet/index.tsx +57 -0
- package/src/components/DataRow/index.tsx +140 -0
- package/src/components/ListItem/index.tsx +74 -79
- package/src/components/Pill/index.tsx +167 -0
- package/src/components/PoweredBy/index.tsx +29 -0
- package/src/components/Screen/EllipticalRadial.tsx +65 -0
- package/src/components/Screen/MultiRadialBackground.tsx +323 -0
- package/src/components/Screen/index.tsx +121 -0
- package/src/components/Select/index.tsx +153 -0
- package/src/components/TokenListItem/index.tsx +192 -0
- package/src/components/TopNav/index.tsx +67 -0
- package/src/components/Wrapper/index.tsx +4 -1
- package/src/components/ZeroDevLogo/index.tsx +55 -0
- package/src/index.ts +50 -0
- package/src/svg.d.ts +9 -0
- package/tailwind.config.ts +39 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CSSProperties,
|
|
3
|
+
createContext,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
} from 'react'
|
|
8
|
+
import { cn } from '../../utils/common'
|
|
9
|
+
import { TOP_NAV_HEIGHT } from '../TopNav'
|
|
10
|
+
import {
|
|
11
|
+
CardGlow,
|
|
12
|
+
MultiRadialBackground,
|
|
13
|
+
WrapperBorder,
|
|
14
|
+
} from './MultiRadialBackground'
|
|
15
|
+
|
|
16
|
+
const CONTENT_PADDING_TOP = TOP_NAV_HEIGHT + 16
|
|
17
|
+
|
|
18
|
+
// Container for card-scoped overlays (bottom sheets, dialogs, etc.). Consumers
|
|
19
|
+
// portal into this element via `Radix Dialog.Portal container={...}` so the
|
|
20
|
+
// overlay is clipped inside the card's rounded frame instead of covering the
|
|
21
|
+
// whole page. Populated once Screen's inner card mounts.
|
|
22
|
+
const ScreenOverlayContext = createContext<HTMLDivElement | null>(null)
|
|
23
|
+
|
|
24
|
+
/** Element inside `Screen` that overlay/portal-based components (e.g. the QR
|
|
25
|
+
* sheet) should render into. Returns `null` outside a `Screen` — callers
|
|
26
|
+
* should guard on that and skip rendering. */
|
|
27
|
+
export function useScreenOverlayContainer() {
|
|
28
|
+
return useContext(ScreenOverlayContext)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function Screen({
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
contentClassName,
|
|
35
|
+
size = 'lg',
|
|
36
|
+
style,
|
|
37
|
+
topNav,
|
|
38
|
+
overlay,
|
|
39
|
+
}: {
|
|
40
|
+
children: ReactNode
|
|
41
|
+
className?: string | undefined
|
|
42
|
+
contentClassName?: string | undefined
|
|
43
|
+
size?: 'sm' | 'md' | 'lg' | undefined
|
|
44
|
+
style?: CSSProperties | undefined
|
|
45
|
+
topNav?: ReactNode
|
|
46
|
+
overlay?: ReactNode
|
|
47
|
+
}) {
|
|
48
|
+
// Overlay container ref goes into state so children re-render once the DOM
|
|
49
|
+
// node is available — that lets deeply nested components own their own
|
|
50
|
+
// overlay state and portal into the card without hoisting it up here.
|
|
51
|
+
const [overlayContainer, setOverlayContainer] =
|
|
52
|
+
useState<HTMLDivElement | null>(null)
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-zd-size={size}
|
|
57
|
+
className={cn(
|
|
58
|
+
// 400×810 by default, but never taller/wider than the parent — a
|
|
59
|
+
// consumer wrapper with its own size caps the Screen (max-w/h-full =
|
|
60
|
+
// 100% of the parent). overflow-hidden clips the frame to its rounded
|
|
61
|
+
// border ring; content scrolls in the inner div below.
|
|
62
|
+
'zd:relative zd:flex zd:flex-col zd:text-left',
|
|
63
|
+
// w-100 = 400px, h-202.5 = 810px at density 1; both compile to
|
|
64
|
+
// calc(var(--zd-spacing) * N), so they scale with the size variants.
|
|
65
|
+
// rounded-[38px] = inner card radius (32px) + the 6px border-ring gap,
|
|
66
|
+
// so the ring has a uniform width — equal inner/outer radii would make
|
|
67
|
+
// the corners read thicker than the sides.
|
|
68
|
+
'zd:w-100 zd:h-202.5 zd:max-w-full zd:max-h-full zd:overflow-hidden zd:rounded-[38px]',
|
|
69
|
+
className,
|
|
70
|
+
)}
|
|
71
|
+
style={style}
|
|
72
|
+
>
|
|
73
|
+
{/* Gradient border layer — fills the frame; the card on top has a 6px
|
|
74
|
+
margin so this shows as the border ring. */}
|
|
75
|
+
<WrapperBorder />
|
|
76
|
+
{/* Inner card sits on top with a 6px margin, exposing the border ring.
|
|
77
|
+
Its own gradient (base + CardGlow) fills the card so the colour stays
|
|
78
|
+
vivid and distinct from the border. */}
|
|
79
|
+
<div
|
|
80
|
+
ref={setOverlayContainer}
|
|
81
|
+
className={cn(
|
|
82
|
+
// m-1.5 = calc(var(--zd-spacing) * 1.5) = 6px at density 1, so the
|
|
83
|
+
// gradient border ring thins with the size variants. (Corner radii
|
|
84
|
+
// stay fixed, so at smaller sizes the ring is marginally non-uniform
|
|
85
|
+
// at the corners — a minor cosmetic trade-off.)
|
|
86
|
+
'zd:flex zd:flex-1 zd:flex-col zd:m-1.5 zd:px-4 zd:overflow-hidden zd:rounded-4xl zd:relative',
|
|
87
|
+
contentClassName,
|
|
88
|
+
)}
|
|
89
|
+
// clip-path clips backdrop-filter to the rounded corners; plain
|
|
90
|
+
// overflow-hidden does not (WebKit/Chromium leaves scrolled blurred
|
|
91
|
+
// children bleeding past the radius). Must match rounded-4xl (32px).
|
|
92
|
+
style={{ clipPath: 'inset(0 round 32px)' }}
|
|
93
|
+
>
|
|
94
|
+
<MultiRadialBackground />
|
|
95
|
+
<CardGlow />
|
|
96
|
+
<div className="zd:relative zd:z-10 zd:flex zd:flex-1 zd:flex-col zd:min-h-0">
|
|
97
|
+
{topNav}
|
|
98
|
+
<div
|
|
99
|
+
className="zd:flex zd:flex-1 zd:flex-col zd:min-h-0 zd:overflow-y-auto zd:overflow-x-hidden zd:-mr-4 zd:pr-4"
|
|
100
|
+
// Scale via --zd-spacing (matches TopNav's scaled height) so the
|
|
101
|
+
// top padding shrinks with the frame — otherwise the fixed 68px
|
|
102
|
+
// eats a disproportionate share at smaller sizes and overflows.
|
|
103
|
+
style={{
|
|
104
|
+
paddingTop: `calc(${CONTENT_PADDING_TOP / 4} * var(--zd-spacing))`,
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
<ScreenOverlayContext.Provider value={overlayContainer}>
|
|
108
|
+
{children}
|
|
109
|
+
</ScreenOverlayContext.Provider>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
{/* Overlay is a sibling of the inner card, not a child — so it escapes
|
|
114
|
+
the card's clip-path + horizontal padding, and its own absolute
|
|
115
|
+
`inset-0` fills the outer 400×810 frame. That lets the backdrop dim
|
|
116
|
+
the TopNav band too and lets a bottom sheet anchor to the outer
|
|
117
|
+
frame's bottom, not the padded inner card. */}
|
|
118
|
+
{overlay}
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Select as SelectPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps, Ref } from 'react'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../utils/common'
|
|
5
|
+
import { Icon } from '../Icon'
|
|
6
|
+
import { Wrapper } from '../Wrapper'
|
|
7
|
+
|
|
8
|
+
export const Select = SelectPrimitive.Root
|
|
9
|
+
export const SelectGroup = SelectPrimitive.Group
|
|
10
|
+
export const SelectValue = SelectPrimitive.Value
|
|
11
|
+
export const SelectItemText = SelectPrimitive.ItemText
|
|
12
|
+
|
|
13
|
+
/** Bare styled trigger. Use `asChild` to wrap a custom trigger like `Pill`. */
|
|
14
|
+
export function SelectTrigger({
|
|
15
|
+
ref,
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
20
|
+
ref?: Ref<HTMLButtonElement>
|
|
21
|
+
}) {
|
|
22
|
+
return (
|
|
23
|
+
<SelectPrimitive.Trigger
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn(
|
|
26
|
+
'zd:inline-flex zd:items-center zd:justify-between zd:gap-2',
|
|
27
|
+
'zd:outline-none zd:cursor-pointer',
|
|
28
|
+
// Counters Radix's `pointer-events: none` on the trigger while the
|
|
29
|
+
// panel is open — without this the trigger goes inert and can't be
|
|
30
|
+
// clicked to toggle closed.
|
|
31
|
+
'zd:data-[state=open]:pointer-events-auto',
|
|
32
|
+
'zd:disabled:cursor-not-allowed zd:disabled:opacity-50',
|
|
33
|
+
className,
|
|
34
|
+
)}
|
|
35
|
+
{...props}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</SelectPrimitive.Trigger>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Trigger affordance slot. Defaults to a chevronDown icon. */
|
|
43
|
+
export function SelectIcon({
|
|
44
|
+
ref,
|
|
45
|
+
className,
|
|
46
|
+
children,
|
|
47
|
+
...props
|
|
48
|
+
}: ComponentProps<typeof SelectPrimitive.Icon> & {
|
|
49
|
+
ref?: Ref<HTMLSpanElement>
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<SelectPrimitive.Icon
|
|
53
|
+
ref={ref}
|
|
54
|
+
className={cn('zd:shrink-0', className)}
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
{children ?? (
|
|
58
|
+
<Icon name="chevronDown" className="zd:size-4 zd:text-greyScale" />
|
|
59
|
+
)}
|
|
60
|
+
</SelectPrimitive.Icon>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Portaled popper panel. Width defaults to `--radix-select-trigger-width`;
|
|
65
|
+
* override via inline `style={{ width }}`. */
|
|
66
|
+
export function SelectContent({
|
|
67
|
+
ref,
|
|
68
|
+
className,
|
|
69
|
+
children,
|
|
70
|
+
position = 'popper',
|
|
71
|
+
sideOffset = 8,
|
|
72
|
+
style,
|
|
73
|
+
...props
|
|
74
|
+
}: ComponentProps<typeof SelectPrimitive.Content> & {
|
|
75
|
+
ref?: Ref<HTMLDivElement>
|
|
76
|
+
}) {
|
|
77
|
+
return (
|
|
78
|
+
<SelectPrimitive.Portal>
|
|
79
|
+
<SelectPrimitive.Content
|
|
80
|
+
ref={ref}
|
|
81
|
+
position={position}
|
|
82
|
+
sideOffset={sideOffset}
|
|
83
|
+
className={cn(
|
|
84
|
+
'zd:z-50 zd:max-h-80 zd:outline-none',
|
|
85
|
+
'zd:data-[state=open]:animate-popper-in',
|
|
86
|
+
className,
|
|
87
|
+
)}
|
|
88
|
+
{...props}
|
|
89
|
+
style={{
|
|
90
|
+
width: 'var(--radix-select-trigger-width)',
|
|
91
|
+
transformOrigin: 'var(--radix-select-content-transform-origin)',
|
|
92
|
+
...style,
|
|
93
|
+
}}
|
|
94
|
+
>
|
|
95
|
+
<Wrapper
|
|
96
|
+
variant="solid"
|
|
97
|
+
// Override Wrapper's 80% white translucency — a dropdown over
|
|
98
|
+
// content shouldn't let the content bleed through.
|
|
99
|
+
style={{ backgroundColor: '#fff' }}
|
|
100
|
+
className="zd:flex zd:flex-col zd:rounded-2xl zd:overflow-hidden"
|
|
101
|
+
>
|
|
102
|
+
<SelectPrimitive.Viewport className="zd:overflow-y-auto zd:max-h-80">
|
|
103
|
+
{children}
|
|
104
|
+
</SelectPrimitive.Viewport>
|
|
105
|
+
</Wrapper>
|
|
106
|
+
</SelectPrimitive.Content>
|
|
107
|
+
</SelectPrimitive.Portal>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Option row. Children render as-is; set `textValue` for typeahead when
|
|
112
|
+
* children aren't plain text. */
|
|
113
|
+
export function SelectItem({
|
|
114
|
+
ref,
|
|
115
|
+
className,
|
|
116
|
+
children,
|
|
117
|
+
...props
|
|
118
|
+
}: ComponentProps<typeof SelectPrimitive.Item> & {
|
|
119
|
+
ref?: Ref<HTMLDivElement>
|
|
120
|
+
}) {
|
|
121
|
+
return (
|
|
122
|
+
<SelectPrimitive.Item
|
|
123
|
+
ref={ref}
|
|
124
|
+
className={cn(
|
|
125
|
+
// Default padding for plain-text items. Complex children with their
|
|
126
|
+
// own padding (e.g. `<TokenListItem>`) opt out via `className="zd:p-0"`.
|
|
127
|
+
'zd:relative zd:outline-none zd:cursor-pointer zd:px-3 zd:py-2',
|
|
128
|
+
'zd:data-[highlighted]:bg-offWhite/40',
|
|
129
|
+
'zd:data-[state=checked]:bg-offWhite/60',
|
|
130
|
+
className,
|
|
131
|
+
)}
|
|
132
|
+
{...props}
|
|
133
|
+
>
|
|
134
|
+
{children}
|
|
135
|
+
</SelectPrimitive.Item>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function SelectSeparator({
|
|
140
|
+
ref,
|
|
141
|
+
className,
|
|
142
|
+
...props
|
|
143
|
+
}: ComponentProps<typeof SelectPrimitive.Separator> & {
|
|
144
|
+
ref?: Ref<HTMLDivElement>
|
|
145
|
+
}) {
|
|
146
|
+
return (
|
|
147
|
+
<SelectPrimitive.Separator
|
|
148
|
+
ref={ref}
|
|
149
|
+
className={cn('zd:h-px zd:bg-offWhite/50 zd:mx-2 zd:my-1', className)}
|
|
150
|
+
{...props}
|
|
151
|
+
/>
|
|
152
|
+
)
|
|
153
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, HTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils/common'
|
|
4
|
+
import { Icon, type IconName } from '../Icon'
|
|
5
|
+
import { Text } from '../Text'
|
|
6
|
+
|
|
7
|
+
export type TokenListItemIconVariant = 'token' | 'network'
|
|
8
|
+
|
|
9
|
+
interface CommonProps {
|
|
10
|
+
/** Primary text — usually a token symbol like "ETH". */
|
|
11
|
+
symbol: string
|
|
12
|
+
/** Subtitle text under the symbol (e.g. "3 networks" or "Arbitrum"). */
|
|
13
|
+
subtitle?: string
|
|
14
|
+
/** Kit `Icon` name for the main slot (preferred for known chains/tokens). */
|
|
15
|
+
iconName?: IconName
|
|
16
|
+
/** URL of the main icon image (token logo). Used when `iconName` is absent. */
|
|
17
|
+
imageSource?: string
|
|
18
|
+
/** Kit `Icon` name rendered inline before the subtitle text (e.g. chain logo). */
|
|
19
|
+
subtitleIcon?: IconName
|
|
20
|
+
/** Right-aligned value (e.g. "$0.00"). */
|
|
21
|
+
value?: string
|
|
22
|
+
/** Right-aligned percentage change. Coloured green by default, red when
|
|
23
|
+
* the string starts with a leading "-". */
|
|
24
|
+
change?: string
|
|
25
|
+
/** Shape of the main icon. `'token'` = 44x44 rounded square (default),
|
|
26
|
+
* `'network'` = 36x36 circle. */
|
|
27
|
+
iconVariant?: TokenListItemIconVariant
|
|
28
|
+
/** Render a skeleton placeholder instead of the content. */
|
|
29
|
+
loading?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type TokenListItemProps = CommonProps &
|
|
33
|
+
(
|
|
34
|
+
| ({ onClick: ButtonHTMLAttributes<HTMLButtonElement>['onClick'] } & Omit<
|
|
35
|
+
ButtonHTMLAttributes<HTMLButtonElement>,
|
|
36
|
+
keyof CommonProps | 'children'
|
|
37
|
+
>)
|
|
38
|
+
| ({ onClick?: undefined } & Omit<
|
|
39
|
+
HTMLAttributes<HTMLDivElement>,
|
|
40
|
+
keyof CommonProps | 'children'
|
|
41
|
+
>)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
function IconBlock({
|
|
45
|
+
iconName,
|
|
46
|
+
imageSource,
|
|
47
|
+
variant,
|
|
48
|
+
}: {
|
|
49
|
+
iconName?: IconName
|
|
50
|
+
imageSource?: string
|
|
51
|
+
variant: TokenListItemIconVariant
|
|
52
|
+
}) {
|
|
53
|
+
const isNetwork = variant === 'network'
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
className={cn(
|
|
57
|
+
'zd:shrink-0 zd:bg-white zd:flex zd:items-center zd:justify-center zd:overflow-hidden',
|
|
58
|
+
isNetwork
|
|
59
|
+
? 'zd:w-9 zd:h-9 zd:rounded-full'
|
|
60
|
+
: 'zd:w-11 zd:h-11 zd:rounded-2xl',
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{iconName ? (
|
|
64
|
+
<Icon name={iconName} className="zd:w-6 zd:h-6" />
|
|
65
|
+
) : imageSource ? (
|
|
66
|
+
<img
|
|
67
|
+
src={imageSource}
|
|
68
|
+
alt=""
|
|
69
|
+
className="zd:w-6 zd:h-6 zd:object-contain"
|
|
70
|
+
/>
|
|
71
|
+
) : null}
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function TokenListItemSkeleton({
|
|
77
|
+
className,
|
|
78
|
+
}: {
|
|
79
|
+
className?: string | undefined
|
|
80
|
+
}) {
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
className={cn(
|
|
84
|
+
'zd:w-full zd:p-2 zd:flex zd:flex-row zd:items-center zd:justify-between zd:gap-2',
|
|
85
|
+
className,
|
|
86
|
+
)}
|
|
87
|
+
>
|
|
88
|
+
<div className="zd:flex zd:flex-row zd:items-center zd:gap-2 zd:min-w-0">
|
|
89
|
+
<div className="zd:w-11 zd:h-11 zd:shrink-0 zd:rounded-2xl zd:bg-greyScale/15 zd:animate-skel-pulse" />
|
|
90
|
+
<div className="zd:flex zd:flex-col zd:gap-2">
|
|
91
|
+
<div className="zd:h-3 zd:w-14 zd:rounded-md zd:bg-greyScale/15 zd:animate-skel-pulse" />
|
|
92
|
+
<div className="zd:h-3 zd:w-10 zd:rounded-md zd:bg-greyScale/15 zd:animate-skel-pulse" />
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="zd:flex zd:flex-col zd:items-end zd:gap-2 zd:shrink-0">
|
|
96
|
+
<div className="zd:h-3 zd:w-12 zd:rounded-md zd:bg-greyScale/15 zd:animate-skel-pulse" />
|
|
97
|
+
<div className="zd:h-3 zd:w-10 zd:rounded-md zd:bg-greyScale/15 zd:animate-skel-pulse" />
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function TokenListItem({
|
|
104
|
+
symbol,
|
|
105
|
+
subtitle,
|
|
106
|
+
iconName,
|
|
107
|
+
imageSource,
|
|
108
|
+
subtitleIcon,
|
|
109
|
+
value,
|
|
110
|
+
change,
|
|
111
|
+
iconVariant = 'token',
|
|
112
|
+
loading = false,
|
|
113
|
+
className,
|
|
114
|
+
onClick,
|
|
115
|
+
...rest
|
|
116
|
+
}: TokenListItemProps) {
|
|
117
|
+
if (loading) return <TokenListItemSkeleton className={className} />
|
|
118
|
+
|
|
119
|
+
const changeColor = change?.startsWith('-')
|
|
120
|
+
? 'zd:text-negative'
|
|
121
|
+
: 'zd:text-positive'
|
|
122
|
+
|
|
123
|
+
const content = (
|
|
124
|
+
<>
|
|
125
|
+
<div className="zd:flex zd:flex-row zd:items-center zd:gap-2 zd:min-w-0">
|
|
126
|
+
<IconBlock
|
|
127
|
+
{...(iconName && { iconName })}
|
|
128
|
+
{...(imageSource && { imageSource })}
|
|
129
|
+
variant={iconVariant}
|
|
130
|
+
/>
|
|
131
|
+
<div className="zd:flex zd:flex-col zd:items-start zd:gap-2 zd:min-w-0">
|
|
132
|
+
<Text className="zd:text-body1 zd:truncate">{symbol}</Text>
|
|
133
|
+
{subtitle && (
|
|
134
|
+
<div className="zd:flex zd:flex-row zd:items-center zd:gap-1 zd:min-w-0">
|
|
135
|
+
{subtitleIcon && (
|
|
136
|
+
<span className="zd:inline-flex zd:w-3 zd:h-3 zd:rounded-full zd:bg-offWhite/50 zd:overflow-hidden zd:shrink-0 zd:items-center zd:justify-center">
|
|
137
|
+
<Icon name={subtitleIcon} className="zd:w-full zd:h-full" />
|
|
138
|
+
</span>
|
|
139
|
+
)}
|
|
140
|
+
<Text className="zd:text-body3 zd:text-greyScale/50 zd:truncate">
|
|
141
|
+
{subtitle}
|
|
142
|
+
</Text>
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
{(value || change) && (
|
|
148
|
+
<div className="zd:flex zd:flex-col zd:items-end zd:gap-2 zd:shrink-0">
|
|
149
|
+
{value && <Text className="zd:text-body1 zd:truncate">{value}</Text>}
|
|
150
|
+
{change && (
|
|
151
|
+
<Text className={cn('zd:text-body3 zd:truncate', changeColor)}>
|
|
152
|
+
{change}
|
|
153
|
+
</Text>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
</>
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
const baseClassName = cn(
|
|
161
|
+
'zd:w-full zd:p-2 zd:flex zd:flex-row zd:items-center zd:justify-between zd:gap-2 zd:text-left',
|
|
162
|
+
className,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if (onClick) {
|
|
166
|
+
return (
|
|
167
|
+
<button
|
|
168
|
+
type="button"
|
|
169
|
+
onClick={onClick}
|
|
170
|
+
className={cn(
|
|
171
|
+
baseClassName,
|
|
172
|
+
'zd:transition-colors zd:rounded-xl',
|
|
173
|
+
(rest as ButtonHTMLAttributes<HTMLButtonElement>).disabled
|
|
174
|
+
? 'zd:opacity-50 zd:cursor-not-allowed'
|
|
175
|
+
: 'zd:cursor-pointer zd:hover:bg-offWhite/40',
|
|
176
|
+
)}
|
|
177
|
+
{...(rest as ButtonHTMLAttributes<HTMLButtonElement>)}
|
|
178
|
+
>
|
|
179
|
+
{content}
|
|
180
|
+
</button>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<div
|
|
186
|
+
className={baseClassName}
|
|
187
|
+
{...(rest as HTMLAttributes<HTMLDivElement>)}
|
|
188
|
+
>
|
|
189
|
+
{content}
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { cn } from '../../utils/common'
|
|
3
|
+
import type { IconName } from '../Icon'
|
|
4
|
+
import { IconButton } from '../IconButton'
|
|
5
|
+
import { Text } from '../Text'
|
|
6
|
+
|
|
7
|
+
export const TOP_NAV_HEIGHT = 52
|
|
8
|
+
|
|
9
|
+
export interface TopNavProps {
|
|
10
|
+
/** Handler for the left icon button. When omitted, the left slot renders
|
|
11
|
+
* an empty spacer so the title/logo stays centred. */
|
|
12
|
+
onLeftButtonClick?: () => void
|
|
13
|
+
/** Handler for the right icon button. When omitted, the right slot
|
|
14
|
+
* renders an empty spacer. */
|
|
15
|
+
onRightButtonClick?: () => void
|
|
16
|
+
/** Icon rendered in the left slot. Defaults to `'chevronLeft'` (back). */
|
|
17
|
+
leftButtonIcon?: IconName
|
|
18
|
+
/** Icon rendered in the right slot. Defaults to `'x'` (close). */
|
|
19
|
+
rightButtonIcon?: IconName
|
|
20
|
+
title?: string
|
|
21
|
+
logo?: ReactNode
|
|
22
|
+
className?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function TopNav({
|
|
26
|
+
onLeftButtonClick,
|
|
27
|
+
onRightButtonClick,
|
|
28
|
+
leftButtonIcon = 'chevronLeft',
|
|
29
|
+
rightButtonIcon = 'x',
|
|
30
|
+
title,
|
|
31
|
+
logo,
|
|
32
|
+
className,
|
|
33
|
+
}: TopNavProps) {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
className={cn(
|
|
37
|
+
'zd:absolute zd:top-4 zd:left-0 zd:right-0 zd:flex zd:flex-row zd:items-center zd:justify-between',
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
// Scale via --zd-spacing (the 4px base) like every spacing utility, so
|
|
41
|
+
// the nav height tracks the density variants and doesn't eat a
|
|
42
|
+
// disproportionate share of the shrunken frame at smaller sizes.
|
|
43
|
+
style={{ height: `calc(${TOP_NAV_HEIGHT / 4} * var(--zd-spacing))` }}
|
|
44
|
+
>
|
|
45
|
+
{onLeftButtonClick ? (
|
|
46
|
+
<IconButton iconName={leftButtonIcon} onClick={onLeftButtonClick} />
|
|
47
|
+
) : (
|
|
48
|
+
<div className="zd:h-13 zd:w-13" />
|
|
49
|
+
)}
|
|
50
|
+
{logo && (
|
|
51
|
+
<div className="zd:absolute zd:left-0 zd:right-0 zd:flex zd:items-center zd:justify-center zd:pointer-events-none">
|
|
52
|
+
{logo}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
{title && (
|
|
56
|
+
<div className="zd:absolute zd:left-0 zd:right-0 zd:flex zd:items-center zd:justify-center zd:pointer-events-none">
|
|
57
|
+
<Text className="zd:text-body1">{title}</Text>
|
|
58
|
+
</div>
|
|
59
|
+
)}
|
|
60
|
+
{onRightButtonClick ? (
|
|
61
|
+
<IconButton iconName={rightButtonIcon} onClick={onRightButtonClick} />
|
|
62
|
+
) : (
|
|
63
|
+
<div className="zd:h-13 zd:w-13" />
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HTMLAttributes, PropsWithChildren } from 'react'
|
|
1
|
+
import type { HTMLAttributes, PropsWithChildren, Ref } from 'react'
|
|
2
2
|
|
|
3
3
|
import { cn } from '../../utils/common'
|
|
4
4
|
|
|
@@ -6,6 +6,7 @@ export type WrapperVariant = 'ghost' | 'soft' | 'solid'
|
|
|
6
6
|
|
|
7
7
|
export interface WrapperProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
8
|
variant?: WrapperVariant
|
|
9
|
+
ref?: Ref<HTMLDivElement> | undefined
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
function getBackgroundAlpha(variant: WrapperVariant): number {
|
|
@@ -23,6 +24,7 @@ export function Wrapper({
|
|
|
23
24
|
className,
|
|
24
25
|
children,
|
|
25
26
|
variant = 'soft',
|
|
27
|
+
ref,
|
|
26
28
|
...rest
|
|
27
29
|
}: PropsWithChildren<WrapperProps>) {
|
|
28
30
|
const backgroundAlpha = getBackgroundAlpha(variant)
|
|
@@ -30,6 +32,7 @@ export function Wrapper({
|
|
|
30
32
|
|
|
31
33
|
return (
|
|
32
34
|
<div
|
|
35
|
+
ref={ref}
|
|
33
36
|
className={cn(
|
|
34
37
|
'zd:overflow-hidden zd:border-offWhite zd:border-[0.3px] zd:backdrop-blur-[15px]',
|
|
35
38
|
className,
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { FC, SVGProps } from 'react'
|
|
2
|
+
|
|
3
|
+
import LockupBlack from '../../../assets/brand/zerodev-lockup-black.svg?react'
|
|
4
|
+
import LockupOffwhite from '../../../assets/brand/zerodev-lockup-offwhite.svg?react'
|
|
5
|
+
import MarkBlack from '../../../assets/brand/zerodev-mark-black.svg?react'
|
|
6
|
+
import MarkColor from '../../../assets/brand/zerodev-mark-color.svg?react'
|
|
7
|
+
import MarkOffwhite from '../../../assets/brand/zerodev-mark-offwhite.svg?react'
|
|
8
|
+
import MarkOrange from '../../../assets/brand/zerodev-mark-orange.svg?react'
|
|
9
|
+
|
|
10
|
+
// The `mark` (icon only) ships in four tones; the `lockup` (mark + wordmark)
|
|
11
|
+
// only in black/offwhite — there is no colorful lockup asset. The union keeps
|
|
12
|
+
// invalid combinations (e.g. lockup + color) from type-checking.
|
|
13
|
+
export type MarkTone = 'black' | 'offwhite' | 'color' | 'orange'
|
|
14
|
+
export type LockupTone = 'black' | 'offwhite'
|
|
15
|
+
|
|
16
|
+
export type ZeroDevLogoProps = SVGProps<SVGSVGElement> &
|
|
17
|
+
(
|
|
18
|
+
| { variant?: 'mark'; tone?: MarkTone }
|
|
19
|
+
| { variant: 'lockup'; tone?: LockupTone }
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
export function ZeroDevLogo({
|
|
23
|
+
variant = 'mark',
|
|
24
|
+
tone = 'black',
|
|
25
|
+
...props
|
|
26
|
+
}: ZeroDevLogoProps) {
|
|
27
|
+
// Brand SVGs carry literal fills (the tone IS the color), so no text-color
|
|
28
|
+
// class is applied — unlike Icon, currentColor would have no effect here.
|
|
29
|
+
let Svg: FC<SVGProps<SVGSVGElement>>
|
|
30
|
+
if (variant === 'lockup') {
|
|
31
|
+
switch (tone) {
|
|
32
|
+
case 'offwhite':
|
|
33
|
+
Svg = LockupOffwhite
|
|
34
|
+
break
|
|
35
|
+
default:
|
|
36
|
+
Svg = LockupBlack
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
switch (tone) {
|
|
40
|
+
case 'offwhite':
|
|
41
|
+
Svg = MarkOffwhite
|
|
42
|
+
break
|
|
43
|
+
case 'color':
|
|
44
|
+
Svg = MarkColor
|
|
45
|
+
break
|
|
46
|
+
case 'orange':
|
|
47
|
+
Svg = MarkOrange
|
|
48
|
+
break
|
|
49
|
+
default:
|
|
50
|
+
Svg = MarkBlack
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return <Svg {...props} />
|
|
55
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,9 +3,26 @@
|
|
|
3
3
|
* React UI primitives for ZeroDev
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
export {
|
|
7
|
+
ArrowCardPair,
|
|
8
|
+
type ArrowCardPairProps,
|
|
9
|
+
ArrowView,
|
|
10
|
+
} from './components/ArrowCardPair'
|
|
6
11
|
export { Badge, type BadgeProps } from './components/Badge'
|
|
12
|
+
export {
|
|
13
|
+
BottomSheet,
|
|
14
|
+
BottomSheetClose,
|
|
15
|
+
BottomSheetContent,
|
|
16
|
+
BottomSheetTitle,
|
|
17
|
+
} from './components/BottomSheet'
|
|
7
18
|
export { Button, type ButtonProps } from './components/Button'
|
|
8
19
|
export { Callout, type CalloutProps } from './components/Callout'
|
|
20
|
+
export {
|
|
21
|
+
DataRow,
|
|
22
|
+
type DataRowProps,
|
|
23
|
+
DataRowSkeleton,
|
|
24
|
+
type DataRowSkeletonProps,
|
|
25
|
+
} from './components/DataRow'
|
|
9
26
|
export {
|
|
10
27
|
Icon,
|
|
11
28
|
type IconName,
|
|
@@ -16,11 +33,38 @@ export { IconButton, type IconButtonProps } from './components/IconButton'
|
|
|
16
33
|
export { Input, type InputProps } from './components/Input'
|
|
17
34
|
export {
|
|
18
35
|
ListItem,
|
|
36
|
+
ListItemChevron,
|
|
37
|
+
ListItemIcon,
|
|
38
|
+
type ListItemIconProps,
|
|
19
39
|
type ListItemProps,
|
|
20
40
|
ListItemSkeleton,
|
|
21
41
|
} from './components/ListItem'
|
|
42
|
+
export {
|
|
43
|
+
Pill,
|
|
44
|
+
type PillProps,
|
|
45
|
+
PillSkeleton,
|
|
46
|
+
} from './components/Pill'
|
|
47
|
+
export { PoweredBy } from './components/PoweredBy'
|
|
48
|
+
export { Screen } from './components/Screen'
|
|
49
|
+
export {
|
|
50
|
+
Select,
|
|
51
|
+
SelectContent,
|
|
52
|
+
SelectGroup,
|
|
53
|
+
SelectIcon,
|
|
54
|
+
SelectItem,
|
|
55
|
+
SelectItemText,
|
|
56
|
+
SelectSeparator,
|
|
57
|
+
SelectTrigger,
|
|
58
|
+
SelectValue,
|
|
59
|
+
} from './components/Select'
|
|
22
60
|
export { Switch, type SwitchProps } from './components/Switch'
|
|
23
61
|
export { Text, type TextProps } from './components/Text'
|
|
62
|
+
export {
|
|
63
|
+
TokenListItem,
|
|
64
|
+
type TokenListItemIconVariant,
|
|
65
|
+
type TokenListItemProps,
|
|
66
|
+
} from './components/TokenListItem'
|
|
67
|
+
export { TopNav, type TopNavProps } from './components/TopNav'
|
|
24
68
|
export {
|
|
25
69
|
WrappedPressable,
|
|
26
70
|
type WrappedPressableProps,
|
|
@@ -30,4 +74,10 @@ export {
|
|
|
30
74
|
type WrapperProps,
|
|
31
75
|
type WrapperVariant,
|
|
32
76
|
} from './components/Wrapper'
|
|
77
|
+
export {
|
|
78
|
+
type LockupTone,
|
|
79
|
+
type MarkTone,
|
|
80
|
+
ZeroDevLogo,
|
|
81
|
+
type ZeroDevLogoProps,
|
|
82
|
+
} from './components/ZeroDevLogo'
|
|
33
83
|
export { cn } from './utils/common'
|