@aircall/blocks 0.12.0 → 0.14.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/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, 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";
1
+ import { Anchor, Badge, Button, CounterBadge, DropdownMenu, DropdownMenuAddon, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, Field, FieldContent, 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 items-end gap-2 overflow-clip rounded-3xl bg-background py-2 pr-2 pl-6", "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),
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__ */ jsx("div", {
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: loading ? /* @__PURE__ */ jsx(InputGroupButton, {
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 }) {
@@ -1667,17 +1734,27 @@ const DashboardStandalonePage = React.forwardRef((componentProps, forwardRef) =>
1667
1734
  return /* @__PURE__ */ jsx("div", {
1668
1735
  ref: forwardRef,
1669
1736
  "data-slot": "dashboard-standalone-page",
1670
- className: cn("flex h-screen flex-col bg-neutral-200 dark:bg-neutral-950", className),
1737
+ className: cn("flex h-screen gap-2 overflow-hidden bg-page-background p-2", className),
1671
1738
  ...props
1672
1739
  });
1673
1740
  });
1674
1741
  DashboardStandalonePage.displayName = "DashboardStandalonePage";
1742
+ const DashboardStandalonePageContent = React.forwardRef((componentProps, forwardRef) => {
1743
+ const { className, ...props } = componentProps;
1744
+ return /* @__PURE__ */ jsx("div", {
1745
+ ref: forwardRef,
1746
+ "data-slot": "dashboard-standalone-page-content",
1747
+ className: cn("flex min-w-0 flex-1 flex-col overflow-hidden rounded-xl bg-page-surface border border-border", className),
1748
+ ...props
1749
+ });
1750
+ });
1751
+ DashboardStandalonePageContent.displayName = "DashboardStandalonePageContent";
1675
1752
  const DashboardStandalonePageHeader = React.forwardRef((componentProps, forwardRef) => {
1676
1753
  const { className, ...props } = componentProps;
1677
1754
  return /* @__PURE__ */ jsx("header", {
1678
1755
  ref: forwardRef,
1679
1756
  "data-slot": "dashboard-standalone-page-header",
1680
- className: cn("grid h-16 shrink-0 grid-cols-[1fr_auto_1fr] items-center gap-4 border-b border-border bg-background px-4 md:px-6", className),
1757
+ className: cn("relative flex h-16 shrink-0 flex-col items-center justify-center gap-0.5 border-b border-border bg-background px-16 text-center", className),
1681
1758
  ...props
1682
1759
  });
1683
1760
  });
@@ -1687,18 +1764,28 @@ const DashboardStandalonePageTitle = React.forwardRef((componentProps, forwardRe
1687
1764
  return /* @__PURE__ */ jsx("h1", {
1688
1765
  ref: forwardRef,
1689
1766
  "data-slot": "dashboard-standalone-page-title",
1690
- className: cn("col-start-2 row-start-1 truncate text-center text-base font-bold text-foreground", className),
1767
+ className: cn("max-w-xl truncate text-center text-xl leading-tight font-bold text-foreground", className),
1691
1768
  ...props
1692
1769
  });
1693
1770
  });
1694
1771
  DashboardStandalonePageTitle.displayName = "DashboardStandalonePageTitle";
1772
+ const DashboardStandalonePageDescription = React.forwardRef((componentProps, forwardRef) => {
1773
+ const { className, ...props } = componentProps;
1774
+ return /* @__PURE__ */ jsx("p", {
1775
+ ref: forwardRef,
1776
+ "data-slot": "dashboard-standalone-page-description",
1777
+ className: cn("max-w-xl truncate text-center text-sm leading-tight font-medium text-muted-foreground", className),
1778
+ ...props
1779
+ });
1780
+ });
1781
+ DashboardStandalonePageDescription.displayName = "DashboardStandalonePageDescription";
1695
1782
  const DashboardStandalonePageActions = React.forwardRef((componentProps, forwardRef) => {
1696
1783
  const { side = "end", className, ...props } = componentProps;
1697
1784
  return /* @__PURE__ */ jsx("div", {
1698
1785
  ref: forwardRef,
1699
1786
  "data-slot": "dashboard-standalone-page-actions",
1700
1787
  "data-side": side,
1701
- className: cn("row-start-1 flex min-w-0 items-center gap-2", side === "start" ? "col-start-1 justify-start" : "col-start-3 justify-end", className),
1788
+ className: cn("absolute inset-y-0 flex items-center gap-2", side === "start" ? "left-4" : "right-4", className),
1702
1789
  ...props
1703
1790
  });
1704
1791
  });
@@ -1713,16 +1800,16 @@ const DashboardStandalonePageAction = React.forwardRef((componentProps, forwardR
1713
1800
  });
1714
1801
  });
