@aircall/blocks 0.18.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
- import { Anchor, Badge, Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle, 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, useDsLocale, useDsTranslation, useSidebar } from "@aircall/ds";
1
+ import { Anchor, Badge, Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle, 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, Skeleton, Spinner, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, TabsList, ToggleGroup, ToggleGroupItem, Tooltip, TooltipContent, TooltipTrigger, cn, registerI18nNamespace, useDsLocale, 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, Frown, History, LockKeyhole, Meh, Menu, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Smile, Sprout, Square, SquareArrowOutUpRight, ThumbsDown, ThumbsDownFilled, ThumbsUp, ThumbsUpFilled, TriangleAlert } from "@aircall/react-icons";
5
+ import { AircallLoader, AircallLogo, AircallMark, ArrowDownRight, ArrowUp, ArrowUpRight, Check, ChevronDown, ChevronLeft, ChevronRight, Copy, Ellipsis, ExternalLink, FileText, Frown, History, LockKeyhole, Meh, Menu, MessageSquare, Minus, PanelLeft, PanelLeftClose, PanelLeftOpen, Plus, Search, Settings, Smile, 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";
9
9
  import { cva } from "class-variance-authority";
10
- import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
11
10
  import { mergeProps } from "@base-ui/react/merge-props";
12
11
  import { useRender } from "@base-ui/react/use-render";
12
+ import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
13
13
 
14
14
  //#region src/i18n/locales/de.ts
15
15
  const de = {
@@ -2523,6 +2523,380 @@ const ScoreBadge = React.forwardRef((componentProps, forwardRef) => {
2523
2523
  });
2524
2524
  ScoreBadge.displayName = "ScoreBadge";
2525
2525
 
2526
+ //#endregion
2527
+ //#region src/helpers/format-kpi.ts
2528
+ /** Empty KPI placeholder matching Figma empty tiles and ScoreBadge. */
2529
+ const KPI_EMPTY = "--";
2530
+ /** Default secondary unit for clock durations. */
2531
+ function clockDurationUnit(scale) {
2532
+ return scale === "hours" ? "hr/min" : "min/sec";
2533
+ }
2534
+ /**
2535
+ * Locale decimal separator (e.g. `.` for `en-US`, `,` for `fr-FR`).
2536
+ */
2537
+ function getDecimalSeparator(locale) {
2538
+ return new Intl.NumberFormat(locale).formatToParts(1.1).find((p) => p.type === "decimal")?.value ?? ".";
2539
+ }
2540
+ /**
2541
+ * Split a locale-formatted number on its decimal separator so the integer group
2542
+ * can use primary typography and the fraction (+ unit) secondary typography.
2543
+ */
2544
+ function splitFormattedNumber(formatted, locale) {
2545
+ const dec = getDecimalSeparator(locale);
2546
+ const idx = formatted.indexOf(dec);
2547
+ if (idx === -1) return {
2548
+ primary: formatted,
2549
+ secondary: ""
2550
+ };
2551
+ return {
2552
+ primary: formatted.slice(0, idx),
2553
+ secondary: formatted.slice(idx)
2554
+ };
2555
+ }
2556
+ /**
2557
+ * Split a duration string on the leading digit run (`"1h 2min 3s"` → `"1"` + `"h 2min 3s"`).
2558
+ */
2559
+ function splitLeadingDigits(text) {
2560
+ const match = /^(\d+)([\s\S]*)$/.exec(text);
2561
+ if (!match) return null;
2562
+ return {
2563
+ primary: match[1],
2564
+ secondary: match[2]
2565
+ };
2566
+ }
2567
+ /**
2568
+ * Format seconds into a readable duration (e.g. `1h 2min 3s`).
2569
+ * Implemented without `date-fns`.
2570
+ *
2571
+ * @example
2572
+ * formatSecondsToDuration(0) // => "0s"
2573
+ * formatSecondsToDuration(60) // => "1min 0s"
2574
+ * formatSecondsToDuration(3600) // => "1h 0min 0s"
2575
+ */
2576
+ function formatSecondsToDuration(totalSeconds) {
2577
+ const s = Math.max(0, Math.floor(totalSeconds));
2578
+ const days = Math.floor(s / 86400);
2579
+ const hours = Math.floor(s % 86400 / 3600);
2580
+ const minutes = Math.floor(s % 3600 / 60);
2581
+ const seconds = s % 60;
2582
+ if (days) return `${days}d ${hours}h ${minutes}min ${seconds}s`;
2583
+ if (hours) return `${hours}h ${minutes}min ${seconds}s`;
2584
+ if (minutes) return `${minutes}min ${seconds}s`;
2585
+ return `${seconds}s`;
2586
+ }
2587
+ /**
2588
+ * Format seconds as a zero-padded clock duration (`HH:MM` / `MM:SS`).
2589
+ * The full clock string is primary typography; the unit is appended separately.
2590
+ *
2591
+ * @example
2592
+ * formatSecondsToClock(75180, 'hours') // => { primary: "20:53", secondary: "" }
2593
+ * formatSecondsToClock(304, 'minutes') // => { primary: "05:04", secondary: "" }
2594
+ */
2595
+ function formatSecondsToClock(totalSeconds, scale) {
2596
+ const s = Math.max(0, Math.floor(totalSeconds));
2597
+ if (scale === "hours") {
2598
+ const hours = Math.floor(s / 3600);
2599
+ const minutes$1 = Math.floor(s % 3600 / 60);
2600
+ return {
2601
+ primary: `${String(hours).padStart(2, "0")}:${String(minutes$1).padStart(2, "0")}`,
2602
+ secondary: ""
2603
+ };
2604
+ }
2605
+ const minutes = Math.floor(s / 60);
2606
+ const seconds = s % 60;
2607
+ return {
2608
+ primary: `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`,
2609
+ secondary: ""
2610
+ };
2611
+ }
2612
+ /**
2613
+ * Format a KPI `value` for split typography display.
2614
+ * Returns `null` for empty / non-finite values (caller renders {@link KPI_EMPTY}).
2615
+ *
2616
+ * Contract:
2617
+ * - `string` is always opaque text (`kind` is ignored). Coerce at the call site
2618
+ * if the API sent a numeric string and you need `number` / `percent` / `duration`.
2619
+ * - `number` / `percent` / `duration` require a finite `number`.
2620
+ */
2621
+ function formatKpiValue(value, kind, locale, { formatOptions, durationStyle = "long", durationScale = "minutes" } = {}) {
2622
+ if (kind === "text" || typeof value === "string") {
2623
+ const text = String(value);
2624
+ if (text === "") return null;
2625
+ return {
2626
+ primary: text,
2627
+ secondary: ""
2628
+ };
2629
+ }
2630
+ if (!Number.isFinite(value)) return null;
2631
+ if (kind === "duration") {
2632
+ if (durationStyle === "clock") return formatSecondsToClock(value, durationScale);
2633
+ const formatted = formatSecondsToDuration(value);
2634
+ return splitLeadingDigits(formatted) ?? {
2635
+ primary: formatted,
2636
+ secondary: ""
2637
+ };
2638
+ }
2639
+ const options = kind === "percent" ? {
2640
+ style: "percent",
2641
+ maximumFractionDigits: 1,
2642
+ ...formatOptions
2643
+ } : {
2644
+ maximumFractionDigits: 1,
2645
+ minimumFractionDigits: 0,
2646
+ ...formatOptions
2647
+ };
2648
+ return splitFormattedNumber(new Intl.NumberFormat(locale, options).format(kind === "percent" ? value / 100 : value), locale);
2649
+ }
2650
+
2651
+ //#endregion
2652
+ //#region src/components/kpi.tsx
2653
+ const TREND_ICON = {
2654
+ up: ArrowUpRight,
2655
+ down: ArrowDownRight,
2656
+ neutral: Minus
2657
+ };
2658
+ const TONE_CLASS = {
2659
+ positive: "text-chart-1",
2660
+ negative: "text-destructive",
2661
+ neutral: "text-muted-foreground"
2662
+ };
2663
+ const KpiCardContext = React.createContext(null);
2664
+ function useKpiCardDisabled() {
2665
+ return React.useContext(KpiCardContext)?.disabled ?? false;
2666
+ }
2667
+ function resolveKind(value, kind) {
2668
+ if (typeof value === "string") return "text";
2669
+ if (kind) return kind;
2670
+ return "number";
2671
+ }
2672
+ function TrendGlyph({ trend, tone, className }) {
2673
+ const Icon = TREND_ICON[trend];
2674
+ return /* @__PURE__ */ jsx(Icon, {
2675
+ "aria-hidden": "true",
2676
+ className: cn("shrink-0", TONE_CLASS[tone], className)
2677
+ });
2678
+ }
2679
+ /**
2680
+ * KPI tile container. Thin wrapper around DS `Card` (`size="sm"` by default)
2681
+ * that provides a `disabled` context for nested {@link KpiValue}s.
2682
+ * When `disabled`, applies `opacity-50` and `cursor-not-allowed`, and blocks
2683
+ * activation of nested interactive values.
2684
+ * Compose with DS `CardHeader` / `CardTitle` / `CardContent` / `CardAction`,
2685
+ * {@link KpiValue}, and {@link KpiDescription}.
2686
+ *
2687
+ * @example
2688
+ * ```tsx
2689
+ * import { CardHeader, CardTitle, CardContent } from '@aircall/ds';
2690
+ * import { KpiCard, KpiValue, KpiDescription } from '@aircall/blocks';
2691
+ *
2692
+ * <KpiCard>
2693
+ * <CardHeader>
2694
+ * <CardTitle>Answered</CardTitle>
2695
+ * </CardHeader>
2696
+ * <CardContent className="flex flex-col gap-1">
2697
+ * <KpiValue value={123.1} kind="percent" trend="up" tone="positive" />
2698
+ * <KpiDescription trend="up" tone="positive">+2.48% vs previous period</KpiDescription>
2699
+ * </CardContent>
2700
+ * </KpiCard>
2701
+ * ```
2702
+ */
2703
+ const KpiCard = React.forwardRef((componentProps, forwardRef) => {
2704
+ const { className, size = "sm", disabled = false, children, ...props } = componentProps;
2705
+ return /* @__PURE__ */ jsx(KpiCardContext.Provider, {
2706
+ value: { disabled },
2707
+ children: /* @__PURE__ */ jsx(Card, {
2708
+ ref: forwardRef,
2709
+ "data-slot": "kpi-card",
2710
+ "data-disabled": disabled || void 0,
2711
+ "aria-disabled": disabled || void 0,
2712
+ size,
2713
+ className: cn("gap-2 data-[size=sm]:gap-2", disabled && "cursor-not-allowed opacity-50", className),
2714
+ ...props,
2715
+ children
2716
+ })
2717
+ });
2718
+ });
2719
+ KpiCard.displayName = "KpiCard";
2720
+ /**
2721
+ * KPI metric value. Formats via `useDsLocale` and renders primary (and optional
2722
+ * secondary) typography, with trend variants. Passing `onClick` makes the value
2723
+ * interactive (button + dotted underline). Prefer {@link KpiCard}`s `disabled`
2724
+ * for tile-level muting; a local `disabled` still works for standalone use.
2725
+ * Compose inside DS `CardContent` with {@link KpiDescription}.
2726
+ *
2727
+ * Prop naming: `kind` + `formatOptions` for formatting; `trend` is direction
2728
+ * (icon); `tone` is meaning (color) — "up" is not always good.
2729
+ *
2730
+ * @example
2731
+ * ```tsx
2732
+ * <KpiValue value={123.1} kind="percent" trend="up" tone="positive" />
2733
+ * ```
2734
+ */
2735
+ const KpiValue = React.forwardRef((componentProps, forwardRef) => {
2736
+ const { className, value, kind: kindProp, formatOptions, durationStyle = "long", durationScale = "minutes", unit, trend, tone = "neutral", active = false, disabled: disabledProp = false, children, onClick, render, ...props } = componentProps;
2737
+ const locale = useDsLocale();
2738
+ const cardDisabled = useKpiCardDisabled();
2739
+ const isDisabled = Boolean(disabledProp || cardDisabled);
2740
+ const isInteractive = typeof onClick === "function";
2741
+ const kind = resolveKind(value, kindProp);
2742
+ let content;
2743
+ const valueUnderlineClassName = cn(isInteractive && "border-b border-current", isInteractive && !active && "border-dotted", isInteractive && active && "border-solid");
2744
+ const trendGlyph = trend ? /* @__PURE__ */ jsx(TrendGlyph, {
2745
+ trend,
2746
+ tone: disabledProp && !cardDisabled ? "neutral" : tone,
2747
+ className: "size-6"
2748
+ }) : null;
2749
+ if (children != null) content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
2750
+ className: cn("inline-flex items-baseline gap-0.5", valueUnderlineClassName),
2751
+ children: [/* @__PURE__ */ jsx("span", {
2752
+ className: "text-3xl font-bold",
2753
+ children
2754
+ }), unit != null && unit !== "" && /* @__PURE__ */ jsx("span", {
2755
+ className: "pt-2 text-xl leading-none font-semibold",
2756
+ children: unit
2757
+ })]
2758
+ }), trendGlyph] });
2759
+ else {
2760
+ const formatted = value == null || typeof value === "number" && !Number.isFinite(value) ? null : formatKpiValue(value, kind, locale, {
2761
+ formatOptions,
2762
+ durationStyle,
2763
+ durationScale
2764
+ });
2765
+ const resolvedUnit = unit !== void 0 ? unit : kind === "duration" && durationStyle === "clock" ? clockDurationUnit(durationScale) : void 0;
2766
+ const unitNode = resolvedUnit != null && resolvedUnit !== "" ? resolvedUnit : null;
2767
+ if (!formatted) content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
2768
+ className: cn("text-3xl font-bold", valueUnderlineClassName),
2769
+ children: KPI_EMPTY
2770
+ }), trendGlyph] });
2771
+ else {
2772
+ const hasSecondary = formatted.secondary !== "" || unitNode != null;
2773
+ content = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
2774
+ className: cn("inline-flex items-baseline gap-0.5", valueUnderlineClassName),
2775
+ children: [/* @__PURE__ */ jsx("span", {
2776
+ className: "text-3xl font-bold",
2777
+ children: formatted.primary
2778
+ }), hasSecondary && /* @__PURE__ */ jsxs("span", {
2779
+ className: "pt-2 text-xl leading-none font-semibold",
2780
+ children: [
2781
+ formatted.secondary,
2782
+ formatted.secondary !== "" && unitNode != null ? " " : null,
2783
+ unitNode
2784
+ ]
2785
+ })]
2786
+ }), trendGlyph] });
2787
+ }
2788
+ }
2789
+ const rootClassName = cn("inline-flex w-fit items-center gap-0.5 font-sans", disabledProp && !cardDisabled ? "text-muted-foreground" : active && isInteractive && !isDisabled ? "text-primary" : "text-foreground", isInteractive && !isDisabled && cn("cursor-pointer border-0 bg-transparent p-0 transition-colors active:opacity-60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50", active ? "hover:text-primary/80" : "hover:text-muted-foreground"), isInteractive && isDisabled && "cursor-not-allowed border-0 bg-transparent p-0", className);
2790
+ return useRender({
2791
+ defaultTagName: isInteractive ? "button" : "div",
2792
+ props: mergeProps({
2793
+ ref: forwardRef,
2794
+ className: rootClassName,
2795
+ children: content,
2796
+ ...isInteractive ? {
2797
+ type: "button",
2798
+ onClick,
2799
+ disabled: isDisabled,
2800
+ "aria-pressed": active
2801
+ } : isDisabled ? { "aria-disabled": true } : {}
2802
+ }, props),
2803
+ render,
2804
+ state: {
2805
+ slot: "kpi-value",
2806
+ active,
2807
+ disabled: isDisabled,
2808
+ interactive: isInteractive,
2809
+ trend,
2810
+ tone
2811
+ }
2812
+ });
2813
+ });
2814
+ KpiValue.displayName = "KpiValue";
2815
+ /**
2816
+ * Supporting comparison / context line under a {@link KpiValue}. Compose as a
2817
+ * sibling inside DS `CardContent` — not the metric title (`CardTitle` owns that).
2818
+ *
2819
+ * Text stays foreground; `trend` / `tone` only color the trailing icon so an
2820
+ * upward change that is bad uses `trend="up" tone="negative"`.
2821
+ *
2822
+ * @example
2823
+ * ```tsx
2824
+ * <KpiDescription trend="up" tone="positive">+2.48% vs previous period</KpiDescription>
2825
+ * <KpiDescription trend="up" tone="negative">+12% abandoned</KpiDescription>
2826
+ * ```
2827
+ */
2828
+ const KpiDescription = React.forwardRef(function KpiDescription(componentProps, forwardRef) {
2829
+ const { className, trend, tone = "neutral", children, ...props } = componentProps;
2830
+ return /* @__PURE__ */ jsxs("div", {
2831
+ ref: forwardRef,
2832
+ "data-slot": "kpi-description",
2833
+ "data-trend": trend,
2834
+ "data-tone": tone,
2835
+ className: cn("inline-flex items-center gap-0.5 font-sans text-xs font-medium text-foreground", className),
2836
+ ...props,
2837
+ children: [children, trend ? /* @__PURE__ */ jsx(TrendGlyph, {
2838
+ trend,
2839
+ tone,
2840
+ className: "size-3"
2841
+ }) : null]
2842
+ });
2843
+ });
2844
+ KpiDescription.displayName = "KpiDescription";
2845
+ /**
2846
+ * Loading placeholder sized for a {@link KpiValue} row. Thin wrapper around
2847
+ * DS `Skeleton` (`h-4 w-[45px]` with vertical margin to match the value row).
2848
+ * Swap in place of {@link KpiValue} while data loads.
2849
+ *
2850
+ * @example
2851
+ * ```tsx
2852
+ * <CardContent>
2853
+ * {loading ? <KpiValueSkeleton /> : <KpiValue value={123} />}
2854
+ * </CardContent>
2855
+ * ```
2856
+ */
2857
+ const KpiValueSkeleton = React.forwardRef((componentProps, forwardRef) => {
2858
+ const { className, ...props } = componentProps;
2859
+ return /* @__PURE__ */ jsx(Skeleton, {
2860
+ ref: forwardRef,
2861
+ "data-slot": "kpi-value-skeleton",
2862
+ className: cn("my-[9px] h-4 w-[45px]", className),
2863
+ ...props
2864
+ });
2865
+ });
2866
+ KpiValueSkeleton.displayName = "KpiValueSkeleton";
2867
+ /**
2868
+ * Loading placeholder sized for a {@link KpiDescription} row. Thin wrapper
2869
+ * around DS `Skeleton` (`12×144`, matching `text-xs` comparison copy).
2870
+ * Swap in place of {@link KpiDescription} while data loads.
2871
+ *
2872
+ * @example
2873
+ * ```tsx
2874
+ * <CardContent className="flex flex-col gap-1">
2875
+ * {loading ? (
2876
+ * <>
2877
+ * <KpiValueSkeleton />
2878
+ * <KpiDescriptionSkeleton />
2879
+ * </>
2880
+ * ) : (
2881
+ * <>
2882
+ * <KpiValue value={123} />
2883
+ * <KpiDescription>+2.48% vs previous period</KpiDescription>
2884
+ * </>
2885
+ * )}
2886
+ * </CardContent>
2887
+ * ```
2888
+ */
2889
+ const KpiDescriptionSkeleton = React.forwardRef((componentProps, forwardRef) => {
2890
+ const { className, ...props } = componentProps;
2891
+ return /* @__PURE__ */ jsx(Skeleton, {
2892
+ ref: forwardRef,
2893
+ "data-slot": "kpi-description-skeleton",
2894
+ className: cn("h-3 w-36", className),
2895
+ ...props
2896
+ });
2897
+ });
2898
+ KpiDescriptionSkeleton.displayName = "KpiDescriptionSkeleton";
2899
+
2526
2900
  //#endregion
