@aircall/blocks 0.11.0 → 0.13.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 +22 -2
- package/dist/globals.css +1 -1
- package/dist/index.d.ts +270 -151
- package/dist/index.js +153 -55
- package/package.json +3 -3
- package/skills/aircall-blocks/migrate-dashboard/form-wizard/SKILL.md +21 -2
- package/skills/aircall-blocks/setup/SKILL.md +110 -12
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Anchor, Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, HoverCard, HoverCardContent, HoverCardTrigger, InputGroup, InputGroupButton, InputGroupTextarea, Item, ItemActions, ItemContent, ItemGroup, ItemMedia, ItemTitle, Popover, PopoverContent, PopoverTrigger, Separator, Sidebar, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, Spinner, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TabsList, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsTranslation, useSidebar } from "@aircall/ds";
|
|
1
|
+
import { Anchor, Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldDescription, FieldError, FieldLabel, FieldLabelAside, FieldLabelInfo, FieldLabelRow, HoverCard, HoverCardContent, HoverCardTrigger, InputGroup, InputGroupButton, InputGroupTextarea, Item, ItemActions, ItemContent, ItemGroup, ItemMedia, ItemTitle, Popover, PopoverContent, PopoverTrigger, Separator, Sidebar, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuAction, SidebarMenuButton, SidebarMenuItem, SidebarProvider, Spinner, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TabsList, ToggleGroup, ToggleGroupItem, 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, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { AircallLoader, AircallLogo, AircallMark, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, LockKeyhole, Menu, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, SquareArrowOutUpRight, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert } from "@aircall/react-icons";
|
|
5
|
+
import { AircallLoader, AircallLogo, AircallMark, ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, History, LockKeyhole, Menu, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Sprout, Square, SquareArrowOutUpRight, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert } from "@aircall/react-icons";
|
|
6
6
|
import ReactMarkdown from "react-markdown";
|
|
7
7
|
import remarkGfm from "remark-gfm";
|
|
8
8
|
import { useCallbackRef, useControllableState, useIsMounted, useUnmount } from "@aircall/hooks";
|
|
@@ -29,6 +29,9 @@ const de = {
|
|
|
29
29
|
"chatbotInput.messageLabel": "Nachricht",
|
|
30
30
|
"chatbotInput.stopGenerating": "Generierung stoppen",
|
|
31
31
|
"chatbotInput.sendMessage": "Nachricht senden",
|
|
32
|
+
"chatbotInput.conversationHistory": "Gesprächsverlauf",
|
|
33
|
+
"chatbotInput.noConversations": "Keine vergangenen Gespräche",
|
|
34
|
+
"chatbotInput.recentChatHistory": "Letzter Chatverlauf",
|
|
32
35
|
"productBadge.beta": "Beta",
|
|
33
36
|
"productBadge.new": "Neu",
|
|
34
37
|
"productBadge.deprecated": "Veraltet",
|
|
@@ -85,6 +88,9 @@ const en = {
|
|
|
85
88
|
"chatbotInput.messageLabel": "Message",
|
|
86
89
|
"chatbotInput.stopGenerating": "Stop generating",
|
|
87
90
|
"chatbotInput.sendMessage": "Send message",
|
|
91
|
+
"chatbotInput.conversationHistory": "Conversation history",
|
|
92
|
+
"chatbotInput.noConversations": "No past conversations",
|
|
93
|
+
"chatbotInput.recentChatHistory": "Recent chat history",
|
|
88
94
|
"productBadge.beta": "Beta",
|
|
89
95
|
"productBadge.new": "New",
|
|
90
96
|
"productBadge.deprecated": "Deprecated",
|
|
@@ -141,6 +147,9 @@ const es = {
|
|
|
141
147
|
"chatbotInput.messageLabel": "Mensaje",
|
|
142
148
|
"chatbotInput.stopGenerating": "Detener la generación",
|
|
143
149
|
"chatbotInput.sendMessage": "Enviar mensaje",
|
|
150
|
+
"chatbotInput.conversationHistory": "Historial de conversaciones",
|
|
151
|
+
"chatbotInput.noConversations": "No hay conversaciones pasadas",
|
|
152
|
+
"chatbotInput.recentChatHistory": "Historial de chats recientes",
|
|
144
153
|
"productBadge.beta": "Beta",
|
|
145
154
|
"productBadge.new": "Nuevo",
|
|
146
155
|
"productBadge.deprecated": "Obsoleto",
|
|
@@ -197,6 +206,9 @@ const fr = {
|
|
|
197
206
|
"chatbotInput.messageLabel": "Message",
|
|
198
207
|
"chatbotInput.stopGenerating": "Arrêter la génération",
|
|
199
208
|
"chatbotInput.sendMessage": "Envoyer un message",
|
|
209
|
+
"chatbotInput.conversationHistory": "Historique des conversations",
|
|
210
|
+
"chatbotInput.noConversations": "Aucune conversation passée",
|
|
211
|
+
"chatbotInput.recentChatHistory": "Historique récent des chats",
|
|
200
212
|
"productBadge.beta": "Bêta",
|
|
201
213
|
"productBadge.new": "Nouveau",
|
|
202
214
|
"productBadge.deprecated": "Obsolète",
|
|
@@ -253,6 +265,9 @@ const it = {
|
|
|
253
265
|
"chatbotInput.messageLabel": "Messaggio",
|
|
254
266
|
"chatbotInput.stopGenerating": "Interrompi la generazione",
|
|
255
267
|
"chatbotInput.sendMessage": "Invia messaggio",
|
|
268
|
+
"chatbotInput.conversationHistory": "Cronologia conversazioni",
|
|
269
|
+
"chatbotInput.noConversations": "Nessuna conversazione passata",
|
|
270
|
+
"chatbotInput.recentChatHistory": "Cronologia recente delle chat",
|
|
256
271
|
"productBadge.beta": "Beta",
|
|
257
272
|
"productBadge.new": "Nuovo",
|
|
258
273
|
"productBadge.deprecated": "Obsoleto",
|
|
@@ -1445,7 +1460,7 @@ function ChatbotPanelHeader({ title = "Ask Aircall", onMenu, menuContent, menuOp
|
|
|
1445
1460
|
const SIDEBAR_MIN_WIDTH = 140;
|
|
1446
1461
|
const SIDEBAR_MAX_WIDTH = 480;
|
|
1447
1462
|
const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
|
|
1448
|
-
const { conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, recentLabel = "Recent", popup = false, className } = componentProps;
|
|
1463
|
+
const { conversations, activeConversationId, onNewChat, onSelectConversation, renderConversationMenuContent, recentLabel = "Recent", showNewChat = true, popup = false, className } = componentProps;
|
|
1449
1464
|
const t = useDsTranslation("blocks");
|
|
1450
1465
|
const isNewChatActive = !activeConversationId || !conversations.some((c) => c.id === activeConversationId);
|
|
1451
1466
|
const sidebarInternalRef = React.useRef(null);
|
|
@@ -1478,7 +1493,7 @@ const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
|
|
|
1478
1493
|
const scrollableContent = /* @__PURE__ */ jsxs("div", {
|
|
1479
1494
|
"data-slot": "sidebar-content",
|
|
1480
1495
|
className: cn("flex min-h-0 flex-1 flex-col gap-2 overflow-auto", "[scrollbar-width:thin] [scrollbar-color:transparent_transparent] hover:[scrollbar-color:rgba(0,0,0,0.15)_transparent]", "[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-thumb]:rounded-full", "[&::-webkit-scrollbar-thumb]:bg-transparent [&:hover::-webkit-scrollbar-thumb]:bg-black/20"),
|
|
1481
|
-
children: [/* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
1496
|
+
children: [showNewChat && /* @__PURE__ */ jsx(SidebarGroup, { children: /* @__PURE__ */ jsx(SidebarMenu, { children: /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
|
|
1482
1497
|
isActive: isNewChatActive,
|
|
1483
1498
|
onClick: onNewChat,
|
|
1484
1499
|
children: [/* @__PURE__ */ jsx(Plus, {}), /* @__PURE__ */ jsx("span", { children: t("chatbotSidebar.newChat") })]
|
|
@@ -1533,6 +1548,17 @@ const ChatbotSidebar = React.forwardRef((componentProps, forwardRef) => {
|
|
|
1533
1548
|
});
|
|
1534
1549
|
ChatbotSidebar.displayName = "ChatbotSidebar";
|
|
1535
1550
|
|
|
1551
|
+
//#endregion
|
|
1552
|
+
//#region src/components/chatbot-panel-suggestion.tsx
|
|
1553
|
+
function ChatbotPanelSuggestion({ className, ...props }) {
|
|
1554
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1555
|
+
"data-slot": "chatbot-panel-suggestion",
|
|
1556
|
+
variant: "ghost",
|
|
1557
|
+
className: cn("h-auto max-w-[280px] text-left rounded-[18px] border border-primary px-3 py-1.5", "bg-background text-primary whitespace-normal", "hover:bg-[color-mix(in_srgb,#5BF1B2_20%,var(--background))] hover:text-primary", className),
|
|
1558
|
+
...props
|
|
1559
|
+
});
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1536
1562
|
//#endregion
|
|
1537
1563
|
//#region src/components/chatbot-input.tsx
|
|
1538
1564
|
const textareaVariants = cva([
|
|
@@ -1556,13 +1582,14 @@ const textareaVariants = cva([
|
|
|
1556
1582
|
expanded: false
|
|
1557
1583
|
}
|
|
1558
1584
|
});
|
|
1559
|
-
function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, placeholder, loading = false, size = "default", expanded = false, disabled, ...textareaProps }) {
|
|
1585
|
+
function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, placeholder, loading = false, size = "default", expanded = false, disabled, suggestions, onSuggestionClick, conversations, activeConversationId, onSelectConversation, ...textareaProps }) {
|
|
1560
1586
|
const t = useDsTranslation("blocks");
|
|
1561
1587
|
const [currentValue = "", setCurrentValue] = useControllableState({
|
|
1562
1588
|
prop: value,
|
|
1563
1589
|
defaultProp: "",
|
|
1564
1590
|
onChange: onValueChange
|
|
1565
1591
|
});
|
|
1592
|
+
const [historyOpen, setHistoryOpen] = React.useState(false);
|
|
1566
1593
|
const hasValue = currentValue.trim().length > 0;
|
|
1567
1594
|
const handleChange = (event) => {
|
|
1568
1595
|
setCurrentValue(event.target.value);
|
|
@@ -1579,10 +1606,63 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
|
|
|
1579
1606
|
}
|
|
1580
1607
|
textareaProps.onKeyDown?.(event);
|
|
1581
1608
|
};
|
|
1609
|
+
const hasSuggestions = suggestions && suggestions.length > 0;
|
|
1610
|
+
const hasBottomRow = hasSuggestions || conversations !== void 0;
|
|
1611
|
+
const sendStopButton = loading ? /* @__PURE__ */ jsx(InputGroupButton, {
|
|
1612
|
+
variant: "secondary",
|
|
1613
|
+
size: "icon",
|
|
1614
|
+
className: "rounded-full",
|
|
1615
|
+
"aria-label": t("chatbotInput.stopGenerating"),
|
|
1616
|
+
disabled,
|
|
1617
|
+
onClick: onStop,
|
|
1618
|
+
children: /* @__PURE__ */ jsx(Square, {})
|
|
1619
|
+
}) : /* @__PURE__ */ jsx(InputGroupButton, {
|
|
1620
|
+
variant: hasValue ? "default" : "ghost",
|
|
1621
|
+
size: "icon",
|
|
1622
|
+
className: "rounded-full",
|
|
1623
|
+
"aria-label": t("chatbotInput.sendMessage"),
|
|
1624
|
+
disabled: disabled || !hasValue,
|
|
1625
|
+
onClick: submit,
|
|
1626
|
+
children: /* @__PURE__ */ jsx(ArrowUp, {})
|
|
1627
|
+
});
|
|
1628
|
+
const historyButton = conversations !== void 0 ? /* @__PURE__ */ jsxs(Popover, {
|
|
1629
|
+
open: historyOpen,
|
|
1630
|
+
onOpenChange: setHistoryOpen,
|
|
1631
|
+
children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx(Button, {
|
|
1632
|
+
variant: "ghost",
|
|
1633
|
+
size: "icon",
|
|
1634
|
+
disabled,
|
|
1635
|
+
"aria-label": t("chatbotInput.conversationHistory"),
|
|
1636
|
+
children: /* @__PURE__ */ jsx(History, {})
|
|
1637
|
+
}) }), /* @__PURE__ */ jsx(PopoverContent, {
|
|
1638
|
+
align: "end",
|
|
1639
|
+
sideOffset: 4,
|
|
1640
|
+
className: "w-80 gap-0 p-0",
|
|
1641
|
+
children: conversations.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
1642
|
+
className: "px-4 py-6 text-center text-sm text-muted-foreground",
|
|
1643
|
+
children: t("chatbotInput.noConversations")
|
|
1644
|
+
}) : /* @__PURE__ */ jsx(SidebarProvider, {
|
|
1645
|
+
className: "!min-h-0",
|
|
1646
|
+
style: { "--sidebar-width": "20rem" },
|
|
1647
|
+
children: /* @__PURE__ */ jsx(ChatbotSidebar, {
|
|
1648
|
+
popup: true,
|
|
1649
|
+
showNewChat: false,
|
|
1650
|
+
recentLabel: t("chatbotInput.recentChatHistory"),
|
|
1651
|
+
className: "bg-sidebar [&_[data-slot=sidebar-content]]:gap-0 [&_[data-slot=sidebar-content]]:max-h-[280px]",
|
|
1652
|
+
conversations,
|
|
1653
|
+
activeConversationId,
|
|
1654
|
+
onSelectConversation: (id) => {
|
|
1655
|
+
onSelectConversation?.(id);
|
|
1656
|
+
setHistoryOpen(false);
|
|
1657
|
+
}
|
|
1658
|
+
})
|
|
1659
|
+
})
|
|
1660
|
+
})]
|
|
1661
|
+
}) : null;
|
|
1582
1662
|
return /* @__PURE__ */ jsxs(InputGroup, {
|
|
1583
1663
|
"data-slot": "chatbot-input",
|
|
1584
1664
|
"data-loading": loading || void 0,
|
|
1585
|
-
className: cn("h-auto
|
|
1665
|
+
className: cn("h-auto gap-2 overflow-clip rounded-3xl bg-background py-2 pr-2 pl-6", hasBottomRow ? "w-fit flex-col items-stretch" : "items-end", "shadow-[0px_0px_8px_0px_var(--input)]", "hover:shadow-[0px_0px_8px_0px_var(--ring)]", "has-[[data-slot=input-group-control]:focus-visible]:border-green-400", "has-[[data-slot=input-group-control]:focus-visible]:ring-0", "has-[[data-slot=input-group-control]:focus-visible]:shadow-[0px_0px_8px_0px_var(--ring)]", className),
|
|
1586
1666
|
"aria-disabled": disabled || void 0,
|
|
1587
1667
|
children: [/* @__PURE__ */ jsx(InputGroupTextarea, {
|
|
1588
1668
|
rows: 1,
|
|
@@ -1597,40 +1677,27 @@ function ChatbotInput({ className, value, onValueChange, onSubmit, onStop, place
|
|
|
1597
1677
|
size,
|
|
1598
1678
|
expanded
|
|
1599
1679
|
}))
|
|
1600
|
-
}), /* @__PURE__ */
|
|
1680
|
+
}), hasBottomRow ? /* @__PURE__ */ jsxs("div", {
|
|
1681
|
+
className: "flex items-center justify-between gap-2",
|
|
1682
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1683
|
+
className: "flex gap-2",
|
|
1684
|
+
children: hasSuggestions && suggestions.map((s) => /* @__PURE__ */ jsx(ChatbotPanelSuggestion, {
|
|
1685
|
+
className: "max-w-none shrink-0 whitespace-nowrap",
|
|
1686
|
+
disabled,
|
|
1687
|
+
onClick: () => onSuggestionClick?.(s),
|
|
1688
|
+
children: s.label
|
|
1689
|
+
}, s.id))
|
|
1690
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1691
|
+
className: "flex items-center gap-2",
|
|
1692
|
+
children: [historyButton, sendStopButton]
|
|
1693
|
+
})]
|
|
1694
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
1601
1695
|
className: "flex shrink-0 items-center gap-2",
|
|
1602
|
-
children:
|
|
1603
|
-
variant: "secondary",
|
|
1604
|
-
size: "icon",
|
|
1605
|
-
className: "rounded-full",
|
|
1606
|
-
"aria-label": t("chatbotInput.stopGenerating"),
|
|
1607
|
-
disabled,
|
|
1608
|
-
onClick: onStop,
|
|
1609
|
-
children: /* @__PURE__ */ jsx(Square, {})
|
|
1610
|
-
}) : /* @__PURE__ */ jsx(InputGroupButton, {
|
|
1611
|
-
variant: hasValue ? "default" : "ghost",
|
|
1612
|
-
size: "icon",
|
|
1613
|
-
className: "rounded-full",
|
|
1614
|
-
"aria-label": t("chatbotInput.sendMessage"),
|
|
1615
|
-
disabled: disabled || !hasValue,
|
|
1616
|
-
onClick: submit,
|
|
1617
|
-
children: /* @__PURE__ */ jsx(ArrowUp, {})
|
|
1618
|
-
})
|
|
1696
|
+
children: sendStopButton
|
|
1619
1697
|
})]
|
|
1620
1698
|
});
|
|
1621
1699
|
}
|
|
1622
1700
|
|
|
1623
|
-
//#endregion
|
|
1624
|
-
//#region src/components/chatbot-panel-suggestion.tsx
|
|
1625
|
-
function ChatbotPanelSuggestion({ className, ...props }) {
|
|
1626
|
-
return /* @__PURE__ */ jsx(Button, {
|
|
1627
|
-
"data-slot": "chatbot-panel-suggestion",
|
|
1628
|
-
variant: "ghost",
|
|
1629
|
-
className: cn("h-auto max-w-[280px] text-left rounded-[18px] border border-primary px-3 py-1.5", "bg-background text-primary whitespace-normal", "hover:bg-[color-mix(in_srgb,#5BF1B2_20%,var(--background))] hover:text-primary", className),
|
|
1630
|
-
...props
|
|
1631
|
-
});
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
1701
|
//#endregion
|
|
1635
1702
|
//#region src/components/chatbot-user-message.tsx
|
|
1636
1703
|
function ChatbotUserMessage({ className, children, ...props }) {
|
|
@@ -2366,17 +2433,35 @@ function toFieldErrors(errors) {
|
|
|
2366
2433
|
|
|
2367
2434
|
//#endregion
|
|
2368
2435
|
//#region src/components/form-field.tsx
|
|
2369
|
-
function FormFieldShell({ label, description, necessityIndicator, children }) {
|
|
2436
|
+
function FormFieldShell({ label, description, necessityIndicator, id, children }) {
|
|
2370
2437
|
const errors = useFieldContext().state.meta.errors;
|
|
2438
|
+
const labelContent = typeof label === "string" ? label : label?.content;
|
|
2439
|
+
const labelInfo = typeof label === "object" ? label.info : void 0;
|
|
2440
|
+
const labelAside = typeof label === "object" ? label.aside : void 0;
|
|
2441
|
+
const hasAddons = labelInfo !== void 0 || labelAside !== void 0;
|
|
2442
|
+
const descriptionContent = typeof description === "string" ? description : description?.content;
|
|
2443
|
+
const descriptionVariant = (typeof description === "object" ? description.variant : void 0) ?? "instructional";
|
|
2444
|
+
const labelNode = labelContent !== void 0 && /* @__PURE__ */ jsx(FieldLabel, {
|
|
2445
|
+
necessityIndicator,
|
|
2446
|
+
htmlFor: id,
|
|
2447
|
+
children: labelContent
|
|
2448
|
+
});
|
|
2449
|
+
const labelRow = labelContent !== void 0 && (hasAddons ? /* @__PURE__ */ jsxs(FieldLabelRow, { children: [
|
|
2450
|
+
labelNode,
|
|
2451
|
+
labelInfo !== void 0 && /* @__PURE__ */ jsx(FieldLabelInfo, { children: labelInfo }),
|
|
2452
|
+
labelAside !== void 0 && /* @__PURE__ */ jsx(FieldLabelAside, { children: labelAside })
|
|
2453
|
+
] }) : labelNode);
|
|
2454
|
+
const descriptionNode = descriptionContent !== void 0 && /* @__PURE__ */ jsx(FieldDescription, {
|
|
2455
|
+
variant: descriptionVariant,
|
|
2456
|
+
children: descriptionContent
|
|
2457
|
+
});
|
|
2371
2458
|
return /* @__PURE__ */ jsxs(Field, {
|
|
2372
2459
|
"data-invalid": errors.length > 0 ? true : void 0,
|
|
2373
2460
|
children: [
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
children: label
|
|
2377
|
-
}),
|
|
2378
|
-
description && /* @__PURE__ */ jsx(FieldDescription, { children: description }),
|
|
2461
|
+
labelRow,
|
|
2462
|
+
descriptionVariant === "instructional" && descriptionNode,
|
|
2379
2463
|
children,
|
|
2464
|
+
descriptionVariant === "contextual" && descriptionNode,
|
|
2380
2465
|
/* @__PURE__ */ jsx(FieldError, { errors: toFieldErrors(errors) })
|
|
2381
2466
|
]
|
|
2382
2467
|
});
|
|
@@ -2388,6 +2473,7 @@ function FormFieldShell({ label, description, necessityIndicator, children }) {
|
|
|
2388
2473
|
*/
|
|
2389
2474
|
function FormFieldBase(props) {
|
|
2390
2475
|
const { form, name, validators, listeners, defaultValue, asyncDebounceMs, asyncAlways, label, description, necessityIndicator, buildControl, children } = props;
|
|
2476
|
+
const id = `${React.useId()}-${name}`;
|
|
2391
2477
|
const AppField = form.AppField;
|
|
2392
2478
|
return /* @__PURE__ */ jsx(AppField, {
|
|
2393
2479
|
name,
|
|
@@ -2400,20 +2486,22 @@ function FormFieldBase(props) {
|
|
|
2400
2486
|
label,
|
|
2401
2487
|
description,
|
|
2402
2488
|
necessityIndicator,
|
|
2403
|
-
|
|
2489
|
+
id,
|
|
2490
|
+
children: children(field, buildControl(field, id))
|
|
2404
2491
|
})
|
|
2405
2492
|
});
|
|
2406
2493
|
}
|
|
2407
2494
|
|
|
2408
2495
|
//#endregion
|
|
2409
2496
|
//#region src/components/form-select-field.tsx
|
|
2410
|
-
function selectControl(field) {
|
|
2497
|
+
function selectControl(field, id) {
|
|
2411
2498
|
return {
|
|
2412
2499
|
selectProps: {
|
|
2413
2500
|
value: field.state.value || null,
|
|
2414
2501
|
onValueChange: (value) => field.handleChange(value ?? "")
|
|
2415
2502
|
},
|
|
2416
2503
|
selectTriggerProps: {
|
|
2504
|
+
id,
|
|
2417
2505
|
onBlur: field.handleBlur,
|
|
2418
2506
|
"aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
|
|
2419
2507
|
}
|
|
@@ -2446,8 +2534,9 @@ function FormSelectField(props) {
|
|
|
2446
2534
|
|
|
2447
2535
|
//#endregion
|
|
2448
2536
|
//#region src/components/form-input-field.tsx
|
|
2449
|
-
function inputControl(field) {
|
|
2537
|
+
function inputControl(field, id) {
|
|
2450
2538
|
return { inputProps: {
|
|
2539
|
+
id,
|
|
2451
2540
|
value: field.state.value ?? "",
|
|
2452
2541
|
onChange: (event) => field.handleChange(event.target.value),
|
|
2453
2542
|
onBlur: field.handleBlur,
|
|
@@ -2473,13 +2562,14 @@ function FormInputField(props) {
|
|
|
2473
2562
|
|
|
2474
2563
|
//#endregion
|
|
2475
2564
|
//#region src/components/form-combobox-field.tsx
|
|
2476
|
-
function comboboxControl(field) {
|
|
2565
|
+
function comboboxControl(field, id) {
|
|
2477
2566
|
return {
|
|
2478
2567
|
comboboxProps: {
|
|
2479
2568
|
value: field.state.value || null,
|
|
2480
2569
|
onValueChange: (value) => field.handleChange(value ?? "")
|
|
2481
2570
|
},
|
|
2482
2571
|
comboboxInputProps: {
|
|
2572
|
+
id,
|
|
2483
2573
|
onBlur: field.handleBlur,
|
|
2484
2574
|
"aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
|
|
2485
2575
|
}
|
|
@@ -2513,13 +2603,14 @@ function FormComboboxField(props) {
|
|
|
2513
2603
|
|
|
2514
2604
|
//#endregion
|
|
2515
2605
|
//#region src/components/form-multi-combobox-field.tsx
|
|
2516
|
-
function multiComboboxControl(field) {
|
|
2606
|
+
function multiComboboxControl(field, id) {
|
|
2517
2607
|
return {
|
|
2518
2608
|
comboboxProps: {
|
|
2519
2609
|
value: field.state.value ?? [],
|
|
2520
2610
|
onValueChange: (values) => field.handleChange(values)
|
|
2521
2611
|
},
|
|
2522
2612
|
comboboxInputProps: {
|
|
2613
|
+
id,
|
|
2523
2614
|
onBlur: field.handleBlur,
|
|
2524
2615
|
"aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
|
|
2525
2616
|
}
|
|
@@ -2559,7 +2650,7 @@ function FormMultiComboboxField(props) {
|
|
|
2559
2650
|
* Each row is composed from the existing single-control wrappers with an indexed `name`.
|
|
2560
2651
|
*
|
|
2561
2652
|
* @example
|
|
2562
|
-
* <FormArrayField form={form} name="webhooks" label="Webhooks" description=
|
|
2653
|
+
* <FormArrayField form={form} name="webhooks" label="Webhooks" description={{ content: 'Endpoints to notify', variant: 'contextual' }}>
|
|
2563
2654
|
* {field => (
|
|
2564
2655
|
* <>
|
|
2565
2656
|
* {field.state.value.map((_row, i) => (
|
|
@@ -2577,6 +2668,7 @@ function FormMultiComboboxField(props) {
|
|
|
2577
2668
|
*/
|
|
2578
2669
|
function FormArrayField(props) {
|
|
2579
2670
|
const { form, name, validators, listeners, defaultValue, asyncDebounceMs, asyncAlways, label, description, necessityIndicator, children } = props;
|
|
2671
|
+
const id = `${React.useId()}-${name}`;
|
|
2580
2672
|
const AppField = form.AppField;
|
|
2581
2673
|
return /* @__PURE__ */ jsx(AppField, {
|
|
2582
2674
|
name,
|
|
@@ -2590,6 +2682,7 @@ function FormArrayField(props) {
|
|
|
2590
2682
|
label,
|
|
2591
2683
|
description,
|
|
2592
2684
|
necessityIndicator,
|
|
2685
|
+
id,
|
|
2593
2686
|
children: children(field)
|
|
2594
2687
|
})
|
|
2595
2688
|
});
|
|
@@ -2597,8 +2690,9 @@ function FormArrayField(props) {
|
|
|
2597
2690
|
|
|
2598
2691
|
//#endregion
|
|
2599
2692
|
//#region src/components/form-textarea-field.tsx
|
|
2600
|
-
function textareaControl(field) {
|
|
2693
|
+
function textareaControl(field, id) {
|
|
2601
2694
|
return { textareaProps: {
|
|
2695
|
+
id,
|
|
2602
2696
|
value: field.state.value ?? "",
|
|
2603
2697
|
onChange: (event) => field.handleChange(event.target.value),
|
|
2604
2698
|
onBlur: field.handleBlur,
|
|
@@ -2624,7 +2718,7 @@ function FormTextareaField(props) {
|
|
|
2624
2718
|
|
|
2625
2719
|
//#endregion
|
|
2626
2720
|
//#region src/components/form-radio-group-field.tsx
|
|
2627
|
-
function radioGroupControl(field) {
|
|
2721
|
+
function radioGroupControl(field, _id) {
|
|
2628
2722
|
return { radioGroupProps: {
|
|
2629
2723
|
value: field.state.value || null,
|
|
2630
2724
|
onValueChange: (value) => field.handleChange(value ?? ""),
|
|
@@ -2655,8 +2749,9 @@ function FormRadioGroupField(props) {
|
|
|
2655
2749
|
|
|
2656
2750
|
//#endregion
|
|
2657
2751
|
//#region src/components/form-otp-field.tsx
|
|
2658
|
-
function otpControl(field) {
|
|
2752
|
+
function otpControl(field, id) {
|
|
2659
2753
|
return { otpProps: {
|
|
2754
|
+
id,
|
|
2660
2755
|
value: field.state.value ?? "",
|
|
2661
2756
|
onChange: (value) => field.handleChange(value),
|
|
2662
2757
|
onBlur: field.handleBlur,
|
|
@@ -2682,8 +2777,9 @@ function FormOTPField(props) {
|
|
|
2682
2777
|
|
|
2683
2778
|
//#endregion
|
|
2684
2779
|
//#region src/components/form-switch-field.tsx
|
|
2685
|
-
function switchControl(field) {
|
|
2780
|
+
function switchControl(field, id) {
|
|
2686
2781
|
return { switchProps: {
|
|
2782
|
+
id,
|
|
2687
2783
|
checked: field.state.value,
|
|
2688
2784
|
onCheckedChange: (checked) => field.handleChange(checked),
|
|
2689
2785
|
onBlur: field.handleBlur,
|
|
@@ -2709,8 +2805,9 @@ function FormSwitchField(props) {
|
|
|
2709
2805
|
|
|
2710
2806
|
//#endregion
|
|
2711
2807
|
//#region src/components/form-numeric-field.tsx
|
|
2712
|
-
function numericControl(field) {
|
|
2808
|
+
function numericControl(field, id) {
|
|
2713
2809
|
return { numericProps: {
|
|
2810
|
+
id,
|
|
2714
2811
|
value: field.state.value,
|
|
2715
2812
|
onValueChange: (value) => field.handleChange(value ?? 0),
|
|
2716
2813
|
onBlur: field.handleBlur,
|
|
@@ -2736,10 +2833,11 @@ function FormNumericField(props) {
|
|
|
2736
2833
|
|
|
2737
2834
|
//#endregion
|
|
2738
2835
|
//#region src/components/form-slider-field.tsx
|
|
2739
|
-
function sliderControl(field) {
|
|
2836
|
+
function sliderControl(field, id) {
|
|
2740
2837
|
return { sliderProps: {
|
|
2838
|
+
id,
|
|
2741
2839
|
value: [field.state.value],
|
|
2742
|
-
onValueChange: (
|
|
2840
|
+
onValueChange: (value) => field.handleChange(Array.isArray(value) ? value[0] ?? 0 : value),
|
|
2743
2841
|
onBlur: field.handleBlur
|
|
2744
2842
|
} };
|
|
2745
2843
|
}
|
|
@@ -2762,7 +2860,7 @@ function FormSliderField(props) {
|
|
|
2762
2860
|
|
|
2763
2861
|
//#endregion
|
|
2764
2862
|
//#region src/components/form-toggle-group-field.tsx
|
|
2765
|
-
function toggleGroupControl(field) {
|
|
2863
|
+
function toggleGroupControl(field, _id) {
|
|
2766
2864
|
return { toggleGroupProps: {
|
|
2767
2865
|
value: field.state.value ? [field.state.value] : [],
|
|
2768
2866
|
onValueChange: (values) => field.handleChange(values[values.length - 1] ?? null),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aircall/blocks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"private": false,
|
|
6
6
|
"sideEffects": [
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"vite": "7.3.1",
|
|
82
82
|
"vitest": "4.0.17",
|
|
83
83
|
"zod": "4.4.3",
|
|
84
|
-
"@aircall/ds": "0.
|
|
84
|
+
"@aircall/ds": "0.19.0",
|
|
85
85
|
"@aircall/hooks": "0.5.1"
|
|
86
86
|
},
|
|
87
87
|
"keywords": [
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"build:js": "tsdown --config ./tsdown.config.ts",
|
|
98
98
|
"build:css": "tailwindcss -i ./src/styles/globals.css -o ./dist/globals.css --minify",
|
|
99
99
|
"build:package": "pnpm run build:js && pnpm run build:css",
|
|
100
|
-
"typecheck": "tsc --noEmit",
|
|
100
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.strict.json",
|
|
101
101
|
"lint": "eslint . --max-warnings 0",
|
|
102
102
|
"sb:dev": "storybook dev -p 6009",
|
|
103
103
|
"sb:build": "storybook build",
|
|
@@ -169,15 +169,34 @@ cross-step validation.
|
|
|
169
169
|
|
|
170
170
|
| `FormFieldProps` | After |
|
|
171
171
|
|---|---|
|
|
172
|
-
| `name`
|
|
172
|
+
| `name` | `name` on the `Form*Field` |
|
|
173
|
+
| `label` | `label` — a string, or `{ content, aside?, info? }` (see below) |
|
|
174
|
+
| helper/description text | `description` — a string, or `{ content, variant? }` (see below) |
|
|
173
175
|
| `validate` | `validators.onChange` / `onSubmit` on the `Form*Field` |
|
|
174
176
|
| `defaultValue` | `defaultValues[name]` in `useForm` |
|
|
175
177
|
| `getErrorMessage(e)` | return the translated string from the validator |
|
|
176
178
|
|
|
179
|
+
**`label`** — a string is the label text. The object form adds label-row affordances:
|
|
180
|
+
- `label={{ content, info }}` — `info` is an `(i)` icon opening a popover of purely additive context
|
|
181
|
+
(hide it when ~90% of users don't need it).
|
|
182
|
+
- `label={{ content, aside }}` — `aside` is free-form content pinned to the **right** of the row (a
|
|
183
|
+
"Where do I find this?" link, a popover, plain text, …). You pass the node — an `Anchor`, a
|
|
184
|
+
`Popover`, whatever — and blocks wraps it in `FieldLabelAside` for alignment + typography, forcing
|
|
185
|
+
no styling of its own.
|
|
186
|
+
|
|
187
|
+
**`description`** — a string renders as the **`instructional`** variant (under the label, always
|
|
188
|
+
visible — the default). Pass `{ content, variant: 'contextual' }` to move it **below the control**,
|
|
189
|
+
where it is **hidden when the field is invalid** (the error takes its slot). Never mix both kinds on
|
|
190
|
+
one field.
|
|
191
|
+
|
|
192
|
+
**`necessityIndicator="required" | "optional"`** — mark only the exceptions (the few optional fields
|
|
193
|
+
in a mostly-required form, or vice versa); never mix both markers in one form.
|
|
194
|
+
|
|
177
195
|
## Fields reference
|
|
178
196
|
|
|
179
197
|
`@aircall/blocks` ships typed wrappers for all common DS controls; each takes `form`,
|
|
180
|
-
`name`, `label`, optional `validators`,
|
|
198
|
+
`name`, `label`, optional `validators`, optional `description` / `necessityIndicator`
|
|
199
|
+
(see Prop mapping above), and a render-prop `(field, control) => …` —
|
|
181
200
|
spread the control bundle onto the matching DS primitive.
|
|
182
201
|
|
|
183
202
|
| Input | Block | Input | Block |
|
|
@@ -5,7 +5,8 @@ description: >
|
|
|
5
5
|
Load when wiring blocks compositions (DashboardPage, DashboardPageHeader,
|
|
6
6
|
DashboardSidebar, empty states, form fields) into a project: installing the
|
|
7
7
|
package, importing its precompiled globals.css after the DS bundle, and the DS
|
|
8
|
-
providers it relies on.
|
|
8
|
+
providers it relies on. Covers both standalone apps and module-federation
|
|
9
|
+
consumers where the host already loads DS globals.
|
|
9
10
|
type: core
|
|
10
11
|
library: aircall-blocks
|
|
11
12
|
library_version: "0.5.1"
|
|
@@ -29,8 +30,27 @@ Install blocks alongside DS:
|
|
|
29
30
|
pnpm add @aircall/blocks @aircall/ds @aircall/react-icons
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
How you import the CSS depends on whether your app is standalone or a
|
|
34
|
+
**module-federation consumer** whose host already loads DS globals.
|
|
35
|
+
|
|
36
|
+
### Standalone app (owns its own Preflight)
|
|
37
|
+
|
|
38
|
+
`@aircall/blocks/globals.css` is a **delta build** — it contains only blocks-specific tokens
|
|
39
|
+
and utility classes; it does NOT bundle DS. Import DS globals first (Preflight + DS tokens +
|
|
40
|
+
DS utilities), then blocks globals (blocks-specific delta). Always import in this order.
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
/* style.css */
|
|
44
|
+
@layer theme, base, components, utilities;
|
|
45
|
+
@import 'tailwindcss/theme.css' layer(theme);
|
|
46
|
+
@import 'tailwindcss/utilities.css' layer(utilities);
|
|
47
|
+
@import '@aircall/ds/globals.css';
|
|
48
|
+
@import '@aircall/blocks/globals.css';
|
|
49
|
+
|
|
50
|
+
@source './src/**/*.{ts,tsx}';
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or from a JS/TS entry (no custom Tailwind classes of your own):
|
|
34
54
|
|
|
35
55
|
```tsx
|
|
36
56
|
// main.tsx
|
|
@@ -38,6 +58,28 @@ import '@aircall/ds/globals.css';
|
|
|
38
58
|
import '@aircall/blocks/globals.css';
|
|
39
59
|
```
|
|
40
60
|
|
|
61
|
+
### Module-federation consumer (host already owns the Preflight)
|
|
62
|
+
|
|
63
|
+
When your app is a remote loaded inside a host that already imports DS globals
|
|
64
|
+
(e.g. `dashboard-v4`), **do not re-import DS or blocks globals** in your own
|
|
65
|
+
CSS. The host's single Preflight applies to the whole document — re-importing
|
|
66
|
+
it duplicates the base reset and causes cascade conflicts (see Common Mistakes
|
|
67
|
+
below).
|
|
68
|
+
|
|
69
|
+
Import only the Tailwind layers you need for your own authored utility classes:
|
|
70
|
+
|
|
71
|
+
```css
|
|
72
|
+
/* style.css */
|
|
73
|
+
@layer theme, base, components, utilities;
|
|
74
|
+
@import 'tailwindcss/theme.css' layer(theme);
|
|
75
|
+
@import 'tailwindcss/utilities.css' layer(utilities);
|
|
76
|
+
|
|
77
|
+
@source './**/*.{ts,tsx}';
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
> **Note on the `.css` extension**: `@import "tailwindcss/theme"` (no `.css`)
|
|
81
|
+
> silently fails in webpack/Rsbuild PostCSS pipelines — the extension is required.
|
|
82
|
+
|
|
41
83
|
Blocks render under the same DS providers — there is no blocks-specific provider; mount the
|
|
42
84
|
DS root providers from `aircall-ds/setup` as needed (`ThemeProvider` / `TooltipProvider` /
|
|
43
85
|
`Toaster`). Two DS providers matter specifically once you use blocks:
|
|
@@ -78,26 +120,82 @@ function CampaignsHeader() {
|
|
|
78
120
|
|
|
79
121
|
## Common Mistakes
|
|
80
122
|
|
|
123
|
+
### HIGH — Re-importing DS/blocks globals in a module-federation consumer
|
|
124
|
+
|
|
125
|
+
Wrong — in a consumer whose host already loads DS globals:
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
@import 'tailwindcss';
|
|
129
|
+
@import '@aircall/ds/globals.css';
|
|
130
|
+
@import '@aircall/blocks/globals.css';
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Correct — consumer owns only its own utilities, no Preflight:
|
|
134
|
+
|
|
135
|
+
```css
|
|
136
|
+
@layer theme, base, components, utilities;
|
|
137
|
+
@import 'tailwindcss/theme.css' layer(theme);
|
|
138
|
+
@import 'tailwindcss/utilities.css' layer(utilities);
|
|
139
|
+
@source './**/*.{ts,tsx}';
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
In a module-federation setup, CSS from each remote lands in the **same document**
|
|
143
|
+
as the host. DS globals ships a Preflight (`@layer base { * { border-color: var(--color-border); } }`)
|
|
144
|
+
that sets the token-based border color globally. If the consumer also imports DS globals
|
|
145
|
+
(or the full `@import 'tailwindcss'`), it emits a second `@layer base` reset. Because
|
|
146
|
+
webpack/Rsbuild emits deep dependencies first and the consumer's own CSS last, the
|
|
147
|
+
consumer's Preflight (`border-color: currentColor`) ends up later in the output — and
|
|
148
|
+
later wins. DS component borders (Card, etc.) lose their token color and become invisible.
|
|
149
|
+
|
|
81
150
|
### HIGH — Importing blocks globals before DS globals (or omitting DS globals)
|
|
82
151
|
|
|
83
|
-
Wrong:
|
|
152
|
+
Wrong — wrong order, or omitting DS globals:
|
|
84
153
|
|
|
85
|
-
```
|
|
86
|
-
import '@aircall/blocks/globals.css';
|
|
87
|
-
import '@aircall/ds/globals.css';
|
|
154
|
+
```css
|
|
155
|
+
@import '@aircall/blocks/globals.css';
|
|
156
|
+
@import '@aircall/ds/globals.css';
|
|
88
157
|
```
|
|
89
158
|
|
|
90
|
-
|
|
159
|
+
```css
|
|
160
|
+
/* missing DS globals entirely */
|
|
161
|
+
@import '@aircall/blocks/globals.css';
|
|
162
|
+
```
|
|
91
163
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
Correct — DS globals first, then blocks globals:
|
|
165
|
+
|
|
166
|
+
```css
|
|
167
|
+
@import '@aircall/ds/globals.css';
|
|
168
|
+
@import '@aircall/blocks/globals.css';
|
|
95
169
|
```
|
|
96
170
|
|
|
97
|
-
|
|
171
|
+
`@aircall/blocks/globals.css` is a delta build — it contains only blocks-specific tokens
|
|
172
|
+
and utility classes. It depends on DS globals being loaded first to provide Preflight and
|
|
173
|
+
DS token definitions. Loading blocks before DS means DS's base reset (`border-color:
|
|
174
|
+
var(--color-border)`) lands after blocks and within the same `@layer base`, and component
|
|
175
|
+
tokens like `--background` and `--border` may not be defined when blocks tries to use them.
|
|
98
176
|
|
|
99
177
|
Source: aircall/hydra:docs/migration-guides/tractor-to-ds/00-setup.md (§3)
|
|
100
178
|
|
|
179
|
+
### MEDIUM — Using bare specifiers without `.css` in Rsbuild/webpack
|
|
180
|
+
|
|
181
|
+
Wrong:
|
|
182
|
+
|
|
183
|
+
```css
|
|
184
|
+
@import 'tailwindcss/theme';
|
|
185
|
+
@import 'tailwindcss/utilities';
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Correct:
|
|
189
|
+
|
|
190
|
+
```css
|
|
191
|
+
@import 'tailwindcss/theme.css';
|
|
192
|
+
@import 'tailwindcss/utilities.css';
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The bare form (no `.css`) silently produces no output in webpack/Rsbuild PostCSS
|
|
196
|
+
pipelines — classes like `flex`, `gap-4`, `text-sm` are never generated. Always
|
|
197
|
+
use the `.css` extension when splitting Tailwind imports.
|
|
198
|
+
|
|
101
199
|
### MEDIUM — Installing @aircall/blocks without @aircall/ds
|
|
102
200
|
|
|
103
201
|
Wrong:
|