1715
1802
  DashboardStandalonePageAction.displayName = "DashboardStandalonePageAction";
1716
- const DashboardStandalonePageContent = React.forwardRef((componentProps, forwardRef) => {
1803
+ const DashboardStandalonePageMain = React.forwardRef((componentProps, forwardRef) => {
1717
1804
  const { className, ...props } = componentProps;
1718
1805
  return /* @__PURE__ */ jsx("main", {
1719
1806
  ref: forwardRef,
1720
- "data-slot": "dashboard-standalone-page-content",
1807
+ "data-slot": "dashboard-standalone-page-main",
1721
1808
  className: cn("min-h-0 flex-1 overflow-auto p-4 md:p-6", className),
1722
1809
  ...props
1723
1810
  });
1724
1811
  });
1725
- DashboardStandalonePageContent.displayName = "DashboardStandalonePageContent";
1812
+ DashboardStandalonePageMain.displayName = "DashboardStandalonePageMain";
1726
1813
 
1727
1814
  //#endregion
1728
1815
  //#region src/components/dashboard-page.tsx
@@ -2366,7 +2453,7 @@ function toFieldErrors(errors) {
2366
2453
 
2367
2454
  //#endregion
2368
2455
  //#region src/components/form-field.tsx
2369
- function FormFieldShell({ label, description, necessityIndicator, children }) {
2456
+ function FormFieldShell({ label, description, necessityIndicator, id, children, orientation = "vertical", controlPosition = "end", controlVariant = "fill" }) {
2370
2457
  const errors = useFieldContext().state.meta.errors;
2371
2458
  const labelContent = typeof label === "string" ? label : label?.content;
2372
2459
  const labelInfo = typeof label === "object" ? label.info : void 0;
@@ -2376,6 +2463,7 @@ function FormFieldShell({ label, description, necessityIndicator, children }) {
2376
2463
  const descriptionVariant = (typeof description === "object" ? description.variant : void 0) ?? "instructional";
2377
2464
  const labelNode = labelContent !== void 0 && /* @__PURE__ */ jsx(FieldLabel, {
2378
2465
  necessityIndicator,
2466
+ htmlFor: id,
2379
2467
  children: labelContent
2380
2468
  });
2381
2469
  const labelRow = labelContent !== void 0 && (hasAddons ? /* @__PURE__ */ jsxs(FieldLabelRow, { children: [
@@ -2387,16 +2475,44 @@ function FormFieldShell({ label, description, necessityIndicator, children }) {
2387
2475
  variant: descriptionVariant,
2388
2476
  children: descriptionContent
2389
2477
  });
2390
- return /* @__PURE__ */ jsxs(Field, {
2391
- "data-invalid": errors.length > 0 ? true : void 0,
2478
+ const invalid = errors.length > 0 ? true : void 0;
2479
+ const instructionalDesc = descriptionVariant === "instructional" ? descriptionNode : null;
2480
+ const contextualDesc = descriptionVariant === "contextual" ? descriptionNode : null;
2481
+ const errorNode = /* @__PURE__ */ jsx(FieldError, { errors: toFieldErrors(errors) });
2482
+ if (orientation === "vertical") return /* @__PURE__ */ jsxs(Field, {
2483
+ "data-invalid": invalid,
2392
2484
  children: [
2393
2485
  labelRow,
2394
- descriptionVariant === "instructional" && descriptionNode,
2486
+ instructionalDesc,
2395
2487
  children,
2396
- descriptionVariant === "contextual" && descriptionNode,
2397
- /* @__PURE__ */ jsx(FieldError, { errors: toFieldErrors(errors) })
2488
+ contextualDesc,
2489
+ errorNode
2398
2490
  ]
2399
2491
  });
2492
+ if (controlVariant === "inline") {
2493
+ const textSide = descriptionContent !== void 0 || errors.length > 0 ? /* @__PURE__ */ jsxs(FieldContent, { children: [
2494
+ labelRow,
2495
+ instructionalDesc,
2496
+ contextualDesc,
2497
+ errorNode
2498
+ ] }) : labelRow;
2499
+ return /* @__PURE__ */ jsxs(Field, {
2500
+ orientation,
2501
+ "data-invalid": invalid,
2502
+ children: [controlPosition === "start" ? children : textSide, controlPosition === "start" ? textSide : children]
2503
+ });
2504
+ }
2505
+ const labelCol = /* @__PURE__ */ jsxs(FieldContent, { children: [labelRow, instructionalDesc] });
2506
+ const controlCol = /* @__PURE__ */ jsxs(FieldContent, { children: [
2507
+ children,
2508
+ contextualDesc,
2509
+ errorNode
2510
+ ] });
2511
+ return /* @__PURE__ */ jsxs(Field, {
2512
+ orientation,
2513
+ "data-invalid": invalid,
2514
+ children: [controlPosition === "start" ? controlCol : labelCol, controlPosition === "start" ? labelCol : controlCol]
2515
+ });
2400
2516
  }
2401
2517
  /**
2402
2518
  * Shared runtime binder for the per-primitive wrappers (FormSelectField, FormInputField, …).
@@ -2404,7 +2520,8 @@ function FormFieldShell({ label, description, necessityIndicator, children }) {
2404
2520
  * the AppField + shared shell. `TData`/`TControl` carry the precise field + bundle shapes through.
2405
2521
  */
2406
2522
  function FormFieldBase(props) {
2407
- const { form, name, validators, listeners, defaultValue, asyncDebounceMs, asyncAlways, label, description, necessityIndicator, buildControl, children } = props;
2523
+ const { form, name, validators, listeners, defaultValue, asyncDebounceMs, asyncAlways, label, description, necessityIndicator, orientation, controlPosition, controlVariant, buildControl, children } = props;
2524
+ const id = `${React.useId()}-${name}`;
2408
2525
  const AppField = form.AppField;
2409
2526
  return /* @__PURE__ */ jsx(AppField, {
2410
2527
  name,
@@ -2417,20 +2534,25 @@ function FormFieldBase(props) {
2417
2534
  label,
2418
2535
  description,
2419
2536
  necessityIndicator,
2420
- children: children(field, buildControl(field))
2537
+ orientation,
2538
+ controlPosition,
2539
+ controlVariant,
2540
+ id,
2541
+ children: children(field, buildControl(field, id))
2421
2542
  })
2422
2543
  });
2423
2544
  }
2424
2545
 
2425
2546
  //#endregion
2426
2547
  //#region src/components/form-select-field.tsx
2427
- function selectControl(field) {
2548
+ function selectControl(field, id) {
2428
2549
  return {
2429
2550
  selectProps: {
2430
2551
  value: field.state.value || null,
2431
2552
  onValueChange: (value) => field.handleChange(value ?? "")
2432
2553
  },
2433
2554
  selectTriggerProps: {
2555
+ id,
2434
2556
  onBlur: field.handleBlur,
2435
2557
  "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
2436
2558
  }
@@ -2463,8 +2585,9 @@ function FormSelectField(props) {
2463
2585
 
2464
2586
  //#endregion
2465
2587
  //#region src/components/form-input-field.tsx
2466
- function inputControl(field) {
2588
+ function inputControl(field, id) {
2467
2589
  return { inputProps: {
2590
+ id,
2468
2591
  value: field.state.value ?? "",
2469
2592
  onChange: (event) => field.handleChange(event.target.value),
2470
2593
  onBlur: field.handleBlur,
@@ -2490,13 +2613,14 @@ function FormInputField(props) {
2490
2613
 
2491
2614
  //#endregion
2492
2615
  //#region src/components/form-combobox-field.tsx
2493
- function comboboxControl(field) {
2616
+ function comboboxControl(field, id) {
2494
2617
  return {
2495
2618
  comboboxProps: {
2496
2619
  value: field.state.value || null,
2497
2620
  onValueChange: (value) => field.handleChange(value ?? "")
2498
2621
  },
2499
2622
  comboboxInputProps: {
2623
+ id,
2500
2624
  onBlur: field.handleBlur,
2501
2625
  "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
2502
2626
  }
@@ -2530,13 +2654,14 @@ function FormComboboxField(props) {
2530
2654
 
2531
2655
  //#endregion
2532
2656
  //#region src/components/form-multi-combobox-field.tsx
2533
- function multiComboboxControl(field) {
2657
+ function multiComboboxControl(field, id) {
2534
2658
  return {
2535
2659
  comboboxProps: {
2536
2660
  value: field.state.value ?? [],
2537
2661
  onValueChange: (values) => field.handleChange(values)
2538
2662
  },
2539
2663
  comboboxInputProps: {
2664
+ id,
2540
2665
  onBlur: field.handleBlur,
2541
2666
  "aria-invalid": field.state.meta.errors.length > 0 ? true : void 0
2542
2667
  }
@@ -2594,6 +2719,7 @@ function FormMultiComboboxField(props) {
2594
2719
  */
2595
2720
  function FormArrayField(props) {
2596
2721
  const { form, name, validators, listeners, defaultValue, asyncDebounceMs, asyncAlways, label, description, necessityIndicator, children } = props;
2722
+ const id = `${React.useId()}-${name}`;
2597
2723
  const AppField = form.AppField;
2598
2724
  return /* @__PURE__ */ jsx(AppField, {
2599
2725
  name,
@@ -2607,6 +2733,7 @@ function FormArrayField(props) {
2607
2733
  label,
2608
2734
  description,
2609
2735
  necessityIndicator,
2736
+ id,
2610
2737
  children: children(field)
2611
2738
  })
2612
2739
  });
@@ -2614,8 +2741,9 @@ function FormArrayField(props) {
2614
2741
 
2615
2742
  //#endregion
2616
2743
  //#region src/components/form-textarea-field.tsx
2617
- function textareaControl(field) {
2744
+ function textareaControl(field, id) {
2618
2745
  return { textareaProps: {
2746
+ id,
2619
2747
  value: field.state.value ?? "",
2620
2748
  onChange: (event) => field.handleChange(event.target.value),
2621
2749
  onBlur: field.handleBlur,
@@ -2641,7 +2769,7 @@ function FormTextareaField(props) {
2641
2769
 
2642
2770
  //#endregion
2643
2771
  //#region src/components/form-radio-group-field.tsx
2644
- function radioGroupControl(field) {
2772
+ function radioGroupControl(field, _id) {
2645
2773
  return { radioGroupProps: {
2646
2774
  value: field.state.value || null,
2647
2775
  onValueChange: (value) => field.handleChange(value ?? ""),
@@ -2672,8 +2800,9 @@ function FormRadioGroupField(props) {
2672
2800
 
2673
2801
  //#endregion
2674
2802
  //#region src/components/form-otp-field.tsx
2675
- function otpControl(field) {
2803
+ function otpControl(field, id) {
2676
2804
  return { otpProps: {
2805
+ id,
2677
2806
  value: field.state.value ?? "",
2678
2807
  onChange: (value) => field.handleChange(value),
2679
2808
  onBlur: field.handleBlur,
@@ -2699,8 +2828,9 @@ function FormOTPField(props) {
2699
2828
 
2700
2829
  //#endregion
2701
2830
  //#region src/components/form-switch-field.tsx
2702
- function switchControl(field) {
2831
+ function switchControl(field, id) {
2703
2832
  return { switchProps: {
2833
+ id,
2704
2834
  checked: field.state.value,
2705
2835
  onCheckedChange: (checked) => field.handleChange(checked),
2706
2836
  onBlur: field.handleBlur,
@@ -2712,22 +2842,30 @@ function switchControl(field) {
2712
2842
  * with a `switchProps` bundle. `name` is restricted to the form's **boolean** fields — a
2713
2843
  * string/number `name` is a type error.
2714
2844
  *
2845
+ * In a horizontal/responsive layout the switch is a bare, natural-width control beside its label
2846
+ * (`controlPosition` picks the side — defaults to `'start'`, i.e. switch on the left).
2847
+ *
2715
2848
  * @example
2716
2849
  * <FormSwitchField form={form} name="enabled" label="Enabled" validators={{ onChange }}>
2717
2850
  * {(field, { switchProps }) => <Switch {...switchProps} />}
2718
2851
  * </FormSwitchField>
2719
2852
  */
2720
2853
  function FormSwitchField(props) {
2854
+ const { orientation = "horizontal", controlPosition = "start", ...rest } = props;
2721
2855
  return /* @__PURE__ */ jsx(FormFieldBase, {
2722
- ...props,
2856
+ ...rest,
2857
+ orientation,
2858
+ controlPosition,
2859
+ controlVariant: "inline",
2723
2860
  buildControl: switchControl
2724
2861
  });
2725
2862
  }
2726
2863
 
2727
2864
  //#endregion
2728
2865
  //#region src/components/form-numeric-field.tsx
2729
- function numericControl(field) {
2866
+ function numericControl(field, id) {
2730
2867
  return { numericProps: {
2868
+ id,
2731
2869
  value: field.state.value,
2732
2870
  onValueChange: (value) => field.handleChange(value ?? 0),
2733
2871
  onBlur: field.handleBlur,
@@ -2753,10 +2891,11 @@ function FormNumericField(props) {
2753
2891
 
2754
2892
  //#endregion
2755
2893
  //#region src/components/form-slider-field.tsx
2756
- function sliderControl(field) {
2894
+ function sliderControl(field, id) {
2757
2895
  return { sliderProps: {
2896
+ id,
2758
2897
  value: [field.state.value],
2759
- onValueChange: (values) => field.handleChange(values[0] ?? 0),
2898
+ onValueChange: (value) => field.handleChange(Array.isArray(value) ? value[0] ?? 0 : value),
2760
2899
  onBlur: field.handleBlur
2761
2900
  } };
2762
2901
  }
@@ -2779,7 +2918,7 @@ function FormSliderField(props) {
2779
2918
 
2780
2919
  //#endregion
2781
2920
  //#region src/components/form-toggle-group-field.tsx
2782
- function toggleGroupControl(field) {
2921
+ function toggleGroupControl(field, _id) {
2783
2922
  return { toggleGroupProps: {
2784
2923
  value: field.state.value ? [field.state.value] : [],
2785
2924
  onValueChange: (values) => field.handleChange(values[values.length - 1] ?? null),
@@ -3141,4 +3280,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
3141
3280
  }
3142
3281
 
3143
3282
  //#endregion
3144
- export { BetaBadge, CardSaveBar, ChatbotExpandSuggestion, ChatbotInput, ChatbotPage, ChatbotPageBody, ChatbotPageContent, ChatbotPageConversation, ChatbotPageFooter, ChatbotPageHeading, ChatbotPageInputBar, ChatbotPanel, ChatbotPanelHeader, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotResponseBlock, ChatbotResponseLoading, 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, FormArrayField, FormComboboxField, FormFieldBase, FormFieldShell, FormInputField, FormMultiComboboxField, FormNumericField, 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, withFieldGroup, withForm };
3283
+ export { BetaBadge, CardSaveBar, ChatbotExpandSuggestion, ChatbotInput, ChatbotPage, ChatbotPageBody, ChatbotPageContent, ChatbotPageConversation, ChatbotPageFooter, ChatbotPageHeading, ChatbotPageInputBar, ChatbotPanel, ChatbotPanelHeader, ChatbotPanelSuggestion, ChatbotPanelTrigger, ChatbotResponseBlock, ChatbotResponseLoading, 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, DashboardStandalonePageDescription, DashboardStandalonePageHeader, DashboardStandalonePageMain, DashboardStandalonePageTitle, DeprecatedBadge, EmptyState, EmptyStateActions, EmptyStateButton, EmptyStateDescription, EmptyStateExternalLink, EmptyStateHeader, EmptyStateMedia, EmptyStateTitle, FormArrayField, FormComboboxField, FormFieldBase, FormFieldShell, FormInputField, FormMultiComboboxField, FormNumericField, 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, withFieldGroup, withForm };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aircall/blocks",
3
- "version": "0.12.0",
3
+ "version": "0.14.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.18.0",
84
+ "@aircall/ds": "0.20.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",
@@ -80,7 +80,7 @@ utils/data home or must stay in the consuming app.
80
80
 
81
81
  **UI with no blocks/ds equivalent yet**: some `@dashboard/library` UI components have
82
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
83
+ `TileLegend`). If a UI component is not in the routing table below and
84
84
  not listed above, leave it imported from `@dashboard/library` for now — do not force a
85
85
  migration or invent a target.
86
86
 
@@ -100,6 +100,7 @@ migration or invent a target.
100
100
  | `List` + `ListItem` | `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemActions` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/list | available |
101
101
  | `Tile` + `TileHeader` + `TileValue` | `Card` (+ parts) as the tile container (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/tile | available |
102
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
+ | `AudioPlayer` | `Audio`, `AudioPlayerRow`, `AudioPlayerButton`, `AudioPlayerBar`, `AudioPlayerTime`, `AudioPlayerSpeed`, `AudioPlayerSkip`, `AudioPlayerManager`, `useAudioPlayer` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-audio-player | available |
103
104
  | `InfoPopup` + `InfoPopupTrigger` + `InfoPopupContent` | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/info-popup | available |
104
105
  | `CopyToClipboardButton` + `CopyToClipboardText` | `CopyButton`, `CopyButtonIcon`, `CopyButtonLabel` (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/copy-button | available |
105
106
  | `ToggleRow` | `Field` (orientation="horizontal") + `Switch` + `FieldLabel` + optional `FieldDescription` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/toggle-row | available |