@horon-ai/react 0.0.0-stage → 0.1.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.
Files changed (102) hide show
  1. package/README.md +168 -2
  2. package/package.json +50 -5
  3. package/src/assets/sidebar-photo.jpg +0 -0
  4. package/src/components/agent-activity.tsx +204 -0
  5. package/src/components/agent-lane.tsx +80 -0
  6. package/src/components/agent-lanes-timeline.tsx +312 -0
  7. package/src/components/agent-panel.tsx +125 -0
  8. package/src/components/agent-steering-panel.tsx +276 -0
  9. package/src/components/approval-card.tsx +89 -0
  10. package/src/components/avatar.tsx +104 -0
  11. package/src/components/badge.tsx +63 -0
  12. package/src/components/banner.tsx +103 -0
  13. package/src/components/bot-instructions-panel.tsx +369 -0
  14. package/src/components/bot-list-row.tsx +42 -0
  15. package/src/components/bot-memory-panel.tsx +229 -0
  16. package/src/components/bottom-sheet.tsx +140 -0
  17. package/src/components/breadcrumb-header.tsx +87 -0
  18. package/src/components/breadcrumb.tsx +86 -0
  19. package/src/components/button.tsx +146 -0
  20. package/src/components/calendar.tsx +410 -0
  21. package/src/components/card.tsx +79 -0
  22. package/src/components/chat-bubble.tsx +36 -0
  23. package/src/components/chat-controls.tsx +118 -0
  24. package/src/components/checkbox.tsx +73 -0
  25. package/src/components/chip.tsx +81 -0
  26. package/src/components/choice-card.tsx +55 -0
  27. package/src/components/code-panel.tsx +27 -0
  28. package/src/components/combobox.tsx +312 -0
  29. package/src/components/command-palette.tsx +206 -0
  30. package/src/components/composer.tsx +124 -0
  31. package/src/components/data-table.tsx +429 -0
  32. package/src/components/date-field.tsx +100 -0
  33. package/src/components/dialog.tsx +93 -0
  34. package/src/components/directory-card.tsx +59 -0
  35. package/src/components/document-extract-panel.tsx +398 -0
  36. package/src/components/empty-state.tsx +29 -0
  37. package/src/components/field.tsx +78 -0
  38. package/src/components/file-drop.tsx +89 -0
  39. package/src/components/filter-bar.tsx +83 -0
  40. package/src/components/flue-background.tsx +388 -0
  41. package/src/components/horon-icon.tsx +280 -0
  42. package/src/components/horon-wordmark.tsx +26 -0
  43. package/src/components/icon-button.tsx +61 -0
  44. package/src/components/input-group.tsx +112 -0
  45. package/src/components/input.tsx +72 -0
  46. package/src/components/integration-row.tsx +103 -0
  47. package/src/components/kbd.tsx +42 -0
  48. package/src/components/knowledge-base-panel.tsx +185 -0
  49. package/src/components/liquid-glass.tsx +24 -0
  50. package/src/components/list-detail.tsx +275 -0
  51. package/src/components/marketplace-card.tsx +97 -0
  52. package/src/components/mcp-server-row.tsx +122 -0
  53. package/src/components/menu.tsx +165 -0
  54. package/src/components/message-extras.tsx +146 -0
  55. package/src/components/modal.tsx +108 -0
  56. package/src/components/orchestrator-panel.tsx +175 -0
  57. package/src/components/page-header.tsx +48 -0
  58. package/src/components/plan-card.tsx +70 -0
  59. package/src/components/plan-step.tsx +111 -0
  60. package/src/components/plugin-row.tsx +128 -0
  61. package/src/components/progress.tsx +21 -0
  62. package/src/components/radio-group.tsx +84 -0
  63. package/src/components/research-run-panel.tsx +232 -0
  64. package/src/components/schedule.tsx +332 -0
  65. package/src/components/segmented-control.tsx +136 -0
  66. package/src/components/select.tsx +154 -0
  67. package/src/components/settings-list.tsx +99 -0
  68. package/src/components/sidebar.tsx +542 -0
  69. package/src/components/skeleton.tsx +75 -0
  70. package/src/components/slider.tsx +29 -0
  71. package/src/components/source-row.tsx +112 -0
  72. package/src/components/spinner.tsx +29 -0
  73. package/src/components/split-button.tsx +72 -0
  74. package/src/components/switch.tsx +79 -0
  75. package/src/components/tab-bar.tsx +90 -0
  76. package/src/components/tabs.tsx +162 -0
  77. package/src/components/task-row.tsx +159 -0
  78. package/src/components/textarea-field.tsx +127 -0
  79. package/src/components/textarea.tsx +47 -0
  80. package/src/components/toast.tsx +128 -0
  81. package/src/components/tool-call.tsx +215 -0
  82. package/src/components/tool-calls-header.tsx +65 -0
  83. package/src/components/tool-permissions.tsx +105 -0
  84. package/src/components/tooltip.tsx +33 -0
  85. package/src/components/top-bar.tsx +65 -0
  86. package/src/components/usage-meter.tsx +47 -0
  87. package/src/components/workspace-menu.tsx +77 -0
  88. package/src/hooks/index.ts +8 -0
  89. package/src/hooks/use-active-indicator.ts +60 -0
  90. package/src/hooks/use-color-scheme.ts +27 -0
  91. package/src/hooks/use-copy-to-clipboard.ts +32 -0
  92. package/src/hooks/use-device.ts +41 -0
  93. package/src/hooks/use-hotkey.ts +35 -0
  94. package/src/hooks/use-media-query.ts +25 -0
  95. package/src/hooks/use-platform.ts +39 -0
  96. package/src/hooks/use-reduced-motion.ts +8 -0
  97. package/src/index.ts +99 -0
  98. package/src/lib/cn.ts +25 -0
  99. package/src/lib/cursor-ghost.ts +244 -0
  100. package/src/lib/platform.ts +169 -0
  101. package/src/lib/size.ts +10 -0
  102. package/src/styles/globals.css +564 -0
