@zerodev/react-ui 0.0.3 → 0.0.5

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 (84) hide show
  1. package/dist/_types/components/ArrowCardPair/index.d.ts +2 -2
  2. package/dist/_types/components/ArrowCardPair/index.d.ts.map +1 -1
  3. package/dist/_types/components/Badge/index.d.ts +1 -1
  4. package/dist/_types/components/Badge/index.d.ts.map +1 -1
  5. package/dist/_types/components/BottomSheet/index.d.ts +17 -0
  6. package/dist/_types/components/BottomSheet/index.d.ts.map +1 -0
  7. package/dist/_types/components/Button/index.d.ts +1 -1
  8. package/dist/_types/components/Button/index.d.ts.map +1 -1
  9. package/dist/_types/components/Callout/index.d.ts +1 -1
  10. package/dist/_types/components/Callout/index.d.ts.map +1 -1
  11. package/dist/_types/components/DataRow/index.d.ts +6 -2
  12. package/dist/_types/components/DataRow/index.d.ts.map +1 -1
  13. package/dist/_types/components/Icon/index.d.ts +1 -1
  14. package/dist/_types/components/Icon/index.d.ts.map +1 -1
  15. package/dist/_types/components/IconButton/index.d.ts +1 -1
  16. package/dist/_types/components/IconButton/index.d.ts.map +1 -1
  17. package/dist/_types/components/InfoCard/index.d.ts +17 -0
  18. package/dist/_types/components/InfoCard/index.d.ts.map +1 -0
  19. package/dist/_types/components/Input/index.d.ts +1 -1
  20. package/dist/_types/components/Input/index.d.ts.map +1 -1
  21. package/dist/_types/components/ListItem/index.d.ts +32 -15
  22. package/dist/_types/components/ListItem/index.d.ts.map +1 -1
  23. package/dist/_types/components/Pill/index.d.ts +25 -0
  24. package/dist/_types/components/Pill/index.d.ts.map +1 -0
  25. package/dist/_types/components/PoweredBy/index.d.ts +6 -0
  26. package/dist/_types/components/PoweredBy/index.d.ts.map +1 -0
  27. package/dist/_types/components/Screen/EllipticalRadial.d.ts +23 -0
  28. package/dist/_types/components/Screen/EllipticalRadial.d.ts.map +1 -0
  29. package/dist/_types/components/Screen/MultiRadialBackground.d.ts +4 -0
  30. package/dist/_types/components/Screen/MultiRadialBackground.d.ts.map +1 -0
  31. package/dist/_types/components/Screen/index.d.ts +15 -0
  32. package/dist/_types/components/Screen/index.d.ts.map +1 -0
  33. package/dist/_types/components/Section/MessageDetails/index.d.ts +5 -0
  34. package/dist/_types/components/Section/MessageDetails/index.d.ts.map +1 -0
  35. package/dist/_types/components/Section/index.d.ts +11 -0
  36. package/dist/_types/components/Section/index.d.ts.map +1 -0
  37. package/dist/_types/components/Select/index.d.ts +28 -0
  38. package/dist/_types/components/Select/index.d.ts.map +1 -0
  39. package/dist/_types/components/Switch/index.d.ts +1 -1
  40. package/dist/_types/components/Switch/index.d.ts.map +1 -1
  41. package/dist/_types/components/Text/index.d.ts +1 -1
  42. package/dist/_types/components/Text/index.d.ts.map +1 -1
  43. package/dist/_types/components/TokenListItem/index.d.ts +33 -0
  44. package/dist/_types/components/TokenListItem/index.d.ts.map +1 -0
  45. package/dist/_types/components/Tooltip/index.d.ts +43 -0
  46. package/dist/_types/components/Tooltip/index.d.ts.map +1 -0
  47. package/dist/_types/components/TopNav/index.d.ts +20 -0
  48. package/dist/_types/components/TopNav/index.d.ts.map +1 -0
  49. package/dist/_types/components/WrappedPressable/index.d.ts +1 -1
  50. package/dist/_types/components/WrappedPressable/index.d.ts.map +1 -1
  51. package/dist/_types/components/Wrapper/index.d.ts +3 -2
  52. package/dist/_types/components/Wrapper/index.d.ts.map +1 -1
  53. package/dist/_types/components/ZeroDevLogo/index.d.ts +1 -1
  54. package/dist/_types/components/ZeroDevLogo/index.d.ts.map +1 -1
  55. package/dist/_types/index.d.ts +13 -2
  56. package/dist/_types/index.d.ts.map +1 -1
  57. package/dist/_types/utils/common.d.ts +4 -0
  58. package/dist/_types/utils/common.d.ts.map +1 -1
  59. package/dist/index.cjs +51 -7
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.mjs +6538 -1341
  62. package/dist/index.mjs.map +1 -1
  63. package/dist/styles.css +2 -2
  64. package/package.json +7 -5
  65. package/src/components/ArrowCardPair/index.tsx +25 -5
  66. package/src/components/BottomSheet/index.tsx +57 -0
  67. package/src/components/DataRow/index.tsx +55 -29
  68. package/src/components/InfoCard/index.tsx +104 -0
  69. package/src/components/ListItem/index.tsx +74 -79
  70. package/src/components/Pill/index.tsx +167 -0
  71. package/src/components/PoweredBy/index.tsx +29 -0
  72. package/src/components/Screen/EllipticalRadial.tsx +65 -0
  73. package/src/components/Screen/MultiRadialBackground.tsx +323 -0
  74. package/src/components/Screen/index.tsx +121 -0
  75. package/src/components/Section/MessageDetails/index.tsx +19 -0
  76. package/src/components/Section/index.tsx +50 -0
  77. package/src/components/Select/index.tsx +153 -0
  78. package/src/components/TokenListItem/index.tsx +192 -0
  79. package/src/components/Tooltip/index.tsx +94 -0
  80. package/src/components/TopNav/index.tsx +67 -0
  81. package/src/components/Wrapper/index.tsx +4 -1
  82. package/src/index.ts +54 -1
  83. package/src/utils/common.ts +15 -0
  84. package/tailwind.config.ts +47 -0