2527
2901
  //#region src/components/sentiment-badge.tsx
2528
2902
  const SENTIMENT_BADGE = {
@@ -4051,4 +4425,4 @@ function DashboardSidebarNav({ groups, renderLink }) {
4051
4425
  }
4052
4426
 
4053
4427
  //#endregion
4054
- 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, PERCENTAGE_THRESHOLDS, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, ScoreBadge, SentimentBadge, SettingCard, SettingCardAction, SettingCardContent, SettingCardDescription, SettingCardHeader, SettingCardTitle, SubmitButton, TrialBadge, UnknownErrorDescription, UnknownErrorEmptyState, UnknownErrorMedia, UnknownErrorTitle, fieldContext, formContext, toFieldErrors, useCopyToClipboard, useSidebar as useDashboardSidebar, useFieldContext, useForm, useFormContext, withFieldGroup, withForm };
4428
+ 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, KpiCard, KpiDescription, KpiDescriptionSkeleton, KpiValue, KpiValueSkeleton, NewBadge, NoDataDescription, NoDataEmptyState, NoDataMedia, NoDataTitle, NoSupportDescription, NoSupportEmptyState, NoSupportMedia, NoSupportTitle, NotFoundDescription, NotFoundEmptyState, NotFoundMedia, NotFoundTitle, PERCENTAGE_THRESHOLDS, ProfessionalBadge, RestrictedAccessDescription, RestrictedAccessEmptyState, RestrictedAccessMedia, RestrictedAccessTitle, RoleBadge, SaveBar, ScoreBadge, SentimentBadge, SettingCard, SettingCardAction, SettingCardContent, SettingCardDescription, SettingCardHeader, SettingCardTitle, 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.18.0",
3
+ "version": "0.18.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "sideEffects": [
@@ -82,7 +82,7 @@
82
82
  "vite": "7.3.1",
