@vvedantb/ui 0.17.1 → 0.18.0

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 (129) hide show
  1. package/README.md +251 -3
  2. package/package.json +13 -5
  3. package/shell/src/SidebarResize.tsx +1 -4
  4. package/src/ai-elements/__snapshots__/queue.test.tsx.snap +11 -0
  5. package/src/ai-elements/activity-question-utils.ts +26 -0
  6. package/src/ai-elements/activity-question.tsx +159 -0
  7. package/src/ai-elements/activity-shared.tsx +459 -0
  8. package/src/ai-elements/activity-step-detail.tsx +115 -0
  9. package/src/ai-elements/activity-step-label.ts +981 -0
  10. package/src/ai-elements/activity-tasks-utils.ts +199 -0
  11. package/src/ai-elements/activity-tasks.tsx +770 -0
  12. package/src/ai-elements/artifact.tsx +162 -52
  13. package/src/ai-elements/attachments.tsx +62 -32
  14. package/src/ai-elements/code-block.tsx +76 -22
  15. package/src/ai-elements/context.tsx +370 -0
  16. package/src/ai-elements/conversation.tsx +186 -62
  17. package/src/ai-elements/file-chip.tsx +53 -0
  18. package/src/ai-elements/message.tsx +161 -50
  19. package/src/ai-elements/model-picker-content.tsx +505 -0
  20. package/src/ai-elements/model-picker-types.ts +56 -0
  21. package/src/ai-elements/model-picker.tsx +301 -0
  22. package/src/ai-elements/model-selector.tsx +102 -21
  23. package/src/ai-elements/plan.tsx +130 -0
  24. package/src/ai-elements/prompt-input-settings.tsx +144 -0
  25. package/src/ai-elements/prompt-input-speech.tsx +189 -0
  26. package/src/ai-elements/prompt-input-traits.tsx +281 -0
  27. package/src/ai-elements/prompt-input.tsx +320 -134
  28. package/src/ai-elements/provider-icon.tsx +133 -0
  29. package/src/ai-elements/queue.tsx +293 -9
  30. package/src/ai-elements/shimmer.tsx +72 -1
  31. package/src/ai-elements/simple-model-ladder.tsx +108 -0
  32. package/src/ai-elements/test-results.tsx +510 -0
  33. package/src/ai-elements/trace.tsx +243 -0
  34. package/src/ai-elements/use-streaming-disclosure.ts +50 -0
  35. package/src/ai-elements/web-preview.tsx +349 -0
  36. package/src/composer/input-area/ComposerSlashMenu.tsx +1 -1
  37. package/src/index.ts +66 -12
  38. package/src/kibo/avatar-stack.tsx +40 -0
  39. package/src/kibo/contribution-graph.tsx +539 -0
  40. package/src/kibo/cursor.tsx +72 -0
  41. package/src/kibo/gantt-features.tsx +439 -0
  42. package/src/kibo/gantt-header.tsx +181 -0
  43. package/src/kibo/gantt-provider.tsx +657 -0
  44. package/src/kibo/gantt-sidebar.tsx +157 -0
  45. package/src/kibo/gantt-timeline.tsx +154 -0
  46. package/src/kibo/index.ts +151 -0
  47. package/src/kibo/kanban.tsx +338 -0
  48. package/src/kibo/kanbanDropTarget.ts +25 -0
  49. package/src/kibo/list.tsx +162 -0
  50. package/src/kibo/theme-switcher.tsx +109 -0
  51. package/src/kibo/video-player.tsx +237 -0
  52. package/src/markdown/Markdown.tsx +112 -0
  53. package/src/markdown/MarkdownBlockquote.tsx +45 -0
  54. package/src/markdown/MarkdownCodeBlock.tsx +197 -0
  55. package/src/markdown/MarkdownInlineCode.tsx +40 -0
  56. package/src/markdown/MarkdownLink.tsx +92 -0
  57. package/src/markdown/codeFence.ts +57 -0
  58. package/src/markdown/codeHighlight.ts +138 -0
  59. package/src/markdown/incrementalBlocks.ts +182 -0
  60. package/src/markdown/index.ts +3 -0
  61. package/src/markdown/markdownClipboard.ts +178 -0
  62. package/src/markdown/mermaid.ts +155 -0
  63. package/src/markdown/rehypeGithubAlerts.ts +80 -0
  64. package/src/motion/presets.ts +24 -0
  65. package/src/ui/__snapshots__/alt-wrappers.test.tsx.snap +19 -0
  66. package/src/ui/__snapshots__/sonner-dom.test.tsx.snap +5 -0
  67. package/src/ui/_menu-parts.tsx +29 -0
  68. package/src/ui/accordion.alt.tsx +46 -53
  69. package/src/ui/accordion.tsx +59 -10
  70. package/src/ui/avatar.tsx +56 -0
  71. package/src/ui/badge.tsx +45 -21
  72. package/src/ui/border-beam.tsx +97 -0
  73. package/src/ui/button-group.tsx +50 -21
  74. package/src/ui/button.tsx +67 -45
  75. package/src/ui/calendar.tsx +48 -5
  76. package/src/ui/card.tsx +77 -8
  77. package/src/ui/carousel.tsx +60 -0
  78. package/src/ui/centered-spinner.tsx +30 -0
  79. package/src/ui/checkbox.tsx +34 -16
  80. package/src/ui/clear-input.alt.tsx +33 -75
  81. package/src/ui/clear-input.tsx +140 -15
  82. package/src/ui/collapsible.tsx +40 -2
  83. package/src/ui/command.tsx +143 -70
  84. package/src/ui/context-menu.tsx +159 -93
  85. package/src/ui/crossfade-icon.tsx +123 -0
  86. package/src/ui/dialog.tsx +154 -70
  87. package/src/ui/dropdown-menu.tsx +177 -117
  88. package/src/ui/hover-card.tsx +59 -17
  89. package/src/ui/input-group.alt.tsx +83 -83
  90. package/src/ui/input-group.tsx +129 -58
  91. package/src/ui/input.tsx +13 -4
  92. package/src/ui/label.tsx +11 -8
  93. package/src/ui/list-row.tsx +232 -0
  94. package/src/ui/loading-state.tsx +215 -0
  95. package/src/ui/popover.tsx +56 -23
  96. package/src/ui/progress.tsx +28 -16
  97. package/src/ui/refresh-spin-icon.tsx +41 -0
  98. package/src/ui/search-input.tsx +66 -15
  99. package/src/ui/select.tsx +171 -100
  100. package/src/ui/separator.tsx +23 -14
  101. package/src/ui/slider.tsx +21 -7
  102. package/src/ui/sonner.tsx +68 -30
  103. package/src/ui/spinner.tsx +229 -11
  104. package/src/ui/status-dot.tsx +84 -0
  105. package/src/ui/surface.tsx +49 -0
  106. package/src/ui/switch.tsx +29 -15
  107. package/src/ui/table.tsx +38 -16
  108. package/src/ui/tabs.tsx +123 -35
  109. package/src/ui/tabsSliding.ts +33 -1
  110. package/src/ui/textarea.tsx +13 -4
  111. package/src/ui/tooltip.tsx +50 -14
  112. package/src/ui/ui-config.tsx +149 -0
  113. package/src/utils/cat-color.ts +123 -0
  114. package/src/utils/dayjs.ts +6 -0
  115. package/src/utils/gesture.ts +59 -0
  116. package/src/utils/runtimeVisibility.ts +167 -0
  117. package/src/utils/sharedClock.ts +96 -0
  118. package/src/utils/surface-radius.ts +16 -0
  119. package/src/utils/useDragSensors.ts +58 -0
  120. package/tokens/examples/tokens-moved/Fixture.tsx +283 -0
  121. package/tokens/examples/tokens-moved/README.md +20 -0
  122. package/tokens/examples/tokens-moved/index.html +12 -0
  123. package/tokens/examples/tokens-moved/main.tsx +6 -0
  124. package/tokens/examples/tokens-moved/package.json +14 -0
  125. package/tokens/examples/tokens-moved/styles.css +12 -0
  126. package/tokens/examples/tokens-moved/tsconfig.json +14 -0
  127. package/tokens/examples/tokens-moved/vite.config.ts +11 -0
  128. package/tokens/src/css/border-beam.css +116 -0
  129. package/tokens/src/css/markdown.css +318 -0
