@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.
Files changed (72) hide show
  1. package/README.md +1 -0
  2. package/dist/_types/components/ArrowCardPair/index.d.ts +10 -0
  3. package/dist/_types/components/ArrowCardPair/index.d.ts.map +1 -0
  4. package/dist/_types/components/Badge/index.d.ts +1 -1
  5. package/dist/_types/components/Badge/index.d.ts.map +1 -1
  6. package/dist/_types/components/BottomSheet/index.d.ts +17 -0
  7. package/dist/_types/components/BottomSheet/index.d.ts.map +1 -0
  8. package/dist/_types/components/Button/index.d.ts +1 -1
  9. package/dist/_types/components/Button/index.d.ts.map +1 -1
  10. package/dist/_types/components/Callout/index.d.ts +1 -1
  11. package/dist/_types/components/Callout/index.d.ts.map +1 -1
  12. package/dist/_types/components/DataRow/index.d.ts +33 -0
  13. package/dist/_types/components/DataRow/index.d.ts.map +1 -0
  14. package/dist/_types/components/Icon/index.d.ts +1 -1
  15. package/dist/_types/components/Icon/index.d.ts.map +1 -1
  16. package/dist/_types/components/IconButton/index.d.ts +1 -1
  17. package/dist/_types/components/IconButton/index.d.ts.map +1 -1
  18. package/dist/_types/components/Input/index.d.ts +1 -1
  19. package/dist/_types/components/Input/index.d.ts.map +1 -1
  20. package/dist/_types/components/ListItem/index.d.ts +32 -15
  21. package/dist/_types/components/ListItem/index.d.ts.map +1 -1
  22. package/dist/_types/components/Pill/index.d.ts +25 -0
  23. package/dist/_types/components/Pill/index.d.ts.map +1 -0
  24. package/dist/_types/components/PoweredBy/index.d.ts +6 -0
  25. package/dist/_types/components/PoweredBy/index.d.ts.map +1 -0
  26. package/dist/_types/components/Screen/EllipticalRadial.d.ts +23 -0
  27. package/dist/_types/components/Screen/EllipticalRadial.d.ts.map +1 -0
  28. package/dist/_types/components/Screen/MultiRadialBackground.d.ts +4 -0
  29. package/dist/_types/components/Screen/MultiRadialBackground.d.ts.map +1 -0
  30. package/dist/_types/components/Screen/index.d.ts +15 -0
  31. package/dist/_types/components/Screen/index.d.ts.map +1 -0
  32. package/dist/_types/components/Select/index.d.ts +28 -0
  33. package/dist/_types/components/Select/index.d.ts.map +1 -0
  34. package/dist/_types/components/Switch/index.d.ts +1 -1
  35. package/dist/_types/components/Switch/index.d.ts.map +1 -1
  36. package/dist/_types/components/Text/index.d.ts +1 -1
  37. package/dist/_types/components/Text/index.d.ts.map +1 -1
  38. package/dist/_types/components/TokenListItem/index.d.ts +33 -0
  39. package/dist/_types/components/TokenListItem/index.d.ts.map +1 -0
  40. package/dist/_types/components/TopNav/index.d.ts +20 -0
  41. package/dist/_types/components/TopNav/index.d.ts.map +1 -0
  42. package/dist/_types/components/WrappedPressable/index.d.ts +1 -1
  43. package/dist/_types/components/WrappedPressable/index.d.ts.map +1 -1
  44. package/dist/_types/components/Wrapper/index.d.ts +3 -2
  45. package/dist/_types/components/Wrapper/index.d.ts.map +1 -1
  46. package/dist/_types/components/ZeroDevLogo/index.d.ts +12 -0
  47. package/dist/_types/components/ZeroDevLogo/index.d.ts.map +1 -0
  48. package/dist/_types/index.d.ts +11 -1
  49. package/dist/_types/index.d.ts.map +1 -1
  50. package/dist/index.cjs +111 -1
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.mjs +6164 -1199
  53. package/dist/index.mjs.map +1 -1
  54. package/dist/styles.css +2 -2
  55. package/package.json +7 -5
  56. package/src/components/ArrowCardPair/index.tsx +148 -0
  57. package/src/components/BottomSheet/index.tsx +57 -0
  58. package/src/components/DataRow/index.tsx +140 -0
  59. package/src/components/ListItem/index.tsx +74 -79
  60. package/src/components/Pill/index.tsx +167 -0
  61. package/src/components/PoweredBy/index.tsx +29 -0
  62. package/src/components/Screen/EllipticalRadial.tsx +65 -0
  63. package/src/components/Screen/MultiRadialBackground.tsx +323 -0
  64. package/src/components/Screen/index.tsx +121 -0
  65. package/src/components/Select/index.tsx +153 -0
  66. package/src/components/TokenListItem/index.tsx +192 -0
  67. package/src/components/TopNav/index.tsx +67 -0
  68. package/src/components/Wrapper/index.tsx +4 -1
  69. package/src/components/ZeroDevLogo/index.tsx +55 -0
  70. package/src/index.ts +50 -0
  71. package/src/svg.d.ts +9 -0
  72. 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'