83
83
  "vitest": "4.0.17",
84
84
  "zod": "4.4.3",
85
- "@aircall/ds": "0.28.0",
85
+ "@aircall/ds": "0.28.1",
86
86
  "@aircall/hooks": "0.6.0"
87
87
  },
88
88
  "keywords": [
@@ -106,7 +106,7 @@ Components whose migration decision is still **TBD**, **TODO**, or **Ready for d
106
106
  | `FormWizard` + `useFormWizard` + `FormField` | `useForm`, `CommonForm`, `FormInputField`/`FormSelectField`/`FormComboboxField`/etc., `CardSaveBar`/`SaveBar` (@aircall/blocks) | load @aircall/blocks#aircall-blocks/migrate-dashboard/form-wizard | available |
107
107
  | `Loading` | `Spinner` (and `Skeleton` for content placeholders) (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/loading | available |
108
108
  | `List` + `ListItem` | `ItemGroup`, `Item`, `ItemMedia`, `ItemContent`, `ItemActions` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/list | available |
109
- | `Tile` + `TileHeader` + `TileValue` | `Card` (+ parts) as the tile container (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/tile | available |
109
+ | `Tile` + `TileHeader` + `TileValue` | `KpiCard` + `KpiValue` + `KpiDescription` (+ `KpiValueSkeleton` / `KpiDescriptionSkeleton`) with DS `CardHeader` / `CardTitle` / `CardContent` / `CardAction` (@aircall/blocks + @aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/tile | available |
110
110
  | `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 |
111
111
  | `AudioPlayer` | `Audio`, `AudioPlayerRow`, `AudioPlayerButton`, `AudioPlayerBar`, `AudioPlayerTime`, `AudioPlayerSpeed`, `AudioPlayerSkip`, `AudioPlayerManager`, `useAudioPlayer` (@aircall/ds) | load @aircall/ds#aircall-ds/migrate-audio-player | available |
112
112
  | `InfoPopup` + `InfoPopupTrigger` + `InfoPopupContent` | `HoverCard`, `HoverCardTrigger`, `HoverCardContent` (@aircall/ds) | load @aircall/blocks#aircall-blocks/migrate-dashboard/info-popup | available |