@@ -0,0 +1,539 @@
1
+ "use client";
2
+
3
+ import dayjs from "../utils/dayjs";
4
+ import {
5
+ type CSSProperties,
6
+ createContext,
7
+ Fragment,
8
+ type HTMLAttributes,
9
+ type ReactNode,
10
+ useContext,
11
+ useMemo,
12
+ } from "react";
13
+ import { useCn } from "../ui/ui-config";
14
+
15
+ export type ContributionGraphActivity = {
16
+ date: string;
17
+ count: number;
18
+ level: number;
19
+ };
20
+
21
+ type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6;
22
+
23
+ type Week = Array<ContributionGraphActivity | undefined>;
24
+
25
+ export type ContributionGraphLabels = {
26
+ months?: string[];
27
+ weekdays?: string[];
28
+ totalCount?: string;
29
+ legend?: {
30
+ less?: string;
31
+ more?: string;
32
+ };
33
+ };
34
+
35
+ type MonthLabel = {
36
+ weekIndex: number;
37
+ label: string;
38
+ };
39
+
40
+ const DEFAULT_MONTH_LABELS = [
41
+ "Jan",
42
+ "Feb",
43
+ "Mar",
44
+ "Apr",
45
+ "May",
46
+ "Jun",
47
+ "Jul",
48
+ "Aug",
49
+ "Sep",
50
+ "Oct",
51
+ "Nov",
52
+ "Dec",
53
+ ];
54
+
55
+ const DEFAULT_LABELS: ContributionGraphLabels = {
56
+ months: DEFAULT_MONTH_LABELS,
57
+ weekdays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
58
+ totalCount: "{{count}} activities in {{year}}",
59
+ legend: {
60
+ less: "Less",
61
+ more: "More",
62
+ },
63
+ };
64
+
65
+ type ContributionGraphContextType = {
66
+ data: ContributionGraphActivity[];
67
+ weeks: Week[];
68
+ blockMargin: number;
69
+ blockRadius: number;
70
+ blockSize: number;
71
+ fontSize: number;
72
+ labels: ContributionGraphLabels;
73
+ labelHeight: number;
74
+ maxLevel: number;
75
+ totalCount: number;
76
+ weekStart: WeekDay;
77
+ year: number;
78
+ width: number;
79
+ height: number;
80
+ };
81
+
82
+ const ContributionGraphContext =
83
+ createContext<ContributionGraphContextType | null>(null);
84
+
85
+ const useContributionGraph = () => {
86
+ const context = useContext(ContributionGraphContext);
87
+
88
+ if (!context) {
89
+ throw new Error(
90
+ "ContributionGraph components must be used within a ContributionGraph",
91
+ );
92
+ }
93
+
94
+ return context;
95
+ };
96
+
97
+ type Dayjs = ReturnType<typeof dayjs>;
98
+
99
+ const eachDayOfInterval = (start: Dayjs, end: Dayjs): Dayjs[] => {
100
+ const days: Dayjs[] = [];
101
+ let current = start.startOf("day");
102
+ const endDay = end.startOf("day");
103
+ while (current.isBefore(endDay) || current.isSame(endDay, "day")) {
104
+ days.push(current);
105
+ current = current.add(1, "day");
106
+ }
107
+ return days;
108
+ };
109
+
110
+ const fillHoles = (
111
+ activities: ContributionGraphActivity[],
112
+ ): ContributionGraphActivity[] => {
113
+ if (activities.length === 0) {
114
+ return [];
115
+ }
116
+
117
+ const sortedActivities = [...activities].sort((a, b) =>
118
+ a.date.localeCompare(b.date),
119
+ );
120
+
121
+ const calendar = new Map<string, ContributionGraphActivity>(
122
+ activities.map((a) => [a.date, a]),
123
+ );
124
+
125
+ const firstActivity = sortedActivities[0];
126
+ const lastActivity = sortedActivities.at(-1);
127
+
128
+ if (!firstActivity || !lastActivity) {
129
+ return [];
130
+ }
131
+
132
+ return eachDayOfInterval(
133
+ dayjs(firstActivity.date),
134
+ dayjs(lastActivity.date),
135
+ ).map((day) => {
136
+ const date = day.format("YYYY-MM-DD");
137
+
138
+ const existing = calendar.get(date);
139
+ if (existing) {
140
+ return existing;
141
+ }
142
+
143
+ return {
144
+ date,
145
+ count: 0,
146
+ level: 0,
147
+ };
148
+ });
149
+ };
150
+
151
+ const nextDayOfWeek = (date: Dayjs, targetDay: WeekDay): Dayjs => {
152
+ const currentDay = date.day();
153
+ const daysUntil = (targetDay - currentDay + 7) % 7 || 7;
154
+ return date.add(daysUntil, "day");
155
+ };
156
+
157
+ const groupByWeeks = (
158
+ activities: ContributionGraphActivity[],
159
+ weekStart: WeekDay = 0,
160
+ ): Week[] => {
161
+ if (activities.length === 0) {
162
+ return [];
163
+ }
164
+
165
+ const normalizedActivities = fillHoles(activities);
166
+ const firstActivity = normalizedActivities[0];
167
+ if (!firstActivity) {
168
+ return [];
169
+ }
170
+
171
+ const firstDate = dayjs(firstActivity.date);
172
+ const firstCalendarDate =
173
+ firstDate.day() === weekStart
174
+ ? firstDate
175
+ : nextDayOfWeek(firstDate, weekStart).subtract(1, "week");
176
+
177
+ const paddingDays = firstDate.diff(firstCalendarDate, "day");
178
+ const paddedActivities: Array<ContributionGraphActivity | undefined> = [
179
+ ...Array.from<undefined>({ length: paddingDays }),
180
+ ...normalizedActivities,
181
+ ];
182
+
183
+ const numberOfWeeks = Math.ceil(paddedActivities.length / 7);
184
+
185
+ return Array.from({ length: numberOfWeeks }, (_, weekIndex) =>
186
+ paddedActivities.slice(weekIndex * 7, weekIndex * 7 + 7),
187
+ );
188
+ };
189
+
190
+ const getMonthLabels = (
191
+ weeks: Week[],
192
+ monthNames: string[] = DEFAULT_MONTH_LABELS,
193
+ ): MonthLabel[] => {
194
+ return weeks
195
+ .reduce<MonthLabel[]>((labels, week, weekIndex) => {
196
+ const firstActivity = week.find((activity) => activity !== undefined);
197
+
198
+ if (!firstActivity) {
199
+ throw new Error(
200
+ `Unexpected error: Week ${weekIndex + 1} is empty: [${week}].`,
201
+ );
202
+ }
203
+
204
+ const month = monthNames[dayjs(firstActivity.date).month()];
205
+
206
+ if (!month) {
207
+ const monthName = dayjs(firstActivity.date).format("MMM");
208
+ throw new Error(
209
+ `Unexpected error: undefined month label for ${monthName}.`,
210
+ );
211
+ }
212
+
213
+ const prevLabel = labels.at(-1);
214
+
215
+ if (weekIndex === 0 || !prevLabel || prevLabel.label !== month) {
216
+ return labels.concat({ weekIndex, label: month });
217
+ }
218
+
219
+ return labels;
220
+ }, [])
221
+ .filter(({ weekIndex }, index, labels) => {
222
+ const minWeeks = 3;
223
+
224
+ if (index === 0) {
225
+ return labels[1] && labels[1].weekIndex - weekIndex >= minWeeks;
226
+ }
227
+
228
+ if (index === labels.length - 1) {
229
+ return weeks.slice(weekIndex).length >= minWeeks;
230
+ }
231
+
232
+ return true;
233
+ });
234
+ };
235
+
236
+ export type ContributionGraphProps = HTMLAttributes<HTMLDivElement> & {
237
+ data: ContributionGraphActivity[];
238
+ blockMargin?: number;
239
+ blockRadius?: number;
240
+ blockSize?: number;
241
+ fontSize?: number;
242
+ labels?: ContributionGraphLabels;
243
+ maxLevel?: number;
244
+ style?: CSSProperties;
245
+ totalCount?: number;
246
+ weekStart?: WeekDay;
247
+ children: ReactNode;
248
+ className?: string;
249
+ };
250
+
251
+ export const ContributionGraph = ({
252
+ data,
253
+ blockMargin = 4,
254
+ blockRadius = 2,
255
+ blockSize = 12,
256
+ fontSize = 14,
257
+ labels: labelsProp = undefined,
258
+ maxLevel: maxLevelProp = 4,
259
+ style = {},
260
+ totalCount: totalCountProp = undefined,
261
+ weekStart = 0,
262
+ className,
263
+ children,
264
+ ...props
265
+ }: ContributionGraphProps) => {
266
+ const merge = useCn();
267
+ const maxLevel = Math.max(1, maxLevelProp);
268
+ const weeks = useMemo(() => groupByWeeks(data, weekStart), [data, weekStart]);
269
+ const LABEL_MARGIN = 8;
270
+
271
+ const labels = { ...DEFAULT_LABELS, ...labelsProp };
272
+ const labelHeight = fontSize + LABEL_MARGIN;
273
+
274
+ const firstActivity = data[0];
275
+ const year = firstActivity
276
+ ? dayjs(firstActivity.date).year()
277
+ : new Date().getFullYear();
278
+
279
+ const totalCount =
280
+ typeof totalCountProp === "number"
281
+ ? totalCountProp
282
+ : data.reduce((sum, activity) => sum + activity.count, 0);
283
+
284
+ const width = weeks.length * (blockSize + blockMargin) - blockMargin;
285
+ const height = labelHeight + (blockSize + blockMargin) * 7 - blockMargin;
286
+
287
+ if (data.length === 0) {
288
+ return null;
289
+ }
290
+
291
+ return (
292
+ <ContributionGraphContext.Provider
293
+ value={{
294
+ data,
295
+ weeks,
296
+ blockMargin,
297
+ blockRadius,
298
+ blockSize,
299
+ fontSize,
300
+ labels,
301
+ labelHeight,
302
+ maxLevel,
303
+ totalCount,
304
+ weekStart,
305
+ year,
306
+ width,
307
+ height,
308
+ }}
309
+ >
310
+ <div
311
+ className={merge("flex w-max max-w-full flex-col gap-2", className)}
312
+ style={{ fontSize, ...style }}
313
+ {...props}
314
+ >
315
+ {children}
316
+ </div>
317
+ </ContributionGraphContext.Provider>
318
+ );
319
+ };
320
+
321
+ export type ContributionGraphBlockProps = HTMLAttributes<SVGRectElement> & {
322
+ activity: ContributionGraphActivity;
323
+ dayIndex: number;
324
+ weekIndex: number;
325
+ };
326
+
327
+ export const ContributionGraphBlock = ({
328
+ activity,
329
+ dayIndex,
330
+ weekIndex,
331
+ className,
332
+ ...props
333
+ }: ContributionGraphBlockProps) => {
334
+ const merge = useCn();
335
+ const { blockSize, blockMargin, blockRadius, labelHeight, maxLevel } =
336
+ useContributionGraph();
337
+
338
+ if (activity.level < 0 || activity.level > maxLevel) {
339
+ throw new RangeError(
340
+ `Provided activity level ${activity.level} for ${activity.date} is out of range. It must be between 0 and ${maxLevel}.`,
341
+ );
342
+ }
343
+
344
+ return (
345
+ <rect
346
+ className={merge(
347
+ 'data-[level="0"]:fill-surface-secondary',
348
+ 'data-[level="1"]:fill-primary/20',
349
+ 'data-[level="2"]:fill-primary/40',
350
+ 'data-[level="3"]:fill-primary/60',
351
+ 'data-[level="4"]:fill-primary/80',
352
+ className,
353
+ )}
354
+ data-count={activity.count}
355
+ data-date={activity.date}
356
+ data-level={activity.level}
357
+ height={blockSize}
358
+ rx={blockRadius}
359
+ ry={blockRadius}
360
+ width={blockSize}
361
+ x={(blockSize + blockMargin) * weekIndex}
362
+ y={labelHeight + (blockSize + blockMargin) * dayIndex}
363
+ {...props}
364
+ />
365
+ );
366
+ };
367
+
368
+ export type ContributionGraphCalendarProps = Omit<
369
+ HTMLAttributes<HTMLDivElement>,
370
+ "children"
371
+ > & {
372
+ hideMonthLabels?: boolean;
373
+ className?: string;
374
+ children: (props: {
375
+ activity: ContributionGraphActivity;
376
+ dayIndex: number;
377
+ weekIndex: number;
378
+ }) => ReactNode;
379
+ };
380
+
381
+ export const ContributionGraphCalendar = ({
382
+ hideMonthLabels = false,
383
+ className,
384
+ children,
385
+ ...props
386
+ }: ContributionGraphCalendarProps) => {
387
+ const merge = useCn();
388
+ const { weeks, width, height, blockSize, blockMargin, labels } =
389
+ useContributionGraph();
390
+
391
+ const monthLabels = useMemo(
392
+ () => getMonthLabels(weeks, labels.months),
393
+ [weeks, labels.months],
394
+ );
395
+
396
+ return (
397
+ <div
398
+ className={merge("max-w-full overflow-x-auto overflow-y-hidden", className)}
399
+ {...props}
400
+ >
401
+ <svg
402
+ className="block overflow-visible"
403
+ height={height}
404
+ viewBox={`0 0 ${width} ${height}`}
405
+ width={width}
406
+ >
407
+ <title>Contribution Graph</title>
408
+ {!hideMonthLabels && (
409
+ <g className="fill-current">
410
+ {monthLabels.map(({ label, weekIndex }) => (
411
+ <text
412
+ dominantBaseline="hanging"
413
+ key={weekIndex}
414
+ x={(blockSize + blockMargin) * weekIndex}
415
+ >
416
+ {label}
417
+ </text>
418
+ ))}
419
+ </g>
420
+ )}
421
+ {weeks.map((week, weekIndex) =>
422
+ week.map((activity, dayIndex) => {
423
+ if (!activity) {
424
+ return null;
425
+ }
426
+
427
+ return (
428
+ <Fragment key={`${weekIndex}-${dayIndex}`}>
429
+ {children({ activity, dayIndex, weekIndex })}
430
+ </Fragment>
431
+ );
432
+ }),
433
+ )}
434
+ </svg>
435
+ </div>
436
+ );
437
+ };
438
+
439
+ export type ContributionGraphFooterProps = HTMLAttributes<HTMLDivElement>;
440
+
441
+ export const ContributionGraphFooter = ({
442
+ className,
443
+ ...props
444
+ }: ContributionGraphFooterProps) => {
445
+ const merge = useCn();
446
+ return (
447
+ <div
448
+ className={merge(
449
+ "flex flex-wrap gap-1 whitespace-nowrap sm:gap-x-4",
450
+ className,
451
+ )}
452
+ {...props}
453
+ />
454
+ );
455
+ };
456
+
457
+ export type ContributionGraphTotalCountProps = Omit<
458
+ HTMLAttributes<HTMLDivElement>,
459
+ "children"
460
+ > & {
461
+ children?: (props: { totalCount: number; year: number }) => ReactNode;
462
+ };
463
+
464
+ export const ContributionGraphTotalCount = ({
465
+ className,
466
+ children,
467
+ ...props
468
+ }: ContributionGraphTotalCountProps) => {
469
+ const merge = useCn();
470
+ const { totalCount, year, labels } = useContributionGraph();
471
+
472
+ if (children) {
473
+ return <>{children({ totalCount, year })}</>;
474
+ }
475
+
476
+ return (
477
+ <div className={merge("text-muted-foreground", className)} {...props}>
478
+ {labels.totalCount
479
+ ? labels.totalCount
480
+ .replace("{{count}}", String(totalCount))
481
+ .replace("{{year}}", String(year))
482
+ : `${totalCount} activities in ${year}`}
483
+ </div>
484
+ );
485
+ };
486
+
487
+ export type ContributionGraphLegendProps = Omit<
488
+ HTMLAttributes<HTMLDivElement>,
489
+ "children"
490
+ > & {
491
+ children?: (props: { level: number }) => ReactNode;
492
+ };
493
+
494
+ export const ContributionGraphLegend = ({
495
+ className,
496
+ children,
497
+ ...props
498
+ }: ContributionGraphLegendProps) => {
499
+ const merge = useCn();
500
+ const { labels, maxLevel, blockSize, blockRadius } = useContributionGraph();
501
+
502
+ return (
503
+ <div
504
+ className={merge("ml-auto flex items-center gap-[3px]", className)}
505
+ {...props}
506
+ >
507
+ <span className="mr-1 text-muted-foreground">
508
+ {labels.legend?.less || "Less"}
509
+ </span>
510
+ {Array.from({ length: maxLevel + 1 }, (_, level) =>
511
+ children ? (
512
+ <Fragment key={level}>{children({ level })}</Fragment>
513
+ ) : (
514
+ <svg height={blockSize} key={level} width={blockSize}>
515
+ <title>{`${level} contributions`}</title>
516
+ <rect
517
+ className={merge(
518
+ "stroke-[1px] stroke-border",
519
+ 'data-[level="0"]:fill-surface-secondary',
520
+ 'data-[level="1"]:fill-primary/20',
521
+ 'data-[level="2"]:fill-primary/40',
522
+ 'data-[level="3"]:fill-primary/60',
523
+ 'data-[level="4"]:fill-primary/80',
524
+ )}
525
+ data-level={level}
526
+ height={blockSize}
527
+ rx={blockRadius}
528
+ ry={blockRadius}
529
+ width={blockSize}
530
+ />
531
+ </svg>
532
+ ),
533
+ )}
534
+ <span className="ml-1 text-muted-foreground">
535
+ {labels.legend?.more || "More"}
536
+ </span>
537
+ </div>
538
+ );
539
+ };
@@ -0,0 +1,72 @@
1
+ import type { HTMLAttributes, SVGProps } from "react";
2
+ import { useCn } from "../ui/ui-config";
3
+
4
+ export type LiveCursorProps = HTMLAttributes<HTMLSpanElement>;
5
+
6
+ export const LiveCursor = ({
7
+ className,
8
+ children,
9
+ ...props
10
+ }: LiveCursorProps) => {
11
+ const merge = useCn();
12
+ return (
13
+ <span
14
+ className={merge("pointer-events-none relative select-none", className)}
15
+ {...props}
16
+ >
17
+ {children}
18
+ </span>
19
+ );
20
+ };
21
+
22
+ export type LiveCursorPointerProps = SVGProps<SVGSVGElement>;
23
+
24
+ export const LiveCursorPointer = ({
25
+ className,
26
+ ...props
27
+ }: LiveCursorPointerProps) => {
28
+ const merge = useCn();
29
+ return (
30
+ <svg
31
+ aria-hidden="true"
32
+ className={merge("size-3.5", className)}
33
+ fill="none"
34
+ focusable="false"
35
+ height="20"
36
+ viewBox="0 0 20 20"
37
+ width="20"
38
+ xmlns="http://www.w3.org/2000/svg"
39
+ {...props}
40
+ >
41
+ <path
42
+ d="M19.438 6.716 1.115.05A.832.832 0 0 0 .05 1.116L6.712 19.45a.834.834 0 0 0 1.557.025l3.198-8 7.995-3.2a.833.833 0 0 0 0-1.559h-.024Z"
43
+ fill="currentColor"
44
+ />
45
+ </svg>
46
+ );
47
+ };
48
+
49
+ export type LiveCursorBodyProps = HTMLAttributes<HTMLSpanElement>;
50
+
51
+ export const LiveCursorBody = ({
52
+ children,
53
+ className,
54
+ ...props
55
+ }: LiveCursorBodyProps) => {
56
+ const merge = useCn();
57
+ return (
58
+ <span
59
+ className={merge(
60
+ "relative ml-3.5 flex flex-col whitespace-nowrap rounded-lg py-1 pr-3 pl-2.5 text-xs",
61
+ className,
62
+ )}
63
+ {...props}
64
+ >
65
+ {children}
66
+ </span>
67
+ );
68
+ };
69
+
70
+ export type LiveCursorNameProps = HTMLAttributes<HTMLSpanElement>;
71
+
72
+ export const LiveCursorName = (props: LiveCursorNameProps) => <span {...props} />;