@aircall/blocks 0.6.0 → 0.7.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.
- package/README.md +43 -0
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +80 -23
- package/dist/index.js +339 -86
- package/package.json +17 -3
- package/skills/aircall-blocks/migrate-dashboard/SKILL.md +127 -0
- package/skills/aircall-blocks/migrate-dashboard/card/SKILL.md +364 -0
- package/skills/aircall-blocks/migrate-dashboard/combobox/SKILL.md +487 -0
- package/skills/aircall-blocks/migrate-dashboard/copy-button/SKILL.md +138 -0
- package/skills/aircall-blocks/migrate-dashboard/dashboard-page/SKILL.md +198 -0
- package/skills/aircall-blocks/migrate-dashboard/data-table/SKILL.md +395 -0
- package/skills/aircall-blocks/migrate-dashboard/empty-states/SKILL.md +347 -0
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +271 -0
- package/skills/aircall-blocks/migrate-dashboard/info-popup/SKILL.md +179 -0
- package/skills/aircall-blocks/migrate-dashboard/layout/SKILL.md +328 -0
- package/skills/aircall-blocks/migrate-dashboard/list/SKILL.md +474 -0
- package/skills/aircall-blocks/migrate-dashboard/loading/SKILL.md +260 -0
- package/skills/aircall-blocks/migrate-dashboard/page-header/SKILL.md +394 -0
- package/skills/aircall-blocks/migrate-dashboard/tile/SKILL.md +429 -0
- package/skills/aircall-blocks/migrate-dashboard/toggle-row/SKILL.md +177 -0
- package/skills/aircall-blocks/setup/SKILL.md +127 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, InputGroup, InputGroupButton, InputGroupTextarea, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, TabsList, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
|
|
1
|
+
import { Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, InputGroup, InputGroupButton, InputGroupTextarea, Popover, PopoverContent, PopoverTrigger, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, TabsList, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { AircallLogo, ArrowUp, Check, ChevronLeft, ChevronRight, Copy, Ellipsis, LockKeyhole, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, TriangleAlert } from "@aircall/react-icons";
|
|
6
|
-
import
|
|
5
|
+
import { AircallLogo, ArrowUp, Check, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, LockKeyhole, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert, X } from "@aircall/react-icons";
|
|
6
|
+
import ReactMarkdown from "react-markdown";
|
|
7
|
+
import remarkGfm from "remark-gfm";
|
|
7
8
|
import { useCallbackRef, useControllableState, useIsMounted, useUnmount } from "@aircall/hooks";
|
|
9
|
+
import { cva } from "class-variance-authority";
|
|
8
10
|
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
|
|
9
11
|
import { mergeProps } from "@base-ui/react/merge-props";
|
|
10
12
|
import { useRender } from "@base-ui/react/use-render";
|
|
@@ -371,6 +373,290 @@ const UnknownErrorMedia = UnknownError.Media;
|
|
|
371
373
|
const UnknownErrorTitle = UnknownError.Title;
|
|
372
374
|
const UnknownErrorDescription = UnknownError.Description;
|
|
373
375
|
|
|
376
|
+
//#endregion
|
|
377
|
+
//#region src/hooks/use-copy-to-clipboard.ts
|
|
378
|
+
function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
|
|
379
|
+
const [copied, setCopied] = useState(false);
|
|
380
|
+
const timerRef = useRef(void 0);
|
|
381
|
+
const getIsMounted = useIsMounted();
|
|
382
|
+
const onCopiedRef = useCallbackRef(onCopied);
|
|
383
|
+
useUnmount(() => clearTimeout(timerRef.current));
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
clearTimeout(timerRef.current);
|
|
386
|
+
setCopied(false);
|
|
387
|
+
}, [value]);
|
|
388
|
+
return {
|
|
389
|
+
copy: useCallback(async () => {
|
|
390
|
+
try {
|
|
391
|
+
await navigator.clipboard.writeText(value);
|
|
392
|
+
if (!getIsMounted()) return;
|
|
393
|
+
setCopied(true);
|
|
394
|
+
onCopiedRef();
|
|
395
|
+
clearTimeout(timerRef.current);
|
|
396
|
+
timerRef.current = setTimeout(() => {
|
|
397
|
+
if (getIsMounted()) setCopied(false);
|
|
398
|
+
}, timeout);
|
|
399
|
+
} catch {}
|
|
400
|
+
}, [
|
|
401
|
+
value,
|
|
402
|
+
timeout,
|
|
403
|
+
getIsMounted,
|
|
404
|
+
onCopiedRef
|
|
405
|
+
]),
|
|
406
|
+
copied
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/components/copy-button.tsx
|
|
412
|
+
const CopyButtonContext = createContext(null);
|
|
413
|
+
function useCopyButtonContext() {
|
|
414
|
+
const ctx = useContext(CopyButtonContext);
|
|
415
|
+
if (ctx === null) throw new Error("CopyButtonIcon and CopyButtonLabel must be rendered inside a CopyButton.");
|
|
416
|
+
return ctx;
|
|
417
|
+
}
|
|
418
|
+
const CopyButton = React.forwardRef((componentProps, forwardRef) => {
|
|
419
|
+
const { value, timeout, onCopied, variant = "outline", children, ...buttonProps } = componentProps;
|
|
420
|
+
const { copy, copied } = useCopyToClipboard(value, {
|
|
421
|
+
timeout,
|
|
422
|
+
onCopied
|
|
423
|
+
});
|
|
424
|
+
return /* @__PURE__ */ jsx(CopyButtonContext.Provider, {
|
|
425
|
+
value: { copied },
|
|
426
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
427
|
+
ref: forwardRef,
|
|
428
|
+
onClick: () => {
|
|
429
|
+
copy();
|
|
430
|
+
},
|
|
431
|
+
variant,
|
|
432
|
+
...buttonProps,
|
|
433
|
+
children
|
|
434
|
+
})
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
CopyButton.displayName = "CopyButton";
|
|
438
|
+
const CopyButtonIcon = React.forwardRef((componentProps, forwardRef) => {
|
|
439
|
+
const { className } = componentProps;
|
|
440
|
+
const { copied } = useCopyButtonContext();
|
|
441
|
+
return /* @__PURE__ */ jsx(copied ? Check : Copy, {
|
|
442
|
+
ref: forwardRef,
|
|
443
|
+
className
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
CopyButtonIcon.displayName = "CopyButtonIcon";
|
|
447
|
+
const CopyButtonLabel = React.forwardRef((componentProps, forwardRef) => {
|
|
448
|
+
const { label = "Copy", copiedLabel = "Copied", className } = componentProps;
|
|
449
|
+
const { copied } = useCopyButtonContext();
|
|
450
|
+
return /* @__PURE__ */ jsx("span", {
|
|
451
|
+
ref: forwardRef,
|
|
452
|
+
className: cn(className),
|
|
453
|
+
children: copied ? copiedLabel : label
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
CopyButtonLabel.displayName = "CopyButtonLabel";
|
|
457
|
+
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/components/chatbot-response-block.tsx
|
|
460
|
+
function SourcesBadge({ sources }) {
|
|
461
|
+
const [open, setOpen] = useState(false);
|
|
462
|
+
const closeTimerRef = useRef();
|
|
463
|
+
const openedByHoverRef = useRef(false);
|
|
464
|
+
useUnmount(() => clearTimeout(closeTimerRef.current));
|
|
465
|
+
function handlePointerEnter(e) {
|
|
466
|
+
if (e.pointerType !== "mouse") return;
|
|
467
|
+
openedByHoverRef.current = true;
|
|
468
|
+
clearTimeout(closeTimerRef.current);
|
|
469
|
+
setOpen(true);
|
|
470
|
+
}
|
|
471
|
+
function handlePointerLeave(e) {
|
|
472
|
+
if (e.pointerType !== "mouse") return;
|
|
473
|
+
clearTimeout(closeTimerRef.current);
|
|
474
|
+
closeTimerRef.current = setTimeout(() => {
|
|
475
|
+
openedByHoverRef.current = false;
|
|
476
|
+
setOpen(false);
|
|
477
|
+
}, 150);
|
|
478
|
+
}
|
|
479
|
+
return /* @__PURE__ */ jsxs(Popover, {
|
|
480
|
+
open,
|
|
481
|
+
onOpenChange: (nextOpen, eventDetails) => {
|
|
482
|
+
if (!nextOpen) {
|
|
483
|
+
if (eventDetails.reason === "trigger-press" && openedByHoverRef.current) return;
|
|
484
|
+
openedByHoverRef.current = false;
|
|
485
|
+
clearTimeout(closeTimerRef.current);
|
|
486
|
+
}
|
|
487
|
+
setOpen(nextOpen);
|
|
488
|
+
},
|
|
489
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
490
|
+
className: "cursor-default rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
491
|
+
onPointerEnter: handlePointerEnter,
|
|
492
|
+
onPointerLeave: handlePointerLeave,
|
|
493
|
+
children: /* @__PURE__ */ jsx(Badge, {
|
|
494
|
+
className: "!rounded-full !bg-charcoal-400/20",
|
|
495
|
+
children: `${sources.length} ${sources.length === 1 ? "Source" : "Sources"}`
|
|
496
|
+
})
|
|
497
|
+
}), /* @__PURE__ */ jsx(PopoverContent, {
|
|
498
|
+
side: "top",
|
|
499
|
+
align: "start",
|
|
500
|
+
sideOffset: 8,
|
|
501
|
+
className: "w-80",
|
|
502
|
+
onPointerEnter: handlePointerEnter,
|
|
503
|
+
onPointerLeave: handlePointerLeave,
|
|
504
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
505
|
+
role: "list",
|
|
506
|
+
className: "flex flex-col",
|
|
507
|
+
children: sources.map((source) => /* @__PURE__ */ jsxs("div", {
|
|
508
|
+
role: "listitem",
|
|
509
|
+
className: "flex items-center gap-2 px-2 py-2",
|
|
510
|
+
children: [
|
|
511
|
+
/* @__PURE__ */ jsx(FileText, { className: "size-3.5 shrink-0 text-foreground" }),
|
|
512
|
+
/* @__PURE__ */ jsx("span", {
|
|
513
|
+
className: "min-w-0 flex-1 line-clamp-2 text-sm",
|
|
514
|
+
children: source.title
|
|
515
|
+
}),
|
|
516
|
+
/* @__PURE__ */ jsx("a", {
|
|
517
|
+
href: source.url,
|
|
518
|
+
target: "_blank",
|
|
519
|
+
rel: "noreferrer",
|
|
520
|
+
"aria-label": `Open ${source.title}`,
|
|
521
|
+
className: "shrink-0 text-foreground hover:text-primary",
|
|
522
|
+
children: /* @__PURE__ */ jsx(ExternalLink, { className: "size-3.5" })
|
|
523
|
+
})
|
|
524
|
+
]
|
|
525
|
+
}, source.url))
|
|
526
|
+
})
|
|
527
|
+
})]
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
function ChatbotResponseBlock({ messageId, markdown, streaming = false, sources, onFeedback, className }) {
|
|
531
|
+
const [feedback, setFeedback] = useState(null);
|
|
532
|
+
const onFeedbackRef = useCallbackRef(onFeedback);
|
|
533
|
+
const isMounted = useRef(false);
|
|
534
|
+
const feedbackRef = useRef(feedback);
|
|
535
|
+
feedbackRef.current = feedback;
|
|
536
|
+
useEffect(() => {
|
|
537
|
+
if (!isMounted.current) {
|
|
538
|
+
isMounted.current = true;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
if (feedbackRef.current === null) return;
|
|
542
|
+
setFeedback(null);
|
|
543
|
+
onFeedbackRef(null);
|
|
544
|
+
}, [
|
|
545
|
+
messageId,
|
|
546
|
+
markdown,
|
|
547
|
+
onFeedbackRef
|
|
548
|
+
]);
|
|
549
|
+
function handleFeedback(value) {
|
|
550
|
+
const next = feedback === value ? null : value;
|
|
551
|
+
setFeedback(next);
|
|
552
|
+
onFeedback?.(next);
|
|
553
|
+
}
|
|
554
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
555
|
+
"data-slot": "chatbot-response-block",
|
|
556
|
+
className: cn("flex flex-col gap-2", className),
|
|
557
|
+
children: [/* @__PURE__ */ jsx(ReactMarkdown, {
|
|
558
|
+
remarkPlugins: [remarkGfm],
|
|
559
|
+
components: {
|
|
560
|
+
h1: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h1", {
|
|
561
|
+
className: "mt-10 text-4xl font-semibold first:mt-0",
|
|
562
|
+
...props
|
|
563
|
+
}),
|
|
564
|
+
h2: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h2", {
|
|
565
|
+
className: "mt-11 border-b border-border pb-2 text-3xl font-semibold leading-9 first:mt-0",
|
|
566
|
+
...props
|
|
567
|
+
}),
|
|
568
|
+
h3: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("h3", {
|
|
569
|
+
className: "mt-10 text-2xl font-semibold leading-8 first:mt-0",
|
|
570
|
+
...props
|
|
571
|
+
}),
|
|
572
|
+
p: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("p", {
|
|
573
|
+
className: "mt-4 text-base leading-6 text-foreground first:mt-0",
|
|
574
|
+
...props
|
|
575
|
+
}),
|
|
576
|
+
blockquote: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("blockquote", {
|
|
577
|
+
className: "mt-4 border-l-2 border-border pl-6 first:mt-0",
|
|
578
|
+
...props
|
|
579
|
+
}),
|
|
580
|
+
ul: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ul", {
|
|
581
|
+
className: "mt-4 list-disc pl-6 text-base leading-6 text-foreground first:mt-0",
|
|
582
|
+
...props
|
|
583
|
+
}),
|
|
584
|
+
ol: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("ol", {
|
|
585
|
+
className: "mt-4 list-decimal pl-6 text-base leading-6 text-foreground first:mt-0",
|
|
586
|
+
...props
|
|
587
|
+
}),
|
|
588
|
+
li: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("li", {
|
|
589
|
+
className: "mb-2 last:mb-0",
|
|
590
|
+
...props
|
|
591
|
+
}),
|
|
592
|
+
a: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("a", {
|
|
593
|
+
className: "text-green-700 underline underline-offset-2 hover:text-green-700/80",
|
|
594
|
+
...props
|
|
595
|
+
}),
|
|
596
|
+
table: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("div", {
|
|
597
|
+
className: "mt-4 overflow-x-auto rounded border border-border first:mt-0",
|
|
598
|
+
children: /* @__PURE__ */ jsx("table", {
|
|
599
|
+
className: "w-full text-base leading-6",
|
|
600
|
+
...props
|
|
601
|
+
})
|
|
602
|
+
}),
|
|
603
|
+
th: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("th", {
|
|
604
|
+
className: "border-t border-border px-4 py-2 text-left font-bold text-foreground",
|
|
605
|
+
style,
|
|
606
|
+
...props
|
|
607
|
+
}),
|
|
608
|
+
td: ({ node: _n, style, ...props }) => /* @__PURE__ */ jsx("td", {
|
|
609
|
+
className: "border-t border-border px-4 py-2 text-foreground",
|
|
610
|
+
style,
|
|
611
|
+
...props
|
|
612
|
+
}),
|
|
613
|
+
tr: ({ node: _n, ...props }) => /* @__PURE__ */ jsx("tr", {
|
|
614
|
+
className: "even:bg-muted",
|
|
615
|
+
...props
|
|
616
|
+
})
|
|
617
|
+
},
|
|
618
|
+
children: markdown
|
|
619
|
+
}), !streaming && /* @__PURE__ */ jsxs("div", {
|
|
620
|
+
className: "flex items-center gap-1",
|
|
621
|
+
children: [sources && sources.length > 0 && /* @__PURE__ */ jsx("div", {
|
|
622
|
+
className: "flex flex-1",
|
|
623
|
+
children: /* @__PURE__ */ jsx(SourcesBadge, { sources })
|
|
624
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
625
|
+
className: "ml-auto flex items-center gap-1",
|
|
626
|
+
children: [
|
|
627
|
+
/* @__PURE__ */ jsx(CopyButton, {
|
|
628
|
+
value: markdown,
|
|
629
|
+
variant: "ghost",
|
|
630
|
+
size: "icon-sm",
|
|
631
|
+
"aria-label": "Copy",
|
|
632
|
+
children: /* @__PURE__ */ jsx(CopyButtonIcon, {})
|
|
633
|
+
}),
|
|
634
|
+
/* @__PURE__ */ jsx(Button, {
|
|
635
|
+
type: "button",
|
|
636
|
+
variant: "ghost",
|
|
637
|
+
size: "icon-sm",
|
|
638
|
+
"aria-pressed": feedback === "up",
|
|
639
|
+
"aria-label": "Helpful",
|
|
640
|
+
onClick: () => handleFeedback("up"),
|
|
641
|
+
className: cn(feedback === "up" && "text-primary"),
|
|
642
|
+
children: feedback === "up" ? /* @__PURE__ */ jsx(ThumbsUpFilled, {}) : /* @__PURE__ */ jsx(ThumbsUp, {})
|
|
643
|
+
}),
|
|
644
|
+
/* @__PURE__ */ jsx(Button, {
|
|
645
|
+
type: "button",
|
|
646
|
+
variant: "ghost",
|
|
647
|
+
size: "icon-sm",
|
|
648
|
+
"aria-pressed": feedback === "down",
|
|
649
|
+
"aria-label": "Not helpful",
|
|
650
|
+
onClick: () => handleFeedback("down"),
|
|
651
|
+
className: cn(feedback === "down" && "text-primary"),
|
|
652
|
+
children: feedback === "down" ? /* @__PURE__ */ jsx(ThumbsDownFilled, {}) : /* @__PURE__ */ jsx(ThumbsDown, {})
|
|
653
|
+
})
|
|
654
|
+
]
|
|
655
|
+
})]
|
|
656
|
+
})]
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
374
660
|
//#endregion
|
|
375
661
|
//#region src/components/chatbot-page.tsx
|
|
376
662
|
const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
|
|
@@ -384,6 +670,55 @@ const ChatbotPage = React.forwardRef((componentProps, forwardRef) => {
|
|
|
384
670
|
});
|
|
385
671
|
ChatbotPage.displayName = "ChatbotPage";
|
|
386
672
|
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/components/chatbot-panel-header.tsx
|
|
675
|
+
/**
|
|
676
|
+
* Header bar for the chatbot panel.
|
|
677
|
+
*
|
|
678
|
+
* Always visible — the panel body collapses/expands below it. Controlled by
|
|
679
|
+
* the parent via `open` + `onOpenChange`; pass `onClose` to show the dismiss (×) button.
|
|
680
|
+
*/
|
|
681
|
+
function ChatbotPanelHeader({ title = "Ask Aircall", open, onOpenChange, onFeedback, onClose, className, ...props }) {
|
|
682
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
683
|
+
"data-slot": "chatbot-panel-header",
|
|
684
|
+
"data-state": open ? "open" : "closed",
|
|
685
|
+
className: cn("flex w-full items-center gap-2 border-b border-border bg-background p-2", className),
|
|
686
|
+
...props,
|
|
687
|
+
children: [
|
|
688
|
+
/* @__PURE__ */ jsx("span", {
|
|
689
|
+
"aria-hidden": "true",
|
|
690
|
+
className: "flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground",
|
|
691
|
+
children: /* @__PURE__ */ jsx(PanelLeft, { className: "size-4" })
|
|
692
|
+
}),
|
|
693
|
+
/* @__PURE__ */ jsx("span", {
|
|
694
|
+
className: "min-w-0 flex-1 truncate text-base font-bold leading-none text-foreground",
|
|
695
|
+
children: title
|
|
696
|
+
}),
|
|
697
|
+
onFeedback != null && /* @__PURE__ */ jsx(Button, {
|
|
698
|
+
variant: "ghost",
|
|
699
|
+
size: "icon",
|
|
700
|
+
"aria-label": "Send feedback",
|
|
701
|
+
onClick: onFeedback,
|
|
702
|
+
children: /* @__PURE__ */ jsx(MessageSquare, { className: "size-4" })
|
|
703
|
+
}),
|
|
704
|
+
/* @__PURE__ */ jsx(Button, {
|
|
705
|
+
variant: "ghost",
|
|
706
|
+
size: "icon",
|
|
707
|
+
"aria-label": open ? "Collapse panel" : "Expand panel",
|
|
708
|
+
onClick: () => onOpenChange(!open),
|
|
709
|
+
children: open ? /* @__PURE__ */ jsx(Minus, { className: "size-4" }) : /* @__PURE__ */ jsx(Plus, { className: "size-4" })
|
|
710
|
+
}),
|
|
711
|
+
onClose != null && /* @__PURE__ */ jsx(Button, {
|
|
712
|
+
variant: "ghost",
|
|
713
|
+
size: "icon",
|
|
714
|
+
"aria-label": "Close panel",
|
|
715
|
+
onClick: onClose,
|
|
716
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-4" })
|
|
717
|
+
})
|
|
718
|
+
]
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
387
722
|
//#endregion
|
|
388
723
|
//#region src/components/chatbot-sidebar.tsx
|
|
389
724
|
const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
|
|
@@ -505,88 +840,6 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
|
|
|
505
840
|
});
|
|
506
841
|
}
|
|
507
842
|
|
|
508
|
-
//#endregion
|
|
509
|
-
//#region src/hooks/use-copy-to-clipboard.ts
|
|
510
|
-
function useCopyToClipboard(value, { timeout = 5e3, onCopied } = {}) {
|
|
511
|
-
const [copied, setCopied] = useState(false);
|
|
512
|
-
const timerRef = useRef(void 0);
|
|
513
|
-
const getIsMounted = useIsMounted();
|
|
514
|
-
const onCopiedRef = useCallbackRef(onCopied);
|
|
515
|
-
useUnmount(() => clearTimeout(timerRef.current));
|
|
516
|
-
useEffect(() => {
|
|
517
|
-
clearTimeout(timerRef.current);
|
|
518
|
-
setCopied(false);
|
|
519
|
-
}, [value]);
|
|
520
|
-
return {
|
|
521
|
-
copy: useCallback(async () => {
|
|
522
|
-
try {
|
|
523
|
-
await navigator.clipboard.writeText(value);
|
|
524
|
-
if (!getIsMounted()) return;
|
|
525
|
-
setCopied(true);
|
|
526
|
-
onCopiedRef();
|
|
527
|
-
clearTimeout(timerRef.current);
|
|
528
|
-
timerRef.current = setTimeout(() => {
|
|
529
|
-
if (getIsMounted()) setCopied(false);
|
|
530
|
-
}, timeout);
|
|
531
|
-
} catch {}
|
|
532
|
-
}, [
|
|
533
|
-
value,
|
|
534
|
-
timeout,
|
|
535
|
-
getIsMounted,
|
|
536
|
-
onCopiedRef
|
|
537
|
-
]),
|
|
538
|
-
copied
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
//#endregion
|
|
543
|
-
//#region src/components/copy-button.tsx
|
|
544
|
-
const CopyButtonContext = createContext(null);
|
|
545
|
-
function useCopyButtonContext() {
|
|
546
|
-
const ctx = useContext(CopyButtonContext);
|
|
547
|
-
if (ctx === null) throw new Error("CopyButtonIcon and CopyButtonLabel must be rendered inside a CopyButton.");
|
|
548
|
-
return ctx;
|
|
549
|
-
}
|
|
550
|
-
const CopyButton = React.forwardRef((componentProps, forwardRef) => {
|
|
551
|
-
const { value, timeout, onCopied, variant = "outline", children, ...buttonProps } = componentProps;
|
|
552
|
-
const { copy, copied } = useCopyToClipboard(value, {
|
|
553
|
-
timeout,
|
|
554
|
-
onCopied
|
|
555
|
-
});
|
|
556
|
-
return /* @__PURE__ */ jsx(CopyButtonContext.Provider, {
|
|
557
|
-
value: { copied },
|
|
558
|
-
children: /* @__PURE__ */ jsx(Button, {
|
|
559
|
-
ref: forwardRef,
|
|
560
|
-
onClick: () => {
|
|
561
|
-
copy();
|
|
562
|
-
},
|
|
563
|
-
variant,
|
|
564
|
-
...buttonProps,
|
|
565
|
-
children
|
|
566
|
-
})
|
|
567
|
-
});
|
|
568
|
-
});
|
|
569
|
-
CopyButton.displayName = "CopyButton";
|
|
570
|
-
const CopyButtonIcon = React.forwardRef((componentProps, forwardRef) => {
|
|
571
|
-
const { className } = componentProps;
|
|
572
|
-
const { copied } = useCopyButtonContext();
|
|
573
|
-
return /* @__PURE__ */ jsx(copied ? Check : Copy, {
|
|
574
|
-
ref: forwardRef,
|
|
575
|
-
className
|
|
576
|
-
});
|
|
577
|
-
});
|
|
578
|
-
CopyButtonIcon.displayName = "CopyButtonIcon";
|
|
579
|
-
const CopyButtonLabel = React.forwardRef((componentProps, forwardRef) => {
|
|
580
|
-
const { label = "Copy", copiedLabel = "Copied", className } = componentProps;
|
|
581
|
-
const { copied } = useCopyButtonContext();
|
|
582
|
-
return /* @__PURE__ */ jsx("span", {
|
|
583
|
-
ref: forwardRef,
|
|
584
|
-
className: cn(className),
|
|
585
|
-
children: copied ? copiedLabel : label
|
|
586
|
-
});
|
|
587
|
-
});
|
|
588
|
-
CopyButtonLabel.displayName = "CopyButtonLabel";
|
|
589
|
-
|
|
590
843
|
//#endregion
|
|
591
844
|
//#region src/components/chatbot-panel-suggestion.tsx
|
|
592
845
|
function ChatbotPanelSuggestion({ className, ...props }) {
|
|
@@ -1777,4 +2030,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
|
|
|
1777
2030
|
}
|
|
1778
2031
|
|
|
1779
2032
|
//#endregion
|
|
1780
|
-
export { BetaBadge, CardSaveBar, ChatbotInput, ChatbotPage, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormComboboxField, FormFieldBase, FormInputField, FormMultiComboboxField, FormNumberField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withForm };
|
|
2033
|
+
export { BetaBadge, CardSaveBar, ChatbotInput, ChatbotPage, ChatbotPanelHeader, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotResponseBlock, ChatbotSidebar, ChatbotUserMessage, ComingSoonDescription, ComingSoonEmptyState, ComingSoonMedia, ComingSoonTitle, CommonForm, CopyButton, CopyButtonIcon, CopyButtonLabel, DashboardPage, DashboardPageBanner, DashboardPageContent, DashboardPageHeader, DashboardPageHeaderAction, DashboardPageHeaderActions, DashboardPageHeaderDescription, DashboardPageHeaderNav, DashboardPageHeaderNavBack, DashboardPageHeaderPrefix, DashboardPageHeaderSubtitle, DashboardPageHeaderTitle, DashboardPageHeaderTitleGroup, DashboardPageMain, DashboardPageTabs, DashboardSidebar, DashboardSidebarContent, DashboardSidebarFooter, DashboardSidebarGroup, DashboardSidebarGroupLabel, DashboardSidebarHeader, DashboardSidebarMenu, DashboardSidebarMenuBadge, DashboardSidebarMenuButton, DashboardSidebarMenuItem, DashboardSidebarNav, DashboardSidebarProvider, DashboardSidebarSubmenu, DashboardSidebarSubmenuItem, DashboardSidebarSubmenuSeparator, DashboardSidebarTrigger, DashboardStandalonePage, DashboardStandalonePageAction, DashboardStandalonePageActions, DashboardStandalonePageContent, DashboardStandalonePageHeader, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormComboboxField, FormFieldBase, FormInputField, FormMultiComboboxField, FormNumberField, FormOTPField, FormRadioGroupField, FormSelectField, FormSliderField, FormSwitchField, FormTextareaField, FormToggleGroupField, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withForm };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aircall/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": [
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
18
|
"README.md",
|
|
19
|
-
"package.json"
|
|
19
|
+
"package.json",
|
|
20
|
+
"skills"
|
|
20
21
|
],
|
|
21
22
|
"exports": {
|
|
22
23
|
".": {
|
|
@@ -40,6 +41,10 @@
|
|
|
40
41
|
"optional": true
|
|
41
42
|
}
|
|
42
43
|
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"react-markdown": "9.0.3",
|
|
46
|
+
"remark-gfm": "4.0.1"
|
|
47
|
+
},
|
|
43
48
|
"devDependencies": {
|
|
44
49
|
"@aircall/react-icons": "*",
|
|
45
50
|
"@aircall/tsconfig": "1.4.3",
|
|
@@ -55,6 +60,7 @@
|
|
|
55
60
|
"@storybook/react-vite": "10.3.3",
|
|
56
61
|
"@tailwindcss/cli": "4.1.18",
|
|
57
62
|
"@tailwindcss/postcss": "4.1.18",
|
|
63
|
+
"@tanstack/intent": "0.1.1",
|
|
58
64
|
"@tanstack/react-form": "^1.33.0",
|
|
59
65
|
"@types/node": "24",
|
|
60
66
|
"@types/react": "19",
|
|
@@ -75,9 +81,17 @@
|
|
|
75
81
|
"vite": "7.3.1",
|
|
76
82
|
"vitest": "4.0.17",
|
|
77
83
|
"zod": "4.4.3",
|
|
78
|
-
"@aircall/ds": "0.
|
|
84
|
+
"@aircall/ds": "0.15.0",
|
|
79
85
|
"@aircall/hooks": "0.5.1"
|
|
80
86
|
},
|
|
87
|
+
"keywords": [
|
|
88
|
+
"tanstack-intent"
|
|
89
|
+
],
|
|
90
|
+
"repository": {
|
|
91
|
+
"type": "git",
|
|
92
|
+
"url": "git+https://gitlab.com/aircall/shared/hydra.git",
|
|
93
|
+
"directory": "packages/blocks"
|
|
94
|
+
},
|
|
81
95
|
"scripts": {
|
|
82
96
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
83
97
|
"build:js": "tsdown --config ./tsdown.config.ts",
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: aircall-blocks/migrate-dashboard
|
|
3
|
+
description: >
|
|
4
|
+
Migrate a file from @dashboard/library to @aircall/blocks / @aircall/ds. Load FIRST
|
|
5
|
+
to get the full component-level routing table before picking a recipe. Identifies what
|
|
6
|
+
has a blocks/ds equivalent, what is out of scope, and which sub-skill to load next.
|
|
7
|
+
type: core
|
|
8
|
+
library: aircall-blocks
|
|
9
|
+
library_version: "0.5.1"
|
|
10
|
+
requires:
|
|
11
|
+
- aircall-blocks/setup
|
|
12
|
+
sources:
|
|
13
|
+
- "aircall/hydra:docs/migration-guides/dashboard-lib-to-blocks/AGENTS.md"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Migrate @dashboard/library → @aircall/blocks / @aircall/ds
|
|
17
|
+
|
|
18
|
+
This is the overview router for the `@dashboard/library` migration. Load it first to
|
|
19
|
+
identify the right sub-skill for each component you are migrating, and to determine what
|
|
20
|
+
falls outside the scope of blocks and ds entirely.
|
|
21
|
+
|
|
22
|
+
## How to run this migration (end-to-end)
|
|
23
|
+
|
|
24
|
+
Migrate incrementally — one screen/file at a time, shipping each green:
|
|
25
|
+
|
|
26
|
+
0. **Set up once** — load `@aircall/blocks#aircall-blocks/setup` (it builds on
|
|
27
|
+
`@aircall/ds#aircall-ds/setup`): install `@aircall/blocks` + `@aircall/ds`, import both
|
|
28
|
+
`globals.css` bundles, and mount the DS root providers — including `DsI18nProvider`
|
|
29
|
+
(under your react-i18next provider, fed the user's language; importing `@aircall/blocks`
|
|
30
|
+
registers a `blocks` i18n namespace, so it needs DS's i18n engine) and
|
|
31
|
+
`NotificationQueueProvider` if you use banners. Add the jsdom test shims from setup.
|
|
32
|
+
(Loaded automatically via `requires`, but do the wiring first.)
|
|
33
|
+
1. **Inventory** — list the file's `@dashboard/library` imports. The routing table below
|
|
34
|
+
says which have a blocks/ds equivalent and which are out of scope (hooks/utils/constants
|
|
35
|
+
don't migrate — leave them on `@dashboard/library`).
|
|
36
|
+
2. **Migrate** — load the per-area recipe from the routing table for each UI component. Any
|
|
37
|
+
Tractor primitives in the same file migrate via `@aircall/ds#aircall-ds/migrate-tractor`;
|
|
38
|
+
icons via `@aircall/ds#aircall-ds/migrate-icons`.
|
|
39
|
+
3. **Verify green** — `tsc --noEmit`, tests (DS popups/Switch need the setup jsdom shims),
|
|
40
|
+
biome/lint.
|
|
41
|
+
4. **Repeat** until no in-scope `@dashboard/library` UI imports remain.
|
|
42
|
+
|
|
43
|
+
## Cross-cutting note
|
|
44
|
+
|
|
45
|
+
`@dashboard/library` mixes UI components with non-UI utilities. Only the UI components
|
|
46
|
+
migrate. When writing replacement code:
|
|
47
|
+
|
|
48
|
+
- Import block-level compositions (page layout, empty states, form layer) from
|
|
49
|
+
`'@aircall/blocks'`.
|
|
50
|
+
- Import DS primitives (Card, Spinner, DataTable, Combobox, ItemGroup, etc.) from
|
|
51
|
+
`'@aircall/ds'`.
|
|
52
|
+
- Never mix the two import paths for the same logical component — pick the package that
|
|
53
|
+
owns it per the table below.
|
|
54
|
+
|
|
55
|
+
## Forms — never local state
|
|
56
|
+
|
|
57
|
+
Any form that collects and submits data migrates to **`@aircall/blocks` `useForm` + the
|
|
58
|
+
`Form*Field` wrappers** (the Storybook-proven `CommonForm` pattern) — NOT React
|
|
59
|
+
`useState` per field, and NOT bare ds `Field`/`Input` primitives wired by hand. The form
|
|
60
|
+
owns field state, validation, dirty/`canSubmit`/`isSubmitting`, and error display; those
|
|
61
|
+
are what drive `SubmitButton`/`CardSaveBar`. Hand-rolled `useState` bypasses all of it and
|
|
62
|
+
must be rewritten, not preserved, during migration. (`useState` for non-field UI — open,
|
|
63
|
+
active tab — is fine.) Converting a large legacy `useState` form is a deliberate refactor,
|
|
64
|
+
not a 1:1 swap. See `…/migrate-dashboard/form-wizard` and `@aircall/ds#aircall-ds/migrate-tractor/form-and-field`.
|
|
65
|
+
|
|
66
|
+
## Out of scope
|
|
67
|
+
|
|
68
|
+
The following `@dashboard/library` exports do NOT have an equivalent in `@aircall/blocks`
|
|
69
|
+
or `@aircall/ds`. Do not try to map them to a DS component. They need a shared
|
|
70
|
+
utils/data home or must stay in the consuming app.
|
|
71
|
+
|
|
72
|
+
**Hooks**: `useGraphQuery`, `useGraphMutation`, `useToast`, `useToggle`,
|
|
73
|
+
`useBroadcastChannel`
|
|
74
|
+
|
|
75
|
+
**Helpers / utils**: `generateRandomUUID`, `isTruthy`, `toFixedNumber`,
|
|
76
|
+
`capitalizeFirstLetter`, `getInitials`
|
|
77
|
+
|
|
78
|
+
**Constants, types, and contexts**: `ROLE_NAME`, `RESOURCE`,
|
|
79
|
+
`NavigationBlockerProvider`, `ClientError`
|
|
80
|
+
|
|
81
|
+
**UI with no blocks/ds equivalent yet**: some `@dashboard/library` UI components have
|
|
82
|
+
no target in `@aircall/blocks` or `@aircall/ds` yet (e.g. `PieChart` and other charts,
|
|
83
|
+
`AudioPlayer`, `TileLegend`). If a UI component is not in the routing table below and
|
|
84
|
+
not listed above, leave it imported from `@dashboard/library` for now — do not force a
|
|
85
|
+
migration or invent a target.
|
|
86
|
+
|
|
87
|
+
## Component routing table
|
|
88
|
+
|
|
89
|
+
| @dashboard/library | Target (pkg) | Recipe to load | Status |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| `MessageScreen` family + `UnknownError` | `ComingSoonEmptyState`, `RestrictedAccessEmptyState`, `NotFoundEmptyState`, `NoDataEmptyState`, `NoSupportEmptyState`, `UnknownErrorEmptyState` and the `EmptyState*` parts (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/empty-states | available |
|
|
92
|
+
| `PageHeader` | `DashboardPageHeader` (+ `DashboardPageHeaderTitle` / `DashboardPageHeaderActions` / `DashboardPageHeaderAction` / `DashboardPageHeaderNav` / `DashboardPageHeaderDescription`) (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/page-header | available |
|
|
93
|
+
| Page / screen shell — incl. full-page flows (Campaign Creation, Add Contacts) | `DashboardPage` (standard: sidebar + header + tabs + content) / `DashboardStandalonePage` (full-page, no sidebar) (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/dashboard-page | available |
|
|
94
|
+
| `Paper` | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, `CardAction` (and @aircall/blocks `CardSaveBar` for a save bar) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/card | available |
|
|
95
|
+
| `LoadMoreTable` | `DataTable` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/data-table | available |
|
|
96
|
+
| `MultiSearchSelect` + `MultiInlineSearchSelect` + `MultiSelectOption` | `Combobox` with `multiple` (multi-select) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available |
|
|
97
|
+
| `SingleSearchSelect` / `SearchSelect` / single-value `MultiSelect` | `Combobox` WITHOUT `multiple` (single-select) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/combobox | available |
|
|
98
|
+
| `FormWizard` + `useFormWizard` + `FormField` | `useForm`, `CommonForm`, `FormInputField`/`FormSelectField`/`FormComboboxField`/etc., `CardSaveBar`/`SaveBar` (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available |
|
|
99
|
+
| `Loading` | `Spinner` (and `Skeleton` for content placeholders) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/loading | available |
|
|
100
|
+
| `List` + `ListItem` | `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemActions` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/list | available |
|
|
101
|
+
| `Tile` + `TileHeader` + `TileValue` | `Card` (+ parts) as the tile container (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/tile | available |
|
|
102
|
+
| `GridLayout` + `GridItem` + `Gap` | native `<div>` + Tailwind grid/flex/gap utilities (NO component import needed — these are layout primitives) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/layout | available |
|
|
103
|
+
| `InfoPopup` + `InfoPopupTrigger` + `InfoPopupContent` | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/info-popup | available |
|
|
104
|
+
| `CopyToClipboardButton` + `CopyToClipboardText` | `CopyButton`, `CopyButtonIcon`, `CopyButtonLabel` (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/copy-button | available |
|
|
105
|
+
| `ToggleRow` | `Field` (orientation="horizontal") + `Switch` + `FieldLabel` + optional `FieldDescription` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/toggle-row | available |
|
|
106
|
+
| `AddButton` + `SaveButton` + `LoadingButton` | `Button` (@aircall/ds) — `AddButton` → `Button` with leading icon; `LoadingButton` loading state → `Button disabled` + `Spinner`; `SaveButton` saved state → `Button variant="outline"` + check icon | load @aircall/ds#aircall-ds/migrate-tractor/button | available |
|
|
107
|
+
| `ConditionalTooltip` | Conditional JSX: wrap children in `<Tooltip>` / `<TooltipTrigger>` / `<TooltipContent>` when condition is true, render children bare when false — no wrapper component needed (@aircall/ds) | inline — no sub-skill | available |
|
|
108
|
+
| `RadioBoxGroup` + `RadioBox` | `RadioGroup` + `RadioGroupItem` wrapped in `Field` + `FieldLabel` + `FieldContent` (@aircall/ds) | inline — see @aircall/ds#aircall-ds/migrate-tractor/form-and-field for Field patterns | available |
|
|
109
|
+
| `TagBeautified` | `Badge` with `legacyColor` prop for stored hex colors; `Badge` with `color` + `tone` props for new tags (@aircall/ds) | inline — `<Badge legacyColor="#0761b5">Sales</Badge>` | available |
|
|
110
|
+
| `RolesTags` | `RoleBadge` with `role` prop (`"owner"` \| `"admin"` \| `"supervisor"` \| `"agent"`) (@aircall/blocks) | inline — `<RoleBadge role="admin" />` | available |
|
|
111
|
+
| `Count` | `CounterBadge` — pass the capped value as children: `<CounterBadge>{n > 99 ? '99+' : n}</CounterBadge>` (@aircall/ds) | inline — no size or max props | available |
|
|
112
|
+
| `Avatar` | `Avatar`, `AvatarImage`, `AvatarFallback` compound (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/avatar | available |
|
|
113
|
+
| `ProgressBar` + `OptimisticProgressBar` | `Progress` with `value` prop (0–100) (@aircall/ds) | inline — `<Progress value={60} />` | available |
|
|
114
|
+
| `ShadowScrollContainer` | `ScrollArea` with `scrollFade` prop — use a fixed `h-[…]` not `max-h-[…]` on the root (@aircall/ds) | inline — `<ScrollArea scrollFade className="h-[400px] rounded-md border">` | available |
|
|
115
|
+
| `AccordionSection` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/accordion | available |
|
|
116
|
+
| `ConfirmationModal` | `AlertDialog` and its parts (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/dialog | available |
|
|
117
|
+
| `Tab` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/tabs | available |
|
|
118
|
+
| `Skeleton` + `SkeletonText` | `Skeleton` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-tractor/skeleton | available |
|
|
119
|
+
|
|
120
|
+
## How to use this router
|
|
121
|
+
|
|
122
|
+
1. Identify the `@dashboard/library` export(s) in the file you are migrating.
|
|
123
|
+
2. Check the **Out of scope** section first — if the export is listed there, skip it.
|
|
124
|
+
3. For each UI component, find the matching row in the table above and load the
|
|
125
|
+
indicated recipe sub-skill.
|
|
126
|
+
4. Each sub-skill is self-contained: it carries the full prop mapping, common mistakes,
|
|
127
|
+
and import paths for its component family.
|