@@ -0,0 +1,312 @@
1
+ import { CaretDown, Pulse, Warning } from "@phosphor-icons/react";
2
+ import { type ComponentProps, type CSSProperties, type ReactNode } from "react";
3
+
4
+ import { cn } from "../lib/cn";
5
+ import { Spinner } from "./spinner";
6
+
7
+ export type LaneTaskTone = "done" | "running" | "failed" | "waiting";
8
+
9
+ export type LaneTask = {
10
+ id: string;
11
+ label: ReactNode;
12
+ icon?: ReactNode;
13
+ tone: LaneTaskTone;
14
+ /** Minutes before the end of the window, 0 is now: a task from 28 minutes ago to 16 minutes ago is `-28` to `-16`. */
15
+ start: number;
16
+ end: number;
17
+ /** A pill inside the bar, such as "Needs you". */
18
+ badge?: ReactNode;
19
+ };
20
+
21
+ export type LaneBotTone = "sky" | "steel" | "neutral" | "ink";
22
+
23
+ export type LaneBot = {
24
+ id: string;
25
+ name: ReactNode;
26
+ tools?: ReactNode;
27
+ initial: string;
28
+ tone: LaneBotTone;
29
+ /** A small tag next to the name: "Idle", "Needs you". */
30
+ badge?: ReactNode;
31
+ badgeTone?: "subtle" | "ink";
32
+ tasks: LaneTask[];
33
+ };
34
+
35
+ export type LaneHandoff = { from: string; to: string };
36
+
37
+ export type LaneStatus = { id: string; tone: "live" | "ink"; label: ReactNode };
38
+
39
+ export type AgentLanesTimelineApproval = {
40
+ message: ReactNode;
41
+ onLater?: () => void;
42
+ onApprove?: () => void;
43
+ };
44
+
45
+ export type AgentLanesTimelineProps = Omit<ComponentProps<"section">, "className" | "title"> & {
46
+ title?: ReactNode;
47
+ statuses?: LaneStatus[];
48
+ /** The range pill at the header's right edge. */
49
+ range?: ReactNode;
50
+ bots: LaneBot[];
51
+ handoffs?: LaneHandoff[];
52
+ /** Window length in minutes; the tick labels sit every five. */
53
+ windowMinutes?: number;
54
+ /** Where the current line sits, in minutes before the end of the window. The line glides to a new value in 280ms. */
55
+ cursor?: number;
56
+ approval?: AgentLanesTimelineApproval;
57
+ /** Makes the range pill actionable; without it the range renders as a plain label. */
58
+ onRangeClick?: () => void;
59
+ className?: string;
60
+ };
61
+
62
+ const avatarTone: Record<LaneBotTone, string> = {
63
+ sky: "bg-sky-500",
64
+ steel: "bg-sky-700",
65
+ neutral: "bg-neutral-500",
66
+ ink: "bg-interactive-primary text-text-inverse",
67
+ };
68
+
69
+ const barTone: Record<LaneTaskTone, string> = {
70
+ done: "bg-overlay-ink-6 text-text-secondary",
71
+ running: "bg-status-live/20 text-text-primary",
72
+ failed: "bg-red-500/14 text-text-danger",
73
+ waiting: "border border-interactive-primary bg-bg-surface-90 text-text-primary",
74
+ };
75
+
76
+ const noStatuses: LaneStatus[] = [];
77
+ const noHandoffs: LaneHandoff[] = [];
78
+ type RowStyle = CSSProperties & { "--i": number };
79
+ const rowStyle = (lane: number): RowStyle => ({ top: TOP + lane * ROW, height: ROW, "--i": lane });
80
+
81
+ const ROW = 64;
82
+ const TOP = 40;
83
+ const LABEL_WIDTH = 230;
84
+ const RIGHT_GUTTER = 18;
85
+
86
+ const TRACK = `(100% - ${LABEL_WIDTH + RIGHT_GUTTER}px)`;
87
+ const trackX = (fraction: number) => `calc(${LABEL_WIDTH}px + ${TRACK} * ${fraction})`;
88
+ const trackW = (fraction: number) => `calc(${TRACK} * ${fraction})`;
89
+
90
+ /**
91
+ * Paper `Agent lanes` (YCO-1): a 24px-radius surface panel with one lane per bot on a shared time axis, a current
92
+ * line, hand-off curves between bots and an approval strip. The current line glides in 280ms with the emphasized
93
+ * easing, rows tint on hover in 150ms, and running bars carry a spinner.
94
+ */
95
+ export function AgentLanesTimeline({
96
+ title = "Bots",
97
+ statuses = noStatuses,
98
+ range = "Last 30m",
99
+ bots,
100
+ handoffs = noHandoffs,
101
+ windowMinutes = 30,
102
+ cursor = 0,
103
+ approval,
104
+ onRangeClick,
105
+ className,
106
+ ...props
107
+ }: AgentLanesTimelineProps) {
108
+ const ticks = Array.from({ length: windowMinutes / 5 - 1 }, (_, i) => -(windowMinutes - 5 * (i + 1)));
109
+ const taskAt = new Map<string, { task: LaneTask; lane: number }>();
110
+ bots.forEach((bot, lane) => {
111
+ for (const task of bot.tasks) taskAt.set(task.id, { task, lane });
112
+ });
113
+ const at = (minutes: number) => (windowMinutes + minutes) / windowMinutes;
114
+
115
+ return (
116
+ <section
117
+ className={cn(
118
+ "flex w-full max-w-252.5 min-w-0 flex-col overflow-clip rounded-[24px] border border-border-default bg-bg-surface",
119
+ className,
120
+ )}
121
+ {...props}
122
+ >
123
+ <div className="flex flex-wrap items-center gap-x-2.5 gap-y-2 px-5 pt-4 pb-3.5">
124
+ <Pulse aria-hidden className="size-4.5 shrink-0 text-text-primary" />
125
+ <h3 className="min-w-0 text-label-lg text-text-primary">{title}</h3>
126
+ {statuses.map((status) => (
127
+ <span key={status.id} className="flex items-center gap-1.5 text-body-sm text-text-secondary">
128
+ <span
129
+ aria-hidden
130
+ className={cn(
131
+ "size-1.75 rounded-full",
132
+ status.tone === "live" ? "bg-status-live" : "bg-interactive-primary",
133
+ )}
134
+ />
135
+ {status.label}
136
+ </span>
137
+ ))}
138
+ <span className="flex-1" />
139
+ {onRangeClick ? (
140
+ <button
141
+ type="button"
142
+ onClick={onRangeClick}
143
+ className="flex h-7.5 motion-press cursor-pointer items-center gap-1 rounded-full bg-overlay-ink-6 px-3 text-label-sm text-text-primary hover:bg-overlay-ink-10 focus-visible:outline-2 focus-visible:outline-text-brand"
144
+ >
145
+ {range}
146
+ <CaretDown aria-hidden className="size-3 text-text-secondary" />
147
+ </button>
148
+ ) : (
149
+ <span className="flex h-7.5 items-center gap-1 rounded-full bg-overlay-ink-6 px-3 text-label-sm text-text-primary">
150
+ {range}
151
+ <CaretDown aria-hidden className="size-3 text-text-secondary" />
152
+ </span>
153
+ )}
154
+ </div>
155
+ <div className="h-px bg-overlay-ink-8" />
156
+
157
+ <div className="overflow-x-auto">
158
+ <div className="relative min-w-175" style={{ height: TOP + Math.max(2, bots.length) * ROW }}>
159
+ {ticks.map((minutes) => (
160
+ <span key={minutes}>
161
+ <span
162
+ aria-hidden
163
+ className="absolute top-9 bottom-0 w-px bg-overlay-ink-5"
164
+ style={{ left: trackX(at(minutes)) }}
165
+ />
166
+ <span
167
+ className="absolute top-2.5 -translate-x-1/2 text-caption whitespace-nowrap text-text-secondary"
168
+ style={{ left: trackX(at(minutes)) }}
169
+ >
170
+ {minutes}m
171
+ </span>
172
+ </span>
173
+ ))}
174
+
175
+ {bots.map((bot, lane) => (
176
+ <div
177
+ key={bot.id}
178
+ style={rowStyle(lane)}
179
+ className={cn(
180
+ "absolute inset-x-0 flex motion-rise items-center gap-3 pl-5 motion-colors hover:bg-overlay-ink-3",
181
+ lane < bots.length - 1 && "border-b border-overlay-ink-6",
182
+ )}
183
+ >
184
+ <span
185
+ className={cn(
186
+ "flex size-8.5 shrink-0 items-center justify-center rounded-[10px] text-label-md text-text-on-ink",
187
+ avatarTone[bot.tone],
188
+ )}
189
+ >
190
+ {bot.initial}
191
+ </span>
192
+ <span className="flex flex-col">
193
+ <span className="flex items-center gap-1.5 text-label-md text-text-primary">
194
+ {bot.name}
195
+ {bot.badge && (
196
+ <span
197
+ className={cn(
198
+ "rounded-md px-1.5 py-px text-label-sm",
199
+ bot.badgeTone === "ink"
200
+ ? "bg-interactive-primary text-text-inverse"
201
+ : "bg-overlay-ink-6 text-text-secondary",
202
+ )}
203
+ >
204
+ {bot.badge}
205
+ </span>
206
+ )}
207
+ </span>
208
+ {bot.tools && <span className="text-caption text-text-secondary">{bot.tools}</span>}
209
+ </span>
210
+ </div>
211
+ ))}
212
+
213
+ {handoffs.map(({ from, to }) => {
214
+ const a = taskAt.get(from);
215
+ const b = taskAt.get(to);
216
+ if (!a || !b) return null;
217
+ const x1 = at(a.task.end);
218
+ const x2 = at(b.task.start);
219
+ const y1 = TOP + a.lane * ROW + 32;
220
+ const y2 = TOP + b.lane * ROW + 32;
221
+ return (
222
+ <span
223
+ key={`${from}-${to}`}
224
+ aria-hidden
225
+ className="absolute"
226
+ style={{
227
+ left: trackX(x1),
228
+ width: trackW(x2 - x1),
229
+ top: y1,
230
+ height: y2 - y1,
231
+ }}
232
+ >
233
+ <svg viewBox="0 0 100 100" preserveAspectRatio="none" className="size-full overflow-visible">
234
+ <path
235
+ d="M0 0 C60 0 40 100 100 100"
236
+ fill="none"
237
+ strokeDasharray="3 3"
238
+ vectorEffect="non-scaling-stroke"
239
+ className="stroke-status-live"
240
+ strokeWidth="1.5"
241
+ />
242
+ </svg>
243
+ </span>
244
+ );
245
+ })}
246
+
247
+ {bots.flatMap((bot, lane) =>
248
+ bot.tasks.map((task) => (
249
+ <div
250
+ key={task.id}
251
+ className={cn(
252
+ "absolute flex h-9 motion-rise items-center gap-1.5 overflow-clip rounded-[10px] px-2.5 text-label-sm transition-[left,width,background-color,color,border-color] duration-280 ease-emphasized [&>svg]:size-3.5",
253
+ barTone[task.tone],
254
+ )}
255
+ style={{
256
+ top: TOP + lane * ROW + 14,
257
+ left: trackX(at(task.start)),
258
+ width: trackW((task.end - task.start) / windowMinutes),
259
+ }}
260
+ >
261
+ {task.icon}
262
+ <span className="min-w-0 flex-1 truncate">{task.label}</span>
263
+ {task.badge && (
264
+ <span className="flex items-center gap-1 rounded-full bg-interactive-primary py-0.5 pr-2 pl-1.5 text-label-sm text-text-inverse [&>svg]:size-3">
265
+ {task.badge}
266
+ </span>
267
+ )}
268
+ {task.tone === "running" && <Spinner size={12} label="Running" />}
269
+ </div>
270
+ )),
271
+ )}
272
+
273
+ <span
274
+ className="absolute top-2.5 -translate-x-full transition-[left] duration-280 ease-emphasized"
275
+ style={{ left: trackX(at(-cursor)) }}
276
+ >
277
+ <span className="rounded-md bg-status-live/20 px-1.5 py-px text-caption text-text-secondary">now</span>
278
+ </span>
279
+ <span
280
+ aria-hidden
281
+ className="absolute top-9 bottom-0 w-[1.5px] bg-status-live/60 transition-[left] duration-280 ease-emphasized"
282
+ style={{ left: trackX(at(-cursor)) }}
283
+ />
284
+ </div>
285
+ </div>
286
+
287
+ {approval && (
288
+ <>
289
+ <div className="h-px bg-overlay-ink-8" />
290
+ <div className="flex flex-wrap items-center gap-3 px-5 py-3">
291
+ <Warning aria-hidden className="size-3.5 shrink-0 text-text-primary" />
292
+ <p className="min-w-0 flex-1 text-body-sm text-text-primary">{approval.message}</p>
293
+ <button
294
+ type="button"
295
+ onClick={approval.onLater}
296
+ className="flex h-7.5 shrink-0 motion-press cursor-pointer items-center rounded-full bg-overlay-ink-6 px-3 text-label-sm text-text-primary hover:bg-overlay-ink-10 focus-visible:outline-2 focus-visible:outline-text-brand"
297
+ >
298
+ Later
299
+ </button>
300
+ <button
301
+ type="button"
302
+ onClick={approval.onApprove}
303
+ className="flex h-7.5 shrink-0 motion-press cursor-pointer items-center rounded-full bg-interactive-primary px-3 text-label-sm text-text-inverse focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-text-brand"
304
+ >
305
+ Approve
306
+ </button>
307
+ </div>
308
+ </>
309
+ )}
310
+ </section>
311
+ );
312
+ }
@@ -0,0 +1,125 @@
1
+ import { CaretDown } from "@phosphor-icons/react";
2
+ import type { ComponentProps, ReactNode } from "react";
3
+
4
+ import { useDevice } from "../hooks/use-device";
5
+ import { usePlatform } from "../hooks/use-platform";
6
+ import { cn } from "../lib/cn";
7
+ import { isModKey } from "../lib/platform";
8
+ import { Kbd } from "./kbd";
9
+
10
+ type WithClass<T> = Omit<T, "className"> & { className?: string };
11
+
12
+ /** The solid panel that hosts the Figma Agents blocks (Agent tasks 112:797): surface fill, 24px radius, 16px padding. */
13
+ export function AgentPanel({
14
+ className,
15
+ filter,
16
+ children,
17
+ ...props
18
+ }: WithClass<ComponentProps<"section">> & {
19
+ /** Filter slot under the panel top, such as a `FilterBar`. */ filter?: ReactNode;
20
+ }) {
21
+ return (
22
+ <section
23
+ className={cn(
24
+ "flex min-w-0 flex-col items-stretch gap-3 rounded-[24px] border border-border-default bg-bg-surface px-5 pt-4 pb-2",
25
+ className,
26
+ )}
27
+ {...props}
28
+ >
29
+ {filter && <div className="flex">{filter}</div>}
30
+ {children}
31
+ </section>
32
+ );
33
+ }
34
+
35
+ export type ComposerChipProps = WithClass<ComponentProps<"button">> & {
36
+ /** 16px tile initial (bots) or a 14px icon (projects). */
37
+ leading?: ReactNode;
38
+ tileClassName?: string;
39
+ };
40
+
41
+ /** A 30px 5% ink picker chip with a 12px caret, used for the bot and project in the task composer. */
42
+ export function ComposerChip({ leading, tileClassName, className, children, ...props }: ComposerChipProps) {
43
+ return (
44
+ <button
45
+ type="button"
46
+ className={cn(
47
+ "flex h-7.5 shrink-0 motion-press cursor-pointer items-center gap-1.5 rounded-full bg-overlay-ink-5 pr-2.5 pl-2 text-label-sm text-text-primary focus-visible:outline-2 focus-visible:outline-text-brand",
48
+ className,
49
+ )}
50
+ {...props}
51
+ >
52
+ {typeof leading === "string" ? (
53
+ <span
54
+ className={cn(
55
+ "flex size-4 items-center justify-center rounded-[5px] text-label-sm text-text-inverse",
56
+ tileClassName ?? "bg-interactive-primary",
57
+ )}
58
+ >
59
+ {leading}
60
+ </span>
61
+ ) : (
62
+ <span className="flex text-text-secondary [&>svg]:size-3.5">{leading}</span>
63
+ )}
64
+ {children}
65
+ <CaretDown aria-hidden className="size-3 text-text-secondary" />
66
+ </button>
67
+ );
68
+ }
69
+
70
+ export type TaskComposerProps = {
71
+ placeholder?: string;
72
+ value?: string;
73
+ onValueChange?: (value: string) => void;
74
+ /** Picker chips, usually two `ComposerChip`s. */
75
+ pickers?: ReactNode;
76
+ onStart?: () => void;
77
+ startLabel?: string;
78
+ className?: string;
79
+ };
80
+
81
+ /** Figma task `Composer` (Agent tasks 112:797): a 4% ink box, 18px radius, with pickers, a Mod+Enter hint and Start. */
82
+ export function TaskComposer({
83
+ placeholder = "Describe a task for a bot",
84
+ value,
85
+ onValueChange,
86
+ pickers,
87
+ onStart,
88
+ startLabel = "Start",
89
+ className,
90
+ }: TaskComposerProps) {
91
+ const { os } = usePlatform();
92
+ const { keyboardHints } = useDevice();
93
+ return (
94
+ <div
95
+ className={cn(
96
+ "flex flex-col gap-3.5 rounded-xl bg-overlay-ink-4 px-4 pt-3.5 pb-2.5 focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-text-brand",
97
+ className,
98
+ )}
99
+ >
100
+ <textarea
101
+ rows={1}
102
+ value={value}
103
+ onChange={(event) => onValueChange?.(event.target.value)}
104
+ onKeyDown={(event) => {
105
+ if (event.key === "Enter" && isModKey(event, os)) onStart?.();
106
+ }}
107
+ placeholder={placeholder}
108
+ aria-label={placeholder}
109
+ className="field-sizing-content max-h-40 min-h-5.5 w-full resize-none overflow-y-auto bg-transparent text-body-sm text-text-primary outline-none placeholder:text-text-secondary"
110
+ />
111
+ <div className="flex flex-wrap items-center gap-1.5">
112
+ {pickers && <span className="flex min-w-0 flex-wrap items-center gap-1.5">{pickers}</span>}
113
+ <span className="min-w-0 flex-1" />
114
+ {keyboardHints && <Kbd keys="Mod+Enter" />}
115
+ <button
116
+ type="button"
117
+ onClick={onStart}
118
+ className="flex h-7.5 shrink-0 motion-press cursor-pointer items-center rounded-full bg-interactive-primary px-3.5 text-label-sm text-text-inverse focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-text-brand"
119
+ >
120
+ {startLabel}
121
+ </button>
122
+ </div>
123
+ </div>
124
+ );
125
+ }