myoperator-ui 0.0.210 → 0.0.211-beta.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.
Files changed (2) hide show
  1. package/dist/index.js +459 -130
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -252,7 +252,7 @@ function looksLikeTailwindClasses(str) {
252
252
  return true;
253
253
  }
254
254
  }
255
- const singleWordUtilities = /^(flex|grid|block|inline|contents|flow-root|hidden|invisible|visible|static|fixed|absolute|relative|sticky|isolate|isolation-auto|overflow-auto|overflow-hidden|overflow-clip|overflow-visible|overflow-scroll|overflow-x-auto|overflow-y-auto|overscroll-auto|overscroll-contain|overscroll-none|truncate|antialiased|subpixel-antialiased|italic|not-italic|underline|overline|line-through|no-underline|uppercase|lowercase|capitalize|normal-case|ordinal|slashed-zero|lining-nums|oldstyle-nums|proportional-nums|tabular-nums|diagonal-fractions|stacked-fractions|sr-only|not-sr-only|resize|resize-none|resize-x|resize-y|snap-start|snap-end|snap-center|snap-align-none|snap-normal|snap-always|touch-auto|touch-none|touch-pan-x|touch-pan-left|touch-pan-right|touch-pan-y|touch-pan-up|touch-pan-down|touch-pinch-zoom|touch-manipulation|select-none|select-text|select-all|select-auto|will-change-auto|will-change-scroll|will-change-contents|will-change-transform|grow|grow-0|shrink|shrink-0|transform|transform-cpu|transform-gpu|transform-none|transition|transition-none|transition-all|transition-colors|transition-opacity|transition-shadow|transition-transform|animate-none|animate-spin|animate-ping|animate-pulse|animate-bounce)$/;
255
+ const singleWordUtilities = /^(flex|inline-flex|grid|inline-grid|block|inline-block|inline-table|inline|contents|flow-root|hidden|invisible|visible|static|fixed|absolute|relative|sticky|isolate|isolation-auto|overflow-auto|overflow-hidden|overflow-clip|overflow-visible|overflow-scroll|overflow-x-auto|overflow-y-auto|overscroll-auto|overscroll-contain|overscroll-none|truncate|antialiased|subpixel-antialiased|italic|not-italic|underline|overline|line-through|no-underline|uppercase|lowercase|capitalize|normal-case|ordinal|slashed-zero|lining-nums|oldstyle-nums|proportional-nums|tabular-nums|diagonal-fractions|stacked-fractions|sr-only|not-sr-only|resize|resize-none|resize-x|resize-y|snap-start|snap-end|snap-center|snap-align-none|snap-normal|snap-always|touch-auto|touch-none|touch-pan-x|touch-pan-left|touch-pan-right|touch-pan-y|touch-pan-up|touch-pan-down|touch-pinch-zoom|touch-manipulation|select-none|select-text|select-all|select-auto|will-change-auto|will-change-scroll|will-change-contents|will-change-transform|grow|grow-0|shrink|shrink-0|transform|transform-cpu|transform-gpu|transform-none|transition|transition-none|transition-all|transition-colors|transition-opacity|transition-shadow|transition-transform|animate-none|animate-spin|animate-ping|animate-pulse|animate-bounce)$/;
256
256
  if (!str.includes(" ") && singleWordUtilities.test(str)) return true;
257
257
  if (/^(@[a-z0-9-]+\/)?[a-z][a-z0-9-]*$/.test(str) && !str.includes(" ")) return false;
258
258
  const words = str.split(/\s+/);
@@ -12632,7 +12632,7 @@ export type { BrandIconProps } from "./icon";
12632
12632
  },