@@ -1,7 +1,7 @@
1
- import type { ButtonHTMLAttributes } from 'react'
1
+ import { Slot } from 'radix-ui'
2
+ import type { ButtonHTMLAttributes, ReactElement, ReactNode } from 'react'
2
3
 
3
4
  import { cn } from '../../utils/common'
4
- import { Badge, type BadgeProps } from '../Badge'
5
5
  import { Icon, type IconName } from '../Icon'
6
6
  import { Text } from '../Text'
7
7
  import { Wrapper } from '../Wrapper'
@@ -20,117 +20,112 @@ export function ListItemSkeleton({ className }: ListItemSkeletonProps) {
20
20
  >
21
21
  <div className="zd:flex zd:flex-row zd:justify-between zd:items-center zd:p-2">
22
22
  <div className="zd:flex zd:flex-row zd:items-center zd:gap-3">
23
- <div className="zd:w-13 zd:h-13 zd:rounded-2xl zd:bg-offWhite/50 zd:animate-pulse" />
23
+ <div className="zd:w-13 zd:h-13 zd:rounded-2xl zd:bg-greyScale/15 zd:animate-skel-pulse" />
24
24
  <div className="zd:gap-2">
25
- <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-offWhite/50 zd:animate-pulse zd:mb-2" />
26
- <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-offWhite/50 zd:animate-pulse" />
25
+ <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-greyScale/15 zd:animate-skel-pulse zd:mb-2" />
26
+ <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-greyScale/15 zd:animate-skel-pulse" />
27
27
  </div>
28
28
  </div>
29
29
  <div className="zd:gap-2">
30
- <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-offWhite/50 zd:animate-pulse zd:mb-2" />
31
- <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-offWhite/50 zd:animate-pulse" />
30
+ <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-greyScale/15 zd:animate-skel-pulse zd:mb-2" />
31
+ <div className="zd:w-14 zd:h-3 zd:rounded-lg zd:bg-greyScale/15 zd:animate-skel-pulse" />
32
32
  </div>
33
33
  </div>
34
34
  </div>
35
35
  )
36
36
  }
37
37
 
38
- export interface ListItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
39
- iconName?: IconName
40
- imageUri?: string
38
+ interface ListItemBaseProps
39
+ extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
41
40
  title: string