12633
12633
  "bots": {
12634
12634
  name: "bots",
12635
- description: "AI Bot management components \u2014 BotList page, BotCard, and CreateBotModal",
12635
+ description: "AI Bot management components \u2014 BotList page, BotListHeader, BotListSearch, BotListCreateCard, BotListGrid, BotCard, and CreateBotModal",
12636
12636
  category: "custom",
12637
12637
  dependencies: [
12638
12638
  "clsx",
@@ -12640,8 +12640,10 @@ export type { BrandIconProps } from "./icon";
12640
12640
  "lucide-react"
12641
12641
  ],
12642
12642
  internalDependencies: [
12643
+ "badge",
12643
12644
  "button",
12644
- "dialog"
12645
+ "dialog",
12646
+ "dropdown-menu"
12645
12647
  ],
12646
12648
  isMultiFile: true,
12647
12649
  directory: "bots",
@@ -12650,114 +12652,127 @@ export type { BrandIconProps } from "./icon";
12650
12652
  {
12651
12653
  name: "bot-card.tsx",
12652
12654
  content: prefixTailwindClasses(`import * as React from "react";
12653
- import { MessageSquare, Phone, MoreVertical, Pencil, Play, Trash2 } from "lucide-react";
12655
+ import { MessageSquare, Phone } from "lucide-react";
12654
12656
  import { cn } from "../../../lib/utils";
12655
- import {
12656
- DropdownMenu,
12657
- DropdownMenuTrigger,
12658
- DropdownMenuContent,
12659
- DropdownMenuItem,
12660
- DropdownMenuSeparator,
12661
- } from "../dropdown-menu";
12662
12657
  import { Badge } from "../badge";
12663
- import type { BotCardProps, BotType } from "./types";
12658
+ import { BotListAction } from "./bot-list-action";
12659
+ import type { Bot, BotCardProps, BotType } from "./types";
12664
12660
 
12665
- const BOT_TYPE_LABELS: Record<BotType, string> = {
12661
+ const DEFAULT_TYPE_LABELS: Record<BotType, string> = {
12666
12662
  chatbot: "Chatbot",
12667
12663
  voicebot: "Voicebot",
12668
12664
  };
12669
12665
 
12666
+ function getTypeLabel(
12667
+ bot: Bot,
12668
+ typeLabels?: Partial<Record<BotType, string>>
12669
+ ): string {
12670
+ if (bot.typeLabel) return bot.typeLabel;
12671
+ const custom = typeLabels?.[bot.type];
12672
+ if (custom) return custom;
12673
+ return DEFAULT_TYPE_LABELS[bot.type];
12674
+ }
12675
+
12676
+ /**
12677
+ * Single card component for both Chatbot and Voicebot.
12678
+ * All displayed data (icon, badge, name, count, last published) comes from the \`bot\` prop.
12679
+ * Set bot.type to "chatbot" or "voicebot"; no separate card components needed.
12680
+ */
12670
12681
  export const BotCard = React.forwardRef<HTMLDivElement, BotCardProps>(
12671
- ({ bot, onEdit, onPublish, onDelete, className }, ref) => {
12682
+ ({ bot, typeLabels, onEdit, onPublish, onDelete, className, ...props }, ref) => {
12683
+ const typeLabel = getTypeLabel(bot, typeLabels);
12672
12684
  const isChatbot = bot.type === "chatbot";
12673
12685
 
12686
+ const handleCardClick = (e: React.MouseEvent<HTMLDivElement>) => {
12687
+ if (onEdit && !(e.target as HTMLElement).closest("[data-bot-card-action]")) {
12688
+ onEdit(bot.id);
12689
+ }
12690
+ };
12691
+
12692
+ const handleCardKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
12693
+ if (onEdit && !(e.target as HTMLElement).closest("[data-bot-card-action]")) {
12694
+ if (e.key === "Enter" || e.key === " ") {
12695
+ e.preventDefault();
12696
+ onEdit(bot.id);
12697
+ }
12698
+ }
12699
+ };
12700
+
12674
12701
  return (
12675
12702
  <div
12676
12703
  ref={ref}
12704
+ role={onEdit ? "button" : undefined}
12705
+ tabIndex={onEdit ? 0 : undefined}
12706
+ aria-label={onEdit ? \`Edit \${bot.name}\` : undefined}
12707
+ onClick={onEdit ? handleCardClick : undefined}
12708
+ onKeyDown={onEdit ? handleCardKeyDown : undefined}
12677
12709
  className={cn(
12678
- "relative bg-semantic-bg-primary border border-semantic-border-layout rounded-[5px]",
12679
- "shadow-[0px_4px_15.1px_0px_rgba(0,0,0,0.06)] p-5 flex flex-col",
12710
+ "relative bg-semantic-bg-primary border border-semantic-border-layout rounded-[5px] min-w-0 max-w-full overflow-hidden flex flex-col",
12711
+ "shadow-[0px_4px_15.1px_0px_rgba(0,0,0,0.06)] p-3 sm:p-4 md:p-5",
12712
+ onEdit && "cursor-pointer",
12680
12713
  className
12681
12714
  )}
12715
+ {...props}
12682
12716
  >
12683
12717
  {/* Top row: icon + badge + menu */}
12684
- <div className="flex items-start justify-between mb-4">
12685
- <div className="flex items-center justify-center size-[38px] rounded-full bg-semantic-info-surface shrink-0">
12718
+ <div className="flex items-start justify-between gap-2 mb-3 sm:mb-4 min-w-0">
12719
+ <div className="flex items-center justify-center size-8 sm:size-[38px] rounded-full bg-semantic-info-surface shrink-0">
12686
12720
  {isChatbot ? (
12687
- <MessageSquare className="size-5 text-semantic-text-secondary" />
12721
+ <MessageSquare className="size-4 sm:size-5 text-semantic-text-secondary" />
12688
12722
  ) : (
12689
- <Phone className="size-5 text-semantic-text-secondary" />
12723
+ <Phone className="size-4 sm:size-5 text-semantic-text-secondary" />
12690
12724
  )}
12691
12725
  </div>
12692
- <div className="flex items-center gap-2">
12693
- <Badge variant="outline" className="text-xs font-normal">
12694
- {BOT_TYPE_LABELS[bot.type]}
12726
+ <div className="flex items-center gap-1.5 sm:gap-2 min-w-0 shrink-0">
12727
+ <Badge variant="outline" className="text-xs font-normal shrink-0">
12728
+ {typeLabel}
12695
12729
  </Badge>
12696
12730
 
12697
- {/* Three-dot dropdown menu */}
12698
- <DropdownMenu>
12699
- <DropdownMenuTrigger asChild>
12700
- <button
12701
- type="button"
12702
- className="p-1 rounded hover:bg-semantic-bg-hover text-semantic-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus"
12703
- aria-label="More options"
12704
- >
12705
- <MoreVertical className="size-4" />
12706
- </button>
12707
- </DropdownMenuTrigger>
12708
- <DropdownMenuContent align="end" className="min-w-[160px]">
12709
- <DropdownMenuItem
12710
- className="flex items-center gap-3 px-4 py-3 text-sm cursor-pointer"
12711
- onClick={() => onEdit?.(bot.id)}
12712
- >
12713
- <Pencil className="size-4 text-semantic-text-muted shrink-0" />
12714
- <span>Edit</span>
12715
- </DropdownMenuItem>
12716
- <DropdownMenuItem
12717
- className="flex items-center gap-3 px-4 py-3 text-sm cursor-pointer"
12718
- onClick={() => onPublish?.(bot.id)}
12719
- >
12720
- <Play className="size-4 text-semantic-text-muted shrink-0" />
12721
- <span>Publish</span>
12722
- </DropdownMenuItem>
12723
- <DropdownMenuSeparator />
12724
- <DropdownMenuItem
12725
- className="flex items-center gap-3 px-4 py-3 text-sm cursor-pointer text-semantic-error-primary focus:text-semantic-error-primary focus:bg-semantic-error-surface"
12726
- onClick={() => onDelete?.(bot.id)}
12727
- >
12728
- <Trash2 className="size-4 shrink-0" />
12729
- <span>Delete</span>
12730
- </DropdownMenuItem>
12731
- </DropdownMenuContent>
12732
- </DropdownMenu>
12731
+ <span data-bot-card-action className="inline-flex" onClick={(e) => e.stopPropagation()}>
12732
+ <BotListAction
12733
+ align="end"
12734
+ onEdit={() => onEdit?.(bot.id)}
12735
+ onDelete={() => onDelete?.(bot.id)}
12736
+ />
12737
+ </span>
12733
12738
  </div>
12734
12739
  </div>
12735
12740
 
12736
12741
  {/* Bot name */}
12737
- <h3 className="m-0 text-base font-normal text-semantic-text-primary truncate mb-1">
12742
+ <h3 className="m-0 text-sm sm:text-base font-normal text-semantic-text-primary truncate mb-1 min-w-0">
12738
12743
  {bot.name}
12739
12744
  </h3>
12740
12745
 
12741
12746
  {/* Conversations count */}
12742
- <p className="m-0 text-sm text-semantic-text-muted mb-4">
12747
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted mb-3 sm:mb-4">
12743
12748
  {bot.conversationCount.toLocaleString()} Conversations
12744
12749
  </p>
12745
12750
 
12746
12751
  {/* Divider */}
12747
- <div className="border-t border-semantic-border-layout mb-3 mt-auto" />
12752
+ <div className="border-t border-semantic-border-layout mb-2 sm:mb-3 mt-auto" />
12748
12753
 
12749
12754
  {/* Last published */}
12750
- <div className="flex flex-col gap-1">
12751
- <span className="text-xs font-normal text-semantic-text-secondary uppercase tracking-[0.048px]">
12752
- Last Published
12753
- </span>
12754
- {bot.lastPublishedBy && bot.lastPublishedDate ? (
12755
- <p className="m-0 text-sm text-semantic-text-muted">
12756
- {bot.lastPublishedBy} | {bot.lastPublishedDate}
12755
+ <div className="flex flex-col gap-0.5 sm:gap-1 min-w-0">
12756
+ {bot.status === "draft" ? (
12757
+ <p className="m-0 text-xs font-normal text-semantic-text-secondary uppercase tracking-[0.048px] flex items-center justify-start gap-5">
12758
+ Last Published
12759
+ <span className="text-xs font-normal text-semantic-error-primary flex items-center gap-1.5 shrink-0">
12760
+ <span className="size-1.5 rounded-full bg-semantic-error-primary shrink-0" aria-hidden />
12761
+ Unpublished changes
12762
+ </span>
12757
12763
  </p>
12758
12764
  ) : (
12759
- <p className="m-0 text-sm text-semantic-text-muted">\u2014</p>
12765
+ <span className="text-xs font-normal text-semantic-text-secondary uppercase tracking-[0.048px]">
12766
+ Last Published
12767
+ </span>
12760
12768
  )}
12769
+ {bot.lastPublishedBy && bot.lastPublishedDate ? (
12770
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted truncate">
12771
+ {bot.lastPublishedBy} | {bot.lastPublishedDate}
12772
+ </p>
12773
+ ) : bot.status !== "draft" ? (
12774
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted">\u2014</p>
12775
+ ) : null}
12761
12776
  </div>
12762
12777
  </div>
12763
12778
  );
@@ -12779,7 +12794,7 @@ import {
12779
12794
  DialogTitle,
12780
12795
  } from "../dialog";
12781
12796
  import { Button } from "../button";
12782
- import type { CreateBotModalProps, BotType } from "./types";
12797
+ import { BOT_TYPE, type CreateBotModalProps, type BotType } from "./types";
12783
12798
 
12784
12799
  interface BotTypeOption {
12785
12800
  id: BotType;
@@ -12809,7 +12824,8 @@ export const CreateBotModal = React.forwardRef<
12809
12824
 
12810
12825
  const handleSubmit = () => {
12811
12826
  if (!name.trim()) return;
12812
- onSubmit?.({ name: name.trim(), type: selectedType });
12827
+ const typeValue = selectedType === "chatbot" ? BOT_TYPE.CHAT : BOT_TYPE.VOICE;
12828
+ onSubmit?.({ name: name.trim(), type: typeValue });
12813
12829
  setName("");
12814
12830
  setSelectedType("chatbot");
12815
12831
  };
@@ -12822,12 +12838,12 @@ export const CreateBotModal = React.forwardRef<
12822
12838
 
12823
12839
  return (
12824
12840
  <Dialog open={open} onOpenChange={onOpenChange}>
12825
- <DialogContent ref={ref} size="sm" className={cn("mx-4 sm:mx-auto", className)}>
12841
+ <DialogContent ref={ref} size="sm" className={cn("mx-3 max-h-[90vh] overflow-y-auto w-[calc(100%-1.5rem)] sm:mx-auto sm:w-full", className)}>
12826
12842
  <DialogHeader>
12827
12843
  <DialogTitle>Create AI bot</DialogTitle>
12828
12844
  </DialogHeader>
12829
12845
 
12830
- <div className="flex flex-col gap-6">
12846
+ <div className="flex flex-col gap-4 sm:gap-6">
12831
12847
  {/* Name field */}
12832
12848
  <div className="flex flex-col gap-1.5">
12833
12849
  <label
@@ -12858,7 +12874,7 @@ export const CreateBotModal = React.forwardRef<
12858
12874
  <span className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
12859
12875
  Select Bot Type
12860
12876
  </span>
12861
- <div className="flex gap-3">
12877
+ <div className="flex flex-col gap-3 sm:flex-row sm:gap-3">
12862
12878
  {BOT_TYPE_OPTIONS.map(({ id, label, description }) => {
12863
12879
  const isSelected = selectedType === id;
12864
12880
  return (
@@ -12867,7 +12883,7 @@ export const CreateBotModal = React.forwardRef<
12867
12883
  type="button"
12868
12884
  onClick={() => setSelectedType(id)}
12869
12885
  className={cn(
12870
- "flex flex-col items-start gap-2.5 p-3 rounded-lg border text-left flex-1 h-[134px] justify-center",
12886
+ "flex flex-col items-start gap-2 sm:gap-2.5 p-3 rounded-lg border text-left flex-1 min-h-[100px] sm:h-[134px] justify-center min-w-0",
12871
12887
  "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus",
12872
12888
  isSelected
12873
12889
  ? "bg-semantic-brand-surface border-semantic-brand shadow-sm"
@@ -12913,7 +12929,7 @@ export const CreateBotModal = React.forwardRef<
12913
12929
  </div>
12914
12930
 
12915
12931
  {/* Footer actions */}
12916
- <div className="flex gap-4 justify-end mt-2">
12932
+ <div className="flex flex-col-reverse gap-3 sm:flex-row sm:gap-4 justify-end mt-2">
12917
12933
  <Button variant="outline" onClick={handleClose}>
12918
12934
  Cancel
12919
12935
  </Button>
@@ -12936,9 +12952,12 @@ CreateBotModal.displayName = "CreateBotModal";
12936
12952
  {
12937
12953
  name: "bot-list.tsx",
12938
12954
  content: prefixTailwindClasses(`import * as React from "react";
12939
- import { Plus, Search } from "lucide-react";
12940
12955
  import { cn } from "../../../lib/utils";
12941
12956
  import { BotCard } from "./bot-card";
12957
+ import { BotListHeader } from "./bot-list-header";
12958
+ import { BotListSearch } from "./bot-list-search";
12959
+ import { BotListCreateCard } from "./bot-list-create-card";
12960
+ import { BotListGrid } from "./bot-list-grid";
12942
12961
  import { CreateBotModal } from "./create-bot-modal";
12943
12962
  import type { BotListProps } from "./types";
12944
12963
 
@@ -12946,6 +12965,7 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
12946
12965
  (
12947
12966
  {
12948
12967
  bots = [],
12968
+ typeLabels,
12949
12969
  onCreateBot,
12950
12970
  onCreateBotSubmit,
12951
12971
  onBotEdit,
@@ -12954,7 +12974,10 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
12954
12974
  onSearch,
12955
12975
  title = "AI Bot",
12956
12976
  subtitle = "Create & manage AI bots",
12977
+ searchPlaceholder = "Search bot...",
12978
+ createCardLabel = "Create new bot",
12957
12979
  className,
12980
+ ...props
12958
12981
  },
12959
12982
  ref
12960
12983
  ) => {
@@ -12967,64 +12990,41 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
12967
12990
  };
12968
12991
 
12969
12992
  return (
12970
- <div ref={ref} className={cn("flex flex-col w-full", className)}>
12971
- {/* Page header */}
12972
- <div className="flex flex-col gap-4 pb-5 mb-6 border-b border-semantic-border-layout sm:flex-row sm:items-center sm:justify-between">
12973
- <div className="flex flex-col gap-1.5">
12974
- <h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px]">
12975
- {title}
12976
- </h1>
12977
- <p className="m-0 text-sm text-semantic-text-muted tracking-[0.035px]">
12978
- {subtitle}
12979
- </p>
12980
- </div>
12981
-
12982
- {/* Search bar */}
12983
- <div className="flex items-center gap-2 h-10 px-2.5 border border-semantic-border-input rounded bg-semantic-bg-primary hover:border-semantic-border-input-focus focus-within:border-semantic-border-input-focus focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)] w-full sm:w-auto">
12984
- <Search className="size-[14px] text-semantic-text-muted shrink-0" />
12985
- <input
12986
- type="text"
12987
- value={searchQuery}
12988
- onChange={(e) => handleSearch(e.target.value)}
12989
- placeholder="Search bot..."
12990
- className="text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none w-full sm:w-[180px]"
12991
- />
12992
- </div>
12993
+ <div
12994
+ ref={ref}
12995
+ className={cn("flex flex-col w-full min-w-0 max-w-full overflow-x-hidden box-border", className)}
12996
+ {...props}
12997
+ >
12998
+ {/* Page header: title, subtitle, and search */}
12999
+ <div className="flex flex-col gap-3 pb-4 mb-4 border-b border-semantic-border-layout sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:pb-5 sm:mb-6 min-w-0">
13000
+ <BotListHeader title={title} subtitle={subtitle} />
13001
+ <BotListSearch
13002
+ value={searchQuery}
13003
+ onSearch={handleSearch}
13004
+ placeholder={searchPlaceholder}
13005
+ />
12993
13006
  </div>
12994
13007
 
12995
- {/* Bot grid */}
12996
- <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
12997
- {/* Create new bot card */}
12998
- <button
12999
- type="button"
13008
+ {/* Bot grid: create card + bot cards */}
13009
+ <BotListGrid>
13010
+ <BotListCreateCard
13011
+ label={createCardLabel}
13000
13012
  onClick={() => {
13001
13013
  setCreateModalOpen(true);
13002
13014
  onCreateBot?.();
13003
13015
  }}
13004
- className={cn(
13005
- "flex flex-col items-center justify-center gap-3 p-2.5 rounded-[5px]",
13006
- "bg-semantic-info-surface-subtle border border-dashed border-[var(--color-primary-100)]",
13007
- "cursor-pointer hover:bg-semantic-bg-hover transition-colors min-h-[207px]",
13008
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus"
13009
- )}
13010
- >
13011
- <Plus className="size-4 text-semantic-text-secondary" />
13012
- <span className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
13013
- Create new bot
13014
- </span>
13015
- </button>
13016
-
13017
- {/* Bot cards */}
13016
+ />
13018
13017
  {bots.map((bot) => (
13019
13018
  <BotCard
13020
13019
  key={bot.id}
13021
13020
  bot={bot}
13021
+ typeLabels={typeLabels}
13022
13022
  onEdit={onBotEdit}
13023
13023
  onPublish={onBotPublish}
13024
13024
  onDelete={onBotDelete}
13025
13025
  />
13026
13026
  ))}
13027
- </div>
13027
+ </BotListGrid>
13028
13028
 
13029
13029
  <CreateBotModal
13030
13030
  open={createModalOpen}
@@ -13040,52 +13040,365 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13040
13040
  );
13041
13041
 
13042
13042
  BotList.displayName = "BotList";
13043
+ `, prefix)
13044
+ },
13045
+ {
13046
+ name: "bot-list-header.tsx",
13047
+ content: prefixTailwindClasses(`import * as React from "react";
13048
+ import { cn } from "../../../lib/utils";
13049
+ import type { BotListHeaderProps } from "./types";
13050
+
13051
+ export const BotListHeader = React.forwardRef<HTMLDivElement, BotListHeaderProps>(
13052
+ ({ title, subtitle, className, ...props }, ref) => (
13053
+ <div
13054
+ ref={ref}
13055
+ className={cn("flex flex-col gap-1.5 min-w-0 shrink", className)}
13056
+ {...props}
13057
+ >
13058
+ {title != null && (
13059
+ <h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px] break-words sm:text-lg">
13060
+ {title}
13061
+ </h1>
13062
+ )}
13063
+ {subtitle != null && (
13064
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted tracking-[0.035px] break-words">
13065
+ {subtitle}
13066
+ </p>
13067
+ )}
13068
+ </div>
13069
+ )
13070
+ );
13071
+
13072
+ BotListHeader.displayName = "BotListHeader";
13073
+ `, prefix)
13074
+ },
13075
+ {
13076
+ name: "bot-list-search.tsx",
13077
+ content: prefixTailwindClasses(`import * as React from "react";
13078
+ import { Search } from "lucide-react";
13079
+ import { cn } from "../../../lib/utils";
13080
+ import type { BotListSearchProps } from "./types";
13081
+
13082
+ export const BotListSearch = React.forwardRef<HTMLDivElement, BotListSearchProps>(
13083
+ (
13084
+ {
13085
+ value,
13086
+ placeholder = "Search bot...",
13087
+ onSearch,
13088
+ defaultValue,
13089
+ className,
13090
+ ...props
13091
+ },
13092
+ ref
13093
+ ) => {
13094
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? "");
13095
+ const isControlled = value !== undefined;
13096
+ const displayValue = isControlled ? value : internalValue;
13097
+
13098
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
13099
+ const next = e.target.value;
13100
+ if (!isControlled) setInternalValue(next);
13101
+ onSearch?.(next);
13102
+ };
13103
+
13104
+ return (
13105
+ <div
13106
+ ref={ref}
13107
+ className={cn(
13108
+ "flex items-center gap-2 h-9 sm:h-10 px-2.5 sm:px-3 border border-semantic-border-input rounded bg-semantic-bg-primary min-w-0 shrink-0",
13109
+ "hover:border-semantic-border-input-focus focus-within:border-semantic-border-input-focus",
13110
+ "focus-within:shadow-[0_0_0_1px_rgba(43,188,202,0.15)] w-full max-w-full sm:max-w-[180px] md:max-w-[220px] sm:shrink-0",
13111
+ className
13112
+ )}
13113
+ {...props}
13114
+ >
13115
+ <Search className="size-[14px] text-semantic-text-muted shrink-0" />
13116
+ <input
13117
+ type="text"
13118
+ value={displayValue}
13119
+ onChange={handleChange}
13120
+ placeholder={placeholder}
13121
+ className="text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none w-full min-w-0"
13122
+ aria-label={placeholder}
13123
+ />
13124
+ </div>
13125
+ );
13126
+ }
13127
+ );
13128
+
13129
+ BotListSearch.displayName = "BotListSearch";
13130
+ `, prefix)
13131
+ },
13132
+ {
13133
+ name: "bot-list-create-card.tsx",
13134
+ content: prefixTailwindClasses(`import * as React from "react";
13135
+ import { Plus } from "lucide-react";
13136
+ import { cn } from "../../../lib/utils";
13137
+ import type { BotListCreateCardProps } from "./types";
13138
+
13139
+ export const BotListCreateCard = React.forwardRef<
13140
+ HTMLButtonElement,
13141
+ BotListCreateCardProps
13142
+ >(
13143
+ (
13144
+ {
13145
+ label = "Create new bot",
13146
+ onClick,
13147
+ className,
13148
+ ...props
13149
+ },
13150
+ ref
13151
+ ) => (
13152
+ <button
13153
+ ref={ref}
13154
+ type="button"
13155
+ onClick={onClick}
13156
+ className={cn(
13157
+ "flex flex-col items-center justify-center gap-2 sm:gap-3 p-3 sm:p-2.5 rounded-[5px] min-h-[180px] sm:min-h-[207px] w-full min-w-0 max-w-full",
13158
+ "bg-semantic-info-surface-subtle border border-dashed border-semantic-border-layout",
13159
+ "cursor-pointer transition-colors hover:bg-semantic-bg-hover hover:border-semantic-border-input",
13160
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus",
13161
+ "self-stretch justify-self-stretch",
13162
+ className
13163
+ )}
13164
+ aria-label={label}
13165
+ {...props}
13166
+ >
13167
+ <Plus className="size-4 text-semantic-text-secondary shrink-0" />
13168
+ <span className="text-sm font-semibold leading-5 text-semantic-text-secondary text-center tracking-[0.014px]">
13169
+ {label}
13170
+ </span>
13171
+ </button>
13172
+ )
13173
+ );
13174
+
13175
+ BotListCreateCard.displayName = "BotListCreateCard";
13176
+ `, prefix)
13177
+ },
13178
+ {
13179
+ name: "bot-list-grid.tsx",
13180
+ content: prefixTailwindClasses(`import * as React from "react";
13181
+ import { cn } from "../../../lib/utils";
13182
+ import type { BotListGridProps } from "./types";
13183
+
13184
+ export const BotListGrid = React.forwardRef<HTMLDivElement, BotListGridProps>(
13185
+ ({ children, className, ...props }, ref) => (
13186
+ <div
13187
+ ref={ref}
13188
+ className={cn(
13189
+ "grid w-full min-w-0 max-w-full overflow-hidden gap-3 sm:gap-5 md:gap-6",
13190
+ "grid-cols-[repeat(auto-fill,minmax(min(100%,280px),1fr))]",
13191
+ className
13192
+ )}
13193
+ {...props}
13194
+ >
13195
+ {children}
13196
+ </div>
13197
+ )
13198
+ );
13199
+
13200
+ BotListGrid.displayName = "BotListGrid";
13201
+ `, prefix)
13202
+ },
13203
+ {
13204
+ name: "bot-list-action.tsx",
13205
+ content: prefixTailwindClasses(`import * as React from "react";
13206
+ import { MoreVertical, Pencil, Trash2 } from "lucide-react";
13207
+ import { cn } from "../../../lib/utils";
13208
+ import {
13209
+ DropdownMenu,
13210
+ DropdownMenuTrigger,
13211
+ DropdownMenuContent,
13212
+ DropdownMenuItem,
13213
+ DropdownMenuSeparator,
13214
+ } from "../dropdown-menu";
13215
+ import type { BotListActionProps } from "./types";
13216
+
13217
+ const defaultTrigger = (
13218
+ <button
13219
+ type="button"
13220
+ className="p-2 min-h-[44px] min-w-[44px] sm:p-1 sm:min-h-0 sm:min-w-0 rounded hover:bg-semantic-bg-hover text-semantic-text-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus flex items-center justify-center touch-manipulation"
13221
+ aria-label="More options"
13222
+ >
13223
+ <MoreVertical className="size-4 shrink-0" />
13224
+ </button>
13225
+ );
13226
+
13227
+ export const BotListAction = React.forwardRef<HTMLDivElement, BotListActionProps>(
13228
+ (
13229
+ {
13230
+ onEdit,
13231
+ onDelete,
13232
+ trigger = defaultTrigger,
13233
+ align = "end",
13234
+ className,
13235
+ ...props
13236
+ },
13237
+ ref
13238
+ ) => {
13239
+ return (
13240
+ <div ref={ref} className={cn("inline-flex", className)} {...props}>
13241
+ <DropdownMenu>
13242
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
13243
+ <DropdownMenuContent
13244
+ align={align}
13245
+ className="min-w-[160px] max-w-[min(100vw-2rem,320px)] max-h-[min(70vh,400px)] overflow-y-auto rounded-lg border border-semantic-border-layout bg-semantic-bg-ui p-1 shadow-lg"
13246
+ >
13247
+ <DropdownMenuItem
13248
+ className="flex cursor-pointer items-center gap-2 px-3 py-2.5 text-sm text-semantic-text-primary outline-none transition-colors focus:bg-semantic-bg-hover focus:text-semantic-text-primary"
13249
+ onSelect={(e) => {
13250
+ e.preventDefault();
13251
+ onEdit?.();
13252
+ }}
13253
+ >
13254
+ <Pencil className="size-4 shrink-0 text-semantic-text-primary" />
13255
+ <span>Edit</span>
13256
+ </DropdownMenuItem>
13257
+ <DropdownMenuSeparator className="my-1 bg-semantic-border-layout" />
13258
+ <DropdownMenuItem
13259
+ className="flex cursor-pointer items-center gap-2 px-3 py-2.5 text-sm text-semantic-error-primary outline-none transition-colors focus:bg-semantic-error-surface focus:text-semantic-error-primary"
13260
+ onSelect={(e) => {
13261
+ e.preventDefault();
13262
+ onDelete?.();
13263
+ }}
13264
+ >
13265
+ <Trash2 className="size-4 shrink-0 text-semantic-error-primary" />
13266
+ <span>Delete</span>
13267
+ </DropdownMenuItem>
13268
+ </DropdownMenuContent>
13269
+ </DropdownMenu>
13270
+ </div>
13271
+ );
13272
+ }
13273
+ );
13274
+
13275
+ BotListAction.displayName = "BotListAction";
13043
13276
  `, prefix)
13044
13277
  },
13045
13278
  {
13046
13279
  name: "types.ts",
13047
- content: prefixTailwindClasses(`export type BotType = "chatbot" | "voicebot";
13280
+ content: prefixTailwindClasses(`import type * as React from "react";
13281
+
13282
+ export const BOT_TYPE = {
13283
+ CHAT: 1,
13284
+ VOICE: 2,
13285
+ } as const;
13286
+
13287
+ export type BOT_TYPE = (typeof BOT_TYPE)[keyof typeof BOT_TYPE];
13288
+
13289
+ export type BotType = "chatbot" | "voicebot";
13290
+
13291
+ export type BotStatus = "draft" | "published";
13048
13292
 
13293
+ /**
13294
+ * Single bot shape for both Chatbot and Voicebot.
13295
+ * Use the same BotCard for both; set type to "chatbot" or "voicebot" and pass all data via this prop.
13296
+ */
13049
13297
  export interface Bot {
13050
13298
  id: string;
13051
13299
  name: string;
13300
+ /** "chatbot" | "voicebot" \u2014 determines icon and default badge label; all other data is from this object */
13052
13301
  type: BotType;
13053
13302
  conversationCount: number;
13054
13303
  lastPublishedBy?: string;
13055
13304
  lastPublishedDate?: string;
13305
+ /** Optional custom label for the type badge (overrides typeLabels and default "Chatbot"/"Voicebot") */
13306
+ typeLabel?: string;
13307
+ /** When "draft", card shows "Unpublished changes" with red indicator in the Last Published section */
13308
+ status?: BotStatus;
13056
13309
  }
13057
13310
 
13058
- export interface BotCardProps {
13311
+ export interface BotCardProps
13312
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
13313
+ /** Single bot object: pass chatbot or voicebot data here; card renders based on bot.type and bot fields */
13059
13314
  bot: Bot;
13315
+ /** Override labels for bot types (e.g. { chatbot: "Chat", voicebot: "Voice" }). Ignored if bot.typeLabel is set. */
13316
+ typeLabels?: Partial<Record<BotType, string>>;
13060
13317
  /** Called when Edit action is selected */
13061
13318
  onEdit?: (botId: string) => void;
13062
13319
  /** Called when Publish action is selected */
13063
13320
  onPublish?: (botId: string) => void;
13064
13321
  /** Called when Delete action is selected */
13065
13322
  onDelete?: (botId: string) => void;
13066
- className?: string;
13067
13323
  }
13068
13324
 
13069
13325
  export interface CreateBotModalProps {
13070
13326
  open: boolean;
13071
13327
  onOpenChange: (open: boolean) => void;
13072
- onSubmit?: (data: { name: string; type: BotType }) => void;
13328
+ /** Called with name and BOT_TYPE (CHAT = 1, VOICE = 2) when user submits */
13329
+ onSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13073
13330
  className?: string;
13074
13331
  }
13075
13332
 
13076
- export interface BotListProps {
13333
+ export interface BotListHeaderProps
13334
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
13335
+ /** Page title */
13336
+ title?: string;
13337
+ /** Optional subtitle below the title */
13338
+ subtitle?: string;
13339
+ }
13340
+
13341
+ export interface BotListSearchProps
13342
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
13343
+ /** Controlled value (use with onSearch) */
13344
+ value?: string;
13345
+ /** Placeholder text */
13346
+ placeholder?: string;
13347
+ /** Called when the search value changes */
13348
+ onSearch?: (query: string) => void;
13349
+ /** Uncontrolled: default value */
13350
+ defaultValue?: string;
13351
+ }
13352
+
13353
+ export interface BotListCreateCardProps
13354
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
13355
+ /** Label for the create card (e.g. "Create new bot") */
13356
+ label?: string;
13357
+ }
13358
+
13359
+ export interface BotListGridProps
13360
+ extends React.HTMLAttributes<HTMLDivElement> {
13361
+ children: React.ReactNode;
13362
+ }
13363
+
13364
+ export interface BotListActionProps
13365
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
13366
+ /** Called when Edit is selected */
13367
+ onEdit?: () => void;
13368
+ /** Called when Delete is selected */
13369
+ onDelete?: () => void;
13370
+ /** Custom trigger element; defaults to three-dot icon button */
13371
+ trigger?: React.ReactNode;
13372
+ /** Content alignment relative to trigger */
13373
+ align?: "start" | "center" | "end";
13374
+ }
13375
+
13376
+ export interface BotListProps
13377
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
13378
+ /** List of bots to display */
13077
13379
  bots?: Bot[];
13380
+ /** Override type badge labels for all cards (e.g. { chatbot: "Chat", voicebot: "Voice" }). Per-bot bot.typeLabel still wins. */
13381
+ typeLabels?: Partial<Record<BotType, string>>;
13078
13382
  /** Called when the "Create new bot" card is clicked (modal opens) */
13079
13383
  onCreateBot?: () => void;
13080
- /** Called when the Create Bot modal is submitted with the new bot data */
13081
- onCreateBotSubmit?: (data: { name: string; type: BotType }) => void;
13384
+ /** Called when the Create Bot modal is submitted with the new bot data (type is BOT_TYPE: CHAT = 1, VOICE = 2) */
13385
+ onCreateBotSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13386
+ /** Called when user selects Edit on a bot (card click or menu) */
13082
13387
  onBotEdit?: (botId: string) => void;
13388
+ /** Called when user selects Publish on a bot (menu; optional) */
13083
13389
  onBotPublish?: (botId: string) => void;
13390
+ /** Called when user selects Delete on a bot */
13084
13391
  onBotDelete?: (botId: string) => void;
13392
+ /** Called when the search query changes */
13085
13393
  onSearch?: (query: string) => void;
13394
+ /** Page title (default: "AI Bot") */
13086
13395
  title?: string;
13396
+ /** Page subtitle (default: "Create & manage AI bots") */
13087
13397
  subtitle?: string;
13088
- className?: string;
13398
+ /** Placeholder for the search input (default: "Search bot...") */
13399
+ searchPlaceholder?: string;
13400
+ /** Label for the create-new-bot card (default: "Create new bot") */
13401
+ createCardLabel?: string;
13089
13402
  }
13090
13403
  `, prefix)
13091
13404
  },
@@ -13098,7 +13411,23 @@ export { CreateBotModal } from "./create-bot-modal";
13098
13411
  export type { CreateBotModalProps } from "./types";
13099
13412
 
13100
13413
  export { BotList } from "./bot-list";
13101
- export type { BotListProps, Bot, BotType } from "./types";
13414
+ export { BotListHeader } from "./bot-list-header";
13415
+ export { BotListSearch } from "./bot-list-search";
13416
+ export { BotListCreateCard } from "./bot-list-create-card";
13417
+ export { BotListGrid } from "./bot-list-grid";
13418
+ export { BotListAction } from "./bot-list-action";
13419
+ export { BOT_TYPE } from "./types";
13420
+ export type {
13421
+ BotListProps,
13422
+ BotListHeaderProps,
13423
+ BotListSearchProps,
13424
+ BotListCreateCardProps,
13425
+ BotListGridProps,
13426
+ BotListActionProps,
13427
+ Bot,
13428
+ BotType,
13429
+ BotStatus,
13430
+ } from "./types";
13102
13431
  `, prefix)
13103
13432
  }
13104
13433
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-ui",
3
- "version": "0.0.210",
3
+ "version": "0.0.211-beta.1",
4
4
  "description": "CLI for adding myOperator UI components to your project",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",