42
- subtitle?: string
43
- badgeProps?: BadgeProps
44
- details?: { text: string; subtext?: string }
45
- chevron?: boolean
46
- alert?: boolean
41
+ /** Secondary line under the title — plain text (auto-styled) or any node,
42
+ * e.g. a `<Badge />`. */
43
+ subtitle?: ReactNode
44
+ /** Leading tile content — any node (`ListItemIcon`, `<img>`, a brand mark).
45
+ * Size the node itself (e.g. `zd:w-6 zd:h-6`); the tile keeps its frame. */
46
+ icon?: ReactNode
47
+ /** Right side of the row — `<ListItemChevron />` or any custom node. */
48
+ trailing?: ReactNode
47
49
  }
48
50
 
51
+ // `children` is only meaningful as the asChild target — the union makes the
52
+ // pairing a compile-time contract.
53
+ type ListItemAsChildProps =
54
+ | {
55
+ /** Render the row into the child element (e.g. an `<a>`) instead of a
56
+ * `<button>`. Pass a single childless element; the row layout is
57
+ * injected into it. */
58
+ asChild: true
59
+ children: ReactElement
60
+ }
61
+ | { asChild?: false; children?: never }
62
+
63
+ export type ListItemProps = ListItemBaseProps & ListItemAsChildProps
64
+
49
65
  export function ListItem({
50
- iconName,
51
- imageUri,
66
+ icon,
52
67
  title,
53
68
  subtitle,
54
- badgeProps,
55
- details,
56
- chevron,
57
- alert,
69
+ trailing,
70
+ asChild,
71
+ children,
58
72
  className,
59
73
  ...rest
60
74
  }: ListItemProps) {
75
+ const Comp = asChild ? Slot.Root : 'button'
61
76
  return (
62
- <Wrapper
63
- className={cn(
64
- 'zd:w-full zd:h-17 zd:rounded-2xl',
65
- alert && 'zd:border-0',
66
- className,
67
- )}
68
- >
69
- <button
70
- type="button"
77
+ <Wrapper className={cn('zd:w-full zd:h-17 zd:rounded-2xl', className)}>
78
+ <Comp
79
+ {...(!asChild && { type: 'button' as const })}
71
80
  className={cn(
72
- 'zd:w-full zd:flex zd:flex-row zd:justify-between zd:items-center zd:p-2 zd:transition-colors zd:cursor-pointer',
73
- alert
74
- ? 'zd:bg-solarOrange/15'
75
- : 'zd:hover:bg-white/20 zd:active:bg-white/30',
81
+ 'zd:w-full zd:flex zd:flex-row zd:justify-between zd:items-center zd:p-2 zd:transition-colors zd:cursor-pointer zd:hover:bg-white/20 zd:active:bg-white/30',
76
82
  rest.disabled && 'zd:opacity-50 zd:cursor-not-allowed',
77
83
  )}
78
84
  {...rest}
79
85
  >
86
+ {asChild && <Slot.Slottable>{children}</Slot.Slottable>}
80
87
  <div className="zd:flex zd:flex-row zd:items-center zd:gap-3">
81
- <div
82
- className={cn(
83
- 'zd:w-13 zd:h-13 zd:rounded-2xl zd:flex zd:items-center zd:justify-center zd:shrink-0',
84
- alert ? 'zd:bg-offWhite/50' : 'zd:bg-white',
85
- )}
86
- >
87
- {imageUri ? (
88
- <img src={imageUri} alt="" className="zd:w-6 zd:h-6" />
89
- ) : iconName ? (
90
- <Icon
91
- name={iconName}
92
- className={cn(
93
- 'zd:w-6 zd:h-6',
94
- details || alert
95
- ? 'zd:text-solarOrange'
96
- : 'zd:text-greyScale',
97
- )}
98
- />
99
- ) : null}
88
+ <div className="zd:w-13 zd:h-13 zd:rounded-2xl zd:bg-white zd:flex zd:items-center zd:justify-center zd:shrink-0">
89
+ {icon}
100
90
  </div>
101
- <div
102
- className={cn(
103
- 'zd:flex zd:flex-col zd:justify-center zd:text-left',
104
- badgeProps ? 'zd:gap-2' : 'zd:gap-1',
105
- )}
106
- >
91
+ <div className="zd:flex zd:flex-col zd:justify-center zd:text-left zd:gap-1">
107
92
  <Text className="zd:text-body1">{title}</Text>
108
- {subtitle && (
93
+ {typeof subtitle === 'string' ? (
109
94
  <Text className="zd:text-body3 zd:text-greyScale/50">
110
95
  {subtitle}
111
96
  </Text>
97
+ ) : (
98
+ subtitle
112
99
  )}
113
- {badgeProps && <Badge {...badgeProps} />}
114
100
  </div>
115
101
  </div>
116
- {details ? (
117
- <div className="zd:flex zd:flex-col zd:pr-1">
118
- <Text className="zd:text-body1">{details.text}</Text>
119
- {details.subtext && (
120
- <Text className="zd:text-body3 zd:text-greyScale/50 zd:self-end">
121
- {details.subtext}
122
- </Text>
123
- )}
124
- </div>
125
- ) : chevron ? (
126
- <div className="zd:w-13 zd:h-13 zd:flex zd:items-center zd:justify-center">
127
- <Icon
128
- name="chevronRight"
129
- className="zd:h-6 zd:w-6 zd:text-greyScale"
130
- />
131
- </div>
132
- ) : null}
133
- </button>
102
+ {trailing}
103
+ </Comp>
134
104
  </Wrapper>
135
105
  )
136
106
  }
107
+
108
+ export interface ListItemIconProps {
109
+ name: IconName
110
+ className?: string
111
+ }
112
+
113
+ /** Standard leading icon for the tile — pre-sized and colored for the slot;
114
+ * override via `className` (e.g. `zd:text-solarOrange`). */
115
+ export function ListItemIcon({ name, className }: ListItemIconProps) {
116
+ return (
117
+ <Icon
118
+ name={name}
119
+ className={cn('zd:w-6 zd:h-6 zd:text-greyScale', className)}
120
+ />
121
+ )
122
+ }
123
+
124
+ /** Standard trailing chevron for navigation rows. */
125
+ export function ListItemChevron() {
126
+ return (
127
+ <div className="zd:w-13 zd:h-13 zd:flex zd:items-center zd:justify-center">
128
+ <Icon name="chevronRight" className="zd:h-6 zd:w-6 zd:text-greyScale" />
129
+ </div>
130
+ )
131
+ }
@@ -0,0 +1,167 @@
1
+ import type { HTMLAttributes, KeyboardEvent, ReactNode, Ref } from 'react'
2
+ import { cn } from '../../utils/common'
3
+ import { Text } from '../Text'
4
+ import { Wrapper } from '../Wrapper'
5
+
6
+ export interface PillProps
7
+ extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
8
+ /** Text label rendered next to the logo (e.g., "USDC", "Base"). */
9
+ label: string
10
+ /** URL of the logo image; when omitted, a neutral placeholder disc with the
11
+ * first letter of `label` is drawn. */
12
+ logoUri?: string
13
+ /** Click handler; when supplied and not `disabled`, the pill becomes a keyboard-accessible button. */
14
+ onClick?: () => void
15
+ /** When true, renders as a dimmed, non-interactive pill. */
16
+ disabled?: boolean
17
+ /** Render a pulsing skeleton placeholder instead of the content. */
18
+ loading?: boolean
19
+ /** Optional trailing affordance (e.g., `<SelectIcon />`). Rendered in a padded slot on the right. */
20
+ trailingIcon?: ReactNode
21
+ ref?: Ref<HTMLDivElement>
22
+ }
23
+
24
+ export function Pill({
25
+ label,
26
+ logoUri,
27
+ onClick,
28
+ disabled,
29
+ loading,
30
+ trailingIcon,
31
+ className,
32
+ ref,
33
+ ...rest
34
+ }: PillProps) {
35
+ if (loading) return <PillSkeleton className={className} disabled={disabled} />
36
+
37
+ const logoInitial = label.charAt(0).toUpperCase()
38
+
39
+ const interactive = Boolean(onClick) && !disabled
40
+
41
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
42
+ if (!interactive) return
43
+ if (event.key === 'Enter' || event.key === ' ') {
44
+ event.preventDefault()
45
+ onClick?.()
46
+ }
47
+ }
48
+
49
+ return (
50
+ <Wrapper
51
+ ref={ref}
52
+ variant="solid"
53
+ // Override Wrapper's variant-based bg color for the display variant —
54
+ // Figma spec is exactly rgba(255,255,255,0.05), which none of the
55
+ // Wrapper variants match. Inline style beats Wrapper's own style.
56
+ style={
57
+ disabled ? { backgroundColor: 'rgba(255, 255, 255, 0.05)' } : undefined
58
+ }
59
+ className={cn(
60
+ // Sizing/padding matches Figma: outer pl-1 pr-2 py-1, rounded-2xl.
61
+ // Height is content-driven — 44px logo + 4px vertical padding = 52px.
62
+ 'zd:relative zd:flex zd:w-full zd:items-center zd:justify-between zd:overflow-hidden zd:rounded-2xl zd:pl-1 zd:pr-2 zd:py-1',
63
+ // Universal inner shadow from the Figma design token.
64
+ 'zd:shadow-[inset_0_-4px_4px_0_rgba(255,255,255,0.1),inset_0_3px_4px_0_rgba(0,0,0,0.02)]',
65
+ !disabled && 'zd:cursor-pointer',
66
+ className,
67
+ )}
68
+ {...rest}
69
+ {...(interactive && {
70
+ role: 'button',
71
+ tabIndex: 0,
72
+ onClick,
73
+ onKeyDown: handleKeyDown,
74
+ })}
75
+ >
76
+ <div className="zd:flex zd:items-center zd:gap-1.5">
77
+ {/* 44×44 logo well with a 34×34 centered inner disc. Figma places the
78
+ image with translate-1/2 offsets so the smaller disc sits inside
79
+ the larger well — the 10px reveal reads as the pill's "logo pad". */}
80
+ <div
81
+ className="zd:relative zd:size-11 zd:shrink-0 zd:rounded-xl"
82
+ aria-hidden={!logoUri}
83
+ >
84
+ <div
85
+ className={cn(
86
+ 'zd:absolute zd:top-1/2 zd:left-1/2 zd:-translate-x-1/2 zd:-translate-y-1/2',
87
+ 'zd:flex zd:size-8.5 zd:items-center zd:justify-center',
88
+ // Only round + clip when we're showing the letter fallback disc.
89
+ // Real logo SVGs bring their own shape (round, diamond, etc.) —
90
+ // clipping them to a circle chops off tips like Polygon's.
91
+ // The neutral greyScale/10 fallback disc reads as a design-
92
+ // system placeholder rather than a token-specific color; real
93
+ // tokens/chains ship via `logoUri`.
94
+ !logoUri &&
95
+ 'zd:overflow-hidden zd:rounded-full zd:bg-greyScale/10',
96
+ )}
97
+ >
98
+ {logoUri ? (
99
+ <img
100
+ alt=""
101
+ src={logoUri}
102
+ className="zd:size-full zd:object-contain"
103
+ data-testid="token-chain-pill-logo"
104
+ />
105
+ ) : logoInitial ? (
106
+ <span className="zd:text-body2 zd:font-medium zd:text-greyScale">
107
+ {logoInitial}
108
+ </span>
109
+ ) : null}
110
+ </div>
111
+ </div>
112
+ <Text className="zd:whitespace-nowrap zd:text-body1">{label}</Text>
113
+ </div>
114
+ {trailingIcon && (
115
+ <div
116
+ className="zd:flex zd:shrink-0 zd:items-center zd:rounded-full zd:p-2"
117
+ data-testid="pill-trailing-icon"
118
+ >
119
+ {trailingIcon}
120
+ </div>
121
+ )}
122
+ </Wrapper>
123
+ )
124
+ }
125
+
126
+ export function PillSkeleton({
127
+ className,
128
+ disabled,
129
+ }: {
130
+ className?: string | undefined
131
+ /** Match `Pill`'s dimmed display-variant background so the skeleton on a
132
+ * destination (disabled) pill doesn't look like an interactive pill. */
133
+ disabled?: boolean | undefined
134
+ }) {
135
+ return (
136
+ <Wrapper
137
+ variant="solid"
138
+ role="status"
139
+ aria-busy="true"
140
+ aria-label="Loading"
141
+ style={
142
+ disabled ? { backgroundColor: 'rgba(255, 255, 255, 0.05)' } : undefined
143
+ }
144
+ className={cn(
145
+ // Match Pill's outer chrome so the placeholder occupies the same
146
+ // footprint (52px tall = 44px logo well + 4px vertical padding).
147
+ 'zd:relative zd:flex zd:w-full zd:items-center zd:overflow-hidden zd:rounded-2xl zd:pl-1 zd:pr-2 zd:py-1',
148
+ 'zd:shadow-[inset_0_-4px_4px_0_rgba(255,255,255,0.1),inset_0_3px_4px_0_rgba(0,0,0,0.02)]',
149
+ className,
150
+ )}
151
+ >
152
+ {/* Visual only — the outer wrapper carries the a11y busy/label state,
153
+ * so hide the placeholder bars from assistive tech to avoid narrating
154
+ * empty pill contents. */}
155
+ <div
156
+ aria-hidden
157
+ className="zd:flex zd:items-center zd:gap-1.5 zd:animate-skel-pulse"
158
+ >
159
+ {/* Logo well mirrors Pill's 44×44 → inner 34×34 (size-8.5). */}
160
+ <div className="zd:relative zd:size-11 zd:shrink-0">
161
+ <div className="zd:absolute zd:top-1/2 zd:left-1/2 zd:size-8.5 zd:-translate-x-1/2 zd:-translate-y-1/2 zd:rounded-full zd:bg-greyScale/15" />
162
+ </div>
163
+ <div className="zd:h-3.5 zd:w-14 zd:rounded-md zd:bg-greyScale/15" />
164
+ </div>
165
+ </Wrapper>
166
+ )
167
+ }
@@ -0,0 +1,29 @@
1
+ import type { CSSProperties } from 'react'
2
+ import { cn } from '../../utils/common'
3
+ import { Text } from '../Text'
4
+ import { ZeroDevLogo } from '../ZeroDevLogo'
5
+
6
+ export function PoweredBy({
7
+ className,
8
+ style,
9
+ }: {
10
+ className?: string
11
+ style?: CSSProperties
12
+ }) {
13
+ return (
14
+ <div
15
+ className={cn(
16
+ 'zd:gap-1.5 zd:flex zd:flex-row zd:items-center',
17
+ className,
18
+ )}
19
+ >
20
+ <Text>Powered by</Text>
21
+ <ZeroDevLogo
22
+ variant="lockup"
23
+ tone="black"
24
+ className="zd:h-4.5 zd:w-auto"
25
+ style={style}
26
+ />
27
+ </div>
28
+ )
29
+ }
@@ -0,0 +1,65 @@
1
+ interface RadialStop {
2
+ offset: number
3
+ color: string
4
+ opacity: number
5
+ }
6
+
7
+ export interface RadialLayer {
8
+ id: string
9
+ // Figma-exported gradientTransform matrix: the unit circle
10
+ // (center 0,0 radius 1) mapped to a rotated/scaled/translated ellipse.
11
+ matrix: [number, number, number, number, number, number]
12
+ // Opacity of the rect the gradient fills (Figma exports this on the <rect>).
13
+ fillOpacity: number
14
+ stops: RadialStop[]
15
+ }
16
+
17
+ // The rect each gradient fills, in the parent SVG's viewBox coordinates.
18
+ interface RadialRect {
19
+ x: number
20
+ y: number
21
+ width: number
22
+ height: number
23
+ }
24
+
25
+ export function EllipticalRadial({
26
+ layer,
27
+ rect,
28
+ }: {
29
+ layer: RadialLayer
30
+ rect: RadialRect
31
+ }) {
32
+ const { id, matrix, fillOpacity, stops } = layer
33
+
34
+ return (
35
+ <>
36
+ <defs>
37
+ <radialGradient
38
+ id={id}
39
+ cx="0"
40
+ cy="0"
41
+ r="1"
42
+ gradientUnits="userSpaceOnUse"
43
+ gradientTransform={`matrix(${matrix.join(' ')})`}
44
+ >
45
+ {stops.map((s, i) => (
46
+ <stop
47
+ key={s.color + i.toString()}
48
+ offset={s.offset}
49
+ stopColor={s.color}
50
+ stopOpacity={s.opacity}
51
+ />
52
+ ))}
53
+ </radialGradient>
54
+ </defs>
55
+ <rect
56
+ x={rect.x}
57
+ y={rect.y}
58
+ width={rect.width}
59
+ height={rect.height}
60
+ fill={`url(#${id})`}
61
+ fillOpacity={fillOpacity}
62
+ />
63
+ </>
64
+ )
65
+ }