myoperator-ui 0.0.211 → 0.0.212

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+/);
@@ -12833,7 +12833,7 @@ export type { BrandIconProps } from "./icon";
12833
12833
  },
12834
12834
  "bots": {
12835
12835
  name: "bots",
12836
- description: "AI Bot management components \u2014 BotList page, BotCard, and CreateBotModal",
12836
+ description: "AI Bot management components \u2014 BotList page, BotListHeader, BotListSearch, BotListCreateCard, BotListGrid, BotCard, and CreateBotModal",
12837
12837
  category: "custom",
12838
12838
  dependencies: [
12839
12839
  "clsx",
@@ -12841,8 +12841,10 @@ export type { BrandIconProps } from "./icon";
12841
12841
  "lucide-react"
12842
12842
  ],
12843
12843
  internalDependencies: [
12844
+ "badge",
12844
12845
  "button",
12845
- "dialog"
12846
+ "dialog",
12847
+ "dropdown-menu"
12846
12848
  ],
12847
12849
  isMultiFile: true,
12848
12850
  directory: "bots",
@@ -12851,114 +12853,127 @@ export type { BrandIconProps } from "./icon";
12851
12853
  {
12852
12854
  name: "bot-card.tsx",
12853
12855
  content: prefixTailwindClasses(`import * as React from "react";
12854
- import { MessageSquare, Phone, MoreVertical, Pencil, Play, Trash2 } from "lucide-react";
12856
+ import { MessageSquare, Phone } from "lucide-react";
12855
12857
  import { cn } from "../../../lib/utils";
12856
- import {
12857
- DropdownMenu,
12858
- DropdownMenuTrigger,
12859
- DropdownMenuContent,
12860
- DropdownMenuItem,
12861
- DropdownMenuSeparator,
12862
- } from "../dropdown-menu";
12863
12858
  import { Badge } from "../badge";
12864
- import type { BotCardProps, BotType } from "./types";
12859
+ import { BotListAction } from "./bot-list-action";
12860
+ import type { Bot, BotCardProps, BotType } from "./types";
12865
12861
 
12866
- const BOT_TYPE_LABELS: Record<BotType, string> = {
12862
+ const DEFAULT_TYPE_LABELS: Record<BotType, string> = {
12867
12863
  chatbot: "Chatbot",
12868
12864
  voicebot: "Voicebot",
12869
12865
  };
12870
12866
 
12867
+ function getTypeLabel(
12868
+ bot: Bot,
12869
+ typeLabels?: Partial<Record<BotType, string>>
12870
+ ): string {
12871
+ if (bot.typeLabel) return bot.typeLabel;
12872
+ const custom = typeLabels?.[bot.type];
12873
+ if (custom) return custom;
12874
+ return DEFAULT_TYPE_LABELS[bot.type];
12875
+ }
12876
+
12877
+ /**
12878
+ * Single card component for both Chatbot and Voicebot.
12879
+ * All displayed data (icon, badge, name, count, last published) comes from the \`bot\` prop.
12880
+ * Set bot.type to "chatbot" or "voicebot"; no separate card components needed.
12881
+ */
12871
12882
  export const BotCard = React.forwardRef<HTMLDivElement, BotCardProps>(
12872
- ({ bot, onEdit, onPublish, onDelete, className }, ref) => {
12883
+ ({ bot, typeLabels, onEdit, onPublish, onDelete, className, ...props }, ref) => {
12884
+ const typeLabel = getTypeLabel(bot, typeLabels);
12873
12885
  const isChatbot = bot.type === "chatbot";
12874
12886
 
12887
+ const handleCardClick = (e: React.MouseEvent<HTMLDivElement>) => {
12888
+ if (onEdit && !(e.target as HTMLElement).closest("[data-bot-card-action]")) {
12889
+ onEdit(bot.id);
12890
+ }
12891
+ };
12892
+
12893
+ const handleCardKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
12894
+ if (onEdit && !(e.target as HTMLElement).closest("[data-bot-card-action]")) {
12895
+ if (e.key === "Enter" || e.key === " ") {
12896
+ e.preventDefault();
12897
+ onEdit(bot.id);
12898
+ }
12899
+ }
12900
+ };
12901
+
12875
12902
  return (
12876
12903
  <div
12877
12904
  ref={ref}
12905
+ role={onEdit ? "button" : undefined}
12906
+ tabIndex={onEdit ? 0 : undefined}
12907
+ aria-label={onEdit ? \`Edit \${bot.name}\` : undefined}
12908
+ onClick={onEdit ? handleCardClick : undefined}
12909
+ onKeyDown={onEdit ? handleCardKeyDown : undefined}
12878
12910
  className={cn(
12879
- "relative bg-semantic-bg-primary border border-semantic-border-layout rounded-[5px]",
12880
- "shadow-[0px_4px_15.1px_0px_rgba(0,0,0,0.06)] p-5 flex flex-col",
12911
+ "relative bg-semantic-bg-primary border border-semantic-border-layout rounded-[5px] min-w-0 max-w-full overflow-hidden flex flex-col",
12912
+ "shadow-[0px_4px_15.1px_0px_rgba(0,0,0,0.06)] p-3 sm:p-4 md:p-5",
12913
+ onEdit && "cursor-pointer",
12881
12914
  className
12882
12915
  )}
12916
+ {...props}
12883
12917
  >
12884
12918
  {/* Top row: icon + badge + menu */}
12885
- <div className="flex items-start justify-between mb-4">
12886
- <div className="flex items-center justify-center size-[38px] rounded-full bg-semantic-info-surface shrink-0">
12919
+ <div className="flex items-start justify-between gap-2 mb-3 sm:mb-4 min-w-0">
12920
+ <div className="flex items-center justify-center size-8 sm:size-[38px] rounded-full bg-semantic-info-surface shrink-0">
12887
12921
  {isChatbot ? (
12888
- <MessageSquare className="size-5 text-semantic-text-secondary" />
12922
+ <MessageSquare className="size-4 sm:size-5 text-semantic-text-secondary" />
12889
12923
  ) : (
12890
- <Phone className="size-5 text-semantic-text-secondary" />
12924
+ <Phone className="size-4 sm:size-5 text-semantic-text-secondary" />
12891
12925
  )}
12892
12926
  </div>
12893
- <div className="flex items-center gap-2">
12894
- <Badge variant="outline" className="text-xs font-normal">
12895
- {BOT_TYPE_LABELS[bot.type]}
12927
+ <div className="flex items-center gap-1.5 sm:gap-2 min-w-0 shrink-0">
12928
+ <Badge variant="outline" className="text-xs font-normal shrink-0">
12929
+ {typeLabel}
12896
12930
  </Badge>
12897
12931
 
12898
- {/* Three-dot dropdown menu */}
12899
- <DropdownMenu>
12900
- <DropdownMenuTrigger asChild>
12901
- <button
12902
- type="button"
12903
- 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"
12904
- aria-label="More options"
12905
- >
12906
- <MoreVertical className="size-4" />
12907
- </button>
12908
- </DropdownMenuTrigger>
12909
- <DropdownMenuContent align="end" className="min-w-[160px]">
12910
- <DropdownMenuItem
12911
- className="flex items-center gap-3 px-4 py-3 text-sm cursor-pointer"
12912
- onClick={() => onEdit?.(bot.id)}
12913
- >
12914
- <Pencil className="size-4 text-semantic-text-muted shrink-0" />
12915
- <span>Edit</span>
12916
- </DropdownMenuItem>
12917
- <DropdownMenuItem
12918
- className="flex items-center gap-3 px-4 py-3 text-sm cursor-pointer"
12919
- onClick={() => onPublish?.(bot.id)}
12920
- >
12921
- <Play className="size-4 text-semantic-text-muted shrink-0" />
12922
- <span>Publish</span>
12923
- </DropdownMenuItem>
12924
- <DropdownMenuSeparator />
12925
- <DropdownMenuItem
12926
- 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"
12927
- onClick={() => onDelete?.(bot.id)}
12928
- >
12929
- <Trash2 className="size-4 shrink-0" />
12930
- <span>Delete</span>
12931
- </DropdownMenuItem>
12932
- </DropdownMenuContent>
12933
- </DropdownMenu>
12932
+ <span data-bot-card-action className="inline-flex" onClick={(e) => e.stopPropagation()}>
12933
+ <BotListAction
12934
+ align="end"
12935
+ onEdit={() => onEdit?.(bot.id)}
12936
+ onDelete={() => onDelete?.(bot.id)}
12937
+ />
12938
+ </span>
12934
12939
  </div>
12935
12940
  </div>
12936
12941
 
12937
12942
  {/* Bot name */}
12938
- <h3 className="m-0 text-base font-normal text-semantic-text-primary truncate mb-1">
12943
+ <h3 className="m-0 text-sm sm:text-base font-normal text-semantic-text-primary truncate mb-1 min-w-0">
12939
12944
  {bot.name}
12940
12945
  </h3>
12941
12946
 
12942
12947
  {/* Conversations count */}
12943
- <p className="m-0 text-sm text-semantic-text-muted mb-4">
12948
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted mb-3 sm:mb-4">
12944
12949
  {bot.conversationCount.toLocaleString()} Conversations
12945
12950
  </p>
12946
12951
 
12947
12952
  {/* Divider */}
12948
- <div className="border-t border-semantic-border-layout mb-3 mt-auto" />
12953
+ <div className="border-t border-semantic-border-layout mb-2 sm:mb-3 mt-auto" />
12949
12954
 
12950
12955
  {/* Last published */}
12951
- <div className="flex flex-col gap-1">
12952
- <span className="text-xs font-normal text-semantic-text-secondary uppercase tracking-[0.048px]">
12953
- Last Published
12954
- </span>
12955
- {bot.lastPublishedBy && bot.lastPublishedDate ? (
12956
- <p className="m-0 text-sm text-semantic-text-muted">
12957
- {bot.lastPublishedBy} | {bot.lastPublishedDate}
12956
+ <div className="flex flex-col gap-0.5 sm:gap-1 min-w-0">
12957
+ {bot.status === "draft" ? (
12958
+ <p className="m-0 text-xs font-normal text-semantic-text-secondary uppercase tracking-[0.048px] flex items-center justify-start gap-5">
12959
+ Last Published
12960
+ <span className="text-xs font-normal text-semantic-error-primary flex items-center gap-1.5 shrink-0">
12961
+ <span className="size-1.5 rounded-full bg-semantic-error-primary shrink-0" aria-hidden />
12962
+ Unpublished changes
12963
+ </span>
12958
12964
  </p>
12959
12965
  ) : (
12960
- <p className="m-0 text-sm text-semantic-text-muted">\u2014</p>
12966
+ <span className="text-xs font-normal text-semantic-text-secondary uppercase tracking-[0.048px]">
12967
+ Last Published
12968
+ </span>
12961
12969
  )}
12970
+ {bot.lastPublishedBy && bot.lastPublishedDate ? (
12971
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted truncate">
12972
+ {bot.lastPublishedBy} | {bot.lastPublishedDate}
12973
+ </p>
12974
+ ) : bot.status !== "draft" ? (
12975
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted">\u2014</p>
12976
+ ) : null}
12962
12977
  </div>
12963
12978
  </div>
12964
12979
  );
@@ -12980,7 +12995,7 @@ import {
12980
12995
  DialogTitle,
12981
12996
  } from "../dialog";
12982
12997
  import { Button } from "../button";
12983
- import type { CreateBotModalProps, BotType } from "./types";
12998
+ import { BOT_TYPE, type CreateBotModalProps, type BotType } from "./types";
12984
12999
 
12985
13000
  interface BotTypeOption {
12986
13001
  id: BotType;
@@ -13010,7 +13025,8 @@ export const CreateBotModal = React.forwardRef<
13010
13025
 
13011
13026
  const handleSubmit = () => {
13012
13027
  if (!name.trim()) return;
13013
- onSubmit?.({ name: name.trim(), type: selectedType });
13028
+ const typeValue = selectedType === "chatbot" ? BOT_TYPE.CHAT : BOT_TYPE.VOICE;
13029
+ onSubmit?.({ name: name.trim(), type: typeValue });
13014
13030
  setName("");
13015
13031
  setSelectedType("chatbot");
13016
13032
  };
@@ -13023,12 +13039,12 @@ export const CreateBotModal = React.forwardRef<
13023
13039
 
13024
13040
  return (
13025
13041
  <Dialog open={open} onOpenChange={onOpenChange}>
13026
- <DialogContent ref={ref} size="sm" className={cn("mx-4 sm:mx-auto", className)}>
13042
+ <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)}>
13027
13043
  <DialogHeader>
13028
13044
  <DialogTitle>Create AI bot</DialogTitle>
13029
13045
  </DialogHeader>
13030
13046
 
13031
- <div className="flex flex-col gap-6">
13047
+ <div className="flex flex-col gap-4 sm:gap-6">
13032
13048
  {/* Name field */}
13033
13049
  <div className="flex flex-col gap-1.5">
13034
13050
  <label
@@ -13059,7 +13075,7 @@ export const CreateBotModal = React.forwardRef<
13059
13075
  <span className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
13060
13076
  Select Bot Type
13061
13077
  </span>
13062
- <div className="flex gap-3">
13078
+ <div className="flex flex-col gap-3 sm:flex-row sm:gap-3">
13063
13079
  {BOT_TYPE_OPTIONS.map(({ id, label, description }) => {
13064
13080
  const isSelected = selectedType === id;
13065
13081
  return (
@@ -13068,7 +13084,7 @@ export const CreateBotModal = React.forwardRef<
13068
13084
  type="button"
13069
13085
  onClick={() => setSelectedType(id)}
13070
13086
  className={cn(
13071
- "flex flex-col items-start gap-2.5 p-3 rounded-lg border text-left flex-1 h-[134px] justify-center",
13087
+ "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",
13072
13088
  "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus",
13073
13089
  isSelected
13074
13090
  ? "bg-semantic-brand-surface border-semantic-brand shadow-sm"
@@ -13114,7 +13130,7 @@ export const CreateBotModal = React.forwardRef<
13114
13130
  </div>
13115
13131
 
13116
13132
  {/* Footer actions */}
13117
- <div className="flex gap-4 justify-end mt-2">
13133
+ <div className="flex flex-col-reverse gap-3 sm:flex-row sm:gap-4 justify-end mt-2">
13118
13134
  <Button variant="outline" onClick={handleClose}>
13119
13135
  Cancel
13120
13136
  </Button>
@@ -13137,9 +13153,12 @@ CreateBotModal.displayName = "CreateBotModal";
13137
13153
  {
13138
13154
  name: "bot-list.tsx",
13139
13155
  content: prefixTailwindClasses(`import * as React from "react";
13140
- import { Plus, Search } from "lucide-react";
13141
13156
  import { cn } from "../../../lib/utils";
13142
13157
  import { BotCard } from "./bot-card";
13158
+ import { BotListHeader } from "./bot-list-header";
13159
+ import { BotListSearch } from "./bot-list-search";
13160
+ import { BotListCreateCard } from "./bot-list-create-card";
13161
+ import { BotListGrid } from "./bot-list-grid";
13143
13162
  import { CreateBotModal } from "./create-bot-modal";
13144
13163
  import type { BotListProps } from "./types";
13145
13164
 
@@ -13147,6 +13166,7 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13147
13166
  (
13148
13167
  {
13149
13168
  bots = [],
13169
+ typeLabels,
13150
13170
  onCreateBot,
13151
13171
  onCreateBotSubmit,
13152
13172
  onBotEdit,
@@ -13155,7 +13175,10 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13155
13175
  onSearch,
13156
13176
  title = "AI Bot",
13157
13177
  subtitle = "Create & manage AI bots",
13178
+ searchPlaceholder = "Search bot...",
13179
+ createCardLabel = "Create new bot",
13158
13180
  className,
13181
+ ...props
13159
13182
  },
13160
13183
  ref
13161
13184
  ) => {
@@ -13168,64 +13191,41 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13168
13191
  };
13169
13192
 
13170
13193
  return (
13171
- <div ref={ref} className={cn("flex flex-col w-full", className)}>
13172
- {/* Page header */}
13173
- <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">
13174
- <div className="flex flex-col gap-1.5">
13175
- <h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px]">
13176
- {title}
13177
- </h1>
13178
- <p className="m-0 text-sm text-semantic-text-muted tracking-[0.035px]">
13179
- {subtitle}
13180
- </p>
13181
- </div>
13182
-
13183
- {/* Search bar */}
13184
- <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">
13185
- <Search className="size-[14px] text-semantic-text-muted shrink-0" />
13186
- <input
13187
- type="text"
13188
- value={searchQuery}
13189
- onChange={(e) => handleSearch(e.target.value)}
13190
- placeholder="Search bot..."
13191
- className="text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none w-full sm:w-[180px]"
13192
- />
13193
- </div>
13194
+ <div
13195
+ ref={ref}
13196
+ className={cn("flex flex-col w-full min-w-0 max-w-full overflow-x-hidden box-border", className)}
13197
+ {...props}
13198
+ >
13199
+ {/* Page header: title, subtitle, and search */}
13200
+ <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">
13201
+ <BotListHeader title={title} subtitle={subtitle} />
13202
+ <BotListSearch
13203
+ value={searchQuery}
13204
+ onSearch={handleSearch}
13205
+ placeholder={searchPlaceholder}
13206
+ />
13194
13207
  </div>
13195
13208
 
13196
- {/* Bot grid */}
13197
- <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
13198
- {/* Create new bot card */}
13199
- <button
13200
- type="button"
13209
+ {/* Bot grid: create card + bot cards */}
13210
+ <BotListGrid>
13211
+ <BotListCreateCard
13212
+ label={createCardLabel}
13201
13213
  onClick={() => {
13202
13214
  setCreateModalOpen(true);
13203
13215
  onCreateBot?.();
13204
13216
  }}
13205
- className={cn(
13206
- "flex flex-col items-center justify-center gap-3 p-2.5 rounded-[5px]",
13207
- "bg-semantic-info-surface-subtle border border-dashed border-[var(--color-primary-100)]",
13208
- "cursor-pointer hover:bg-semantic-bg-hover transition-colors min-h-[207px]",
13209
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus"
13210
- )}
13211
- >
13212
- <Plus className="size-4 text-semantic-text-secondary" />
13213
- <span className="text-sm font-semibold text-semantic-text-secondary tracking-[0.014px]">
13214
- Create new bot
13215
- </span>
13216
- </button>
13217
-
13218
- {/* Bot cards */}
13217
+ />
13219
13218
  {bots.map((bot) => (
13220
13219
  <BotCard
13221
13220
  key={bot.id}
13222
13221
  bot={bot}
13222
+ typeLabels={typeLabels}
13223
13223
  onEdit={onBotEdit}
13224
13224
  onPublish={onBotPublish}
13225
13225
  onDelete={onBotDelete}
13226
13226
  />
13227
13227
  ))}
13228
- </div>
13228
+ </BotListGrid>
13229
13229
 
13230
13230
  <CreateBotModal
13231
13231
  open={createModalOpen}
@@ -13241,52 +13241,365 @@ export const BotList = React.forwardRef<HTMLDivElement, BotListProps>(
13241
13241
  );
13242
13242
 
13243
13243
  BotList.displayName = "BotList";
13244
+ `, prefix)
13245
+ },
13246
+ {
13247
+ name: "bot-list-header.tsx",
13248
+ content: prefixTailwindClasses(`import * as React from "react";
13249
+ import { cn } from "../../../lib/utils";
13250
+ import type { BotListHeaderProps } from "./types";
13251
+
13252
+ export const BotListHeader = React.forwardRef<HTMLDivElement, BotListHeaderProps>(
13253
+ ({ title, subtitle, className, ...props }, ref) => (
13254
+ <div
13255
+ ref={ref}
13256
+ className={cn("flex flex-col gap-1.5 min-w-0 shrink", className)}
13257
+ {...props}
13258
+ >
13259
+ {title != null && (
13260
+ <h1 className="m-0 text-base font-semibold text-semantic-text-primary tracking-[0.064px] break-words sm:text-lg">
13261
+ {title}
13262
+ </h1>
13263
+ )}
13264
+ {subtitle != null && (
13265
+ <p className="m-0 text-xs sm:text-sm text-semantic-text-muted tracking-[0.035px] break-words">
13266
+ {subtitle}
13267
+ </p>
13268
+ )}
13269
+ </div>
13270
+ )
13271
+ );
13272
+
13273
+ BotListHeader.displayName = "BotListHeader";
13274
+ `, prefix)
13275
+ },
13276
+ {
13277
+ name: "bot-list-search.tsx",
13278
+ content: prefixTailwindClasses(`import * as React from "react";
13279
+ import { Search } from "lucide-react";
13280
+ import { cn } from "../../../lib/utils";
13281
+ import type { BotListSearchProps } from "./types";
13282
+
13283
+ export const BotListSearch = React.forwardRef<HTMLDivElement, BotListSearchProps>(
13284
+ (
13285
+ {
13286
+ value,
13287
+ placeholder = "Search bot...",
13288
+ onSearch,
13289
+ defaultValue,
13290
+ className,
13291
+ ...props
13292
+ },
13293
+ ref
13294
+ ) => {
13295
+ const [internalValue, setInternalValue] = React.useState(defaultValue ?? "");
13296
+ const isControlled = value !== undefined;
13297
+ const displayValue = isControlled ? value : internalValue;
13298
+
13299
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
13300
+ const next = e.target.value;
13301
+ if (!isControlled) setInternalValue(next);
13302
+ onSearch?.(next);
13303
+ };
13304
+
13305
+ return (
13306
+ <div
13307
+ ref={ref}
13308
+ className={cn(
13309
+ "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",
13310
+ "hover:border-semantic-border-input-focus focus-within:border-semantic-border-input-focus",
13311
+ "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",
13312
+ className
13313
+ )}
13314
+ {...props}
13315
+ >
13316
+ <Search className="size-[14px] text-semantic-text-muted shrink-0" />
13317
+ <input
13318
+ type="text"
13319
+ value={displayValue}
13320
+ onChange={handleChange}
13321
+ placeholder={placeholder}
13322
+ className="text-sm text-semantic-text-primary placeholder:text-semantic-text-muted bg-transparent outline-none w-full min-w-0"
13323
+ aria-label={placeholder}
13324
+ />
13325
+ </div>
13326
+ );
13327
+ }
13328
+ );
13329
+
13330
+ BotListSearch.displayName = "BotListSearch";
13331
+ `, prefix)
13332
+ },
13333
+ {
13334
+ name: "bot-list-create-card.tsx",
13335
+ content: prefixTailwindClasses(`import * as React from "react";
13336
+ import { Plus } from "lucide-react";
13337
+ import { cn } from "../../../lib/utils";
13338
+ import type { BotListCreateCardProps } from "./types";
13339
+
13340
+ export const BotListCreateCard = React.forwardRef<
13341
+ HTMLButtonElement,
13342
+ BotListCreateCardProps
13343
+ >(
13344
+ (
13345
+ {
13346
+ label = "Create new bot",
13347
+ onClick,
13348
+ className,
13349
+ ...props
13350
+ },
13351
+ ref
13352
+ ) => (
13353
+ <button
13354
+ ref={ref}
13355
+ type="button"
13356
+ onClick={onClick}
13357
+ className={cn(
13358
+ "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",
13359
+ "bg-semantic-info-surface-subtle border border-dashed border-semantic-border-layout",
13360
+ "cursor-pointer transition-colors hover:bg-semantic-bg-hover hover:border-semantic-border-input",
13361
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-semantic-border-focus",
13362
+ "self-stretch justify-self-stretch",
13363
+ className
13364
+ )}
13365
+ aria-label={label}
13366
+ {...props}
13367
+ >
13368
+ <Plus className="size-4 text-semantic-text-secondary shrink-0" />
13369
+ <span className="text-sm font-semibold leading-5 text-semantic-text-secondary text-center tracking-[0.014px]">
13370
+ {label}
13371
+ </span>
13372
+ </button>
13373
+ )
13374
+ );
13375
+
13376
+ BotListCreateCard.displayName = "BotListCreateCard";
13377
+ `, prefix)
13378
+ },
13379
+ {
13380
+ name: "bot-list-grid.tsx",
13381
+ content: prefixTailwindClasses(`import * as React from "react";
13382
+ import { cn } from "../../../lib/utils";
13383
+ import type { BotListGridProps } from "./types";
13384
+
13385
+ export const BotListGrid = React.forwardRef<HTMLDivElement, BotListGridProps>(
13386
+ ({ children, className, ...props }, ref) => (
13387
+ <div
13388
+ ref={ref}
13389
+ className={cn(
13390
+ "grid w-full min-w-0 max-w-full overflow-hidden gap-3 sm:gap-5 md:gap-6",
13391
+ "grid-cols-[repeat(auto-fill,minmax(min(100%,280px),1fr))]",
13392
+ className
13393
+ )}
13394
+ {...props}
13395
+ >
13396
+ {children}
13397
+ </div>
13398
+ )
13399
+ );
13400
+
13401
+ BotListGrid.displayName = "BotListGrid";
13402
+ `, prefix)
13403
+ },
13404
+ {
13405
+ name: "bot-list-action.tsx",
13406
+ content: prefixTailwindClasses(`import * as React from "react";
13407
+ import { MoreVertical, Pencil, Trash2 } from "lucide-react";
13408
+ import { cn } from "../../../lib/utils";
13409
+ import {
13410
+ DropdownMenu,
13411
+ DropdownMenuTrigger,
13412
+ DropdownMenuContent,
13413
+ DropdownMenuItem,
13414
+ DropdownMenuSeparator,
13415
+ } from "../dropdown-menu";
13416
+ import type { BotListActionProps } from "./types";
13417
+
13418
+ const defaultTrigger = (
13419
+ <button
13420
+ type="button"
13421
+ 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"
13422
+ aria-label="More options"
13423
+ >
13424
+ <MoreVertical className="size-4 shrink-0" />
13425
+ </button>
13426
+ );
13427
+
13428
+ export const BotListAction = React.forwardRef<HTMLDivElement, BotListActionProps>(
13429
+ (
13430
+ {
13431
+ onEdit,
13432
+ onDelete,
13433
+ trigger = defaultTrigger,
13434
+ align = "end",
13435
+ className,
13436
+ ...props
13437
+ },
13438
+ ref
13439
+ ) => {
13440
+ return (
13441
+ <div ref={ref} className={cn("inline-flex", className)} {...props}>
13442
+ <DropdownMenu>
13443
+ <DropdownMenuTrigger asChild>{trigger}</DropdownMenuTrigger>
13444
+ <DropdownMenuContent
13445
+ align={align}
13446
+ 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"
13447
+ >
13448
+ <DropdownMenuItem
13449
+ 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"
13450
+ onSelect={(e) => {
13451
+ e.preventDefault();
13452
+ onEdit?.();
13453
+ }}
13454
+ >
13455
+ <Pencil className="size-4 shrink-0 text-semantic-text-primary" />
13456
+ <span>Edit</span>
13457
+ </DropdownMenuItem>
13458
+ <DropdownMenuSeparator className="my-1 bg-semantic-border-layout" />
13459
+ <DropdownMenuItem
13460
+ 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"
13461
+ onSelect={(e) => {
13462
+ e.preventDefault();
13463
+ onDelete?.();
13464
+ }}
13465
+ >
13466
+ <Trash2 className="size-4 shrink-0 text-semantic-error-primary" />
13467
+ <span>Delete</span>
13468
+ </DropdownMenuItem>
13469
+ </DropdownMenuContent>
13470
+ </DropdownMenu>
13471
+ </div>
13472
+ );
13473
+ }
13474
+ );
13475
+
13476
+ BotListAction.displayName = "BotListAction";
13244
13477
  `, prefix)
13245
13478
  },
13246
13479
  {
13247
13480
  name: "types.ts",
13248
- content: prefixTailwindClasses(`export type BotType = "chatbot" | "voicebot";
13481
+ content: prefixTailwindClasses(`import type * as React from "react";
13482
+
13483
+ export const BOT_TYPE = {
13484
+ CHAT: 1,
13485
+ VOICE: 2,
13486
+ } as const;
13487
+
13488
+ export type BOT_TYPE = (typeof BOT_TYPE)[keyof typeof BOT_TYPE];
13489
+
13490
+ export type BotType = "chatbot" | "voicebot";
13491
+
13492
+ export type BotStatus = "draft" | "published";
13249
13493
 
13494
+ /**
13495
+ * Single bot shape for both Chatbot and Voicebot.
13496
+ * Use the same BotCard for both; set type to "chatbot" or "voicebot" and pass all data via this prop.
13497
+ */
13250
13498
  export interface Bot {
13251
13499
  id: string;
13252
13500
  name: string;
13501
+ /** "chatbot" | "voicebot" \u2014 determines icon and default badge label; all other data is from this object */
13253
13502
  type: BotType;
13254
13503
  conversationCount: number;
13255
13504
  lastPublishedBy?: string;
13256
13505
  lastPublishedDate?: string;
13506
+ /** Optional custom label for the type badge (overrides typeLabels and default "Chatbot"/"Voicebot") */
13507
+ typeLabel?: string;
13508
+ /** When "draft", card shows "Unpublished changes" with red indicator in the Last Published section */
13509
+ status?: BotStatus;
13257
13510
  }
13258
13511
 
13259
- export interface BotCardProps {
13512
+ export interface BotCardProps
13513
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
13514
+ /** Single bot object: pass chatbot or voicebot data here; card renders based on bot.type and bot fields */
13260
13515
  bot: Bot;
13516
+ /** Override labels for bot types (e.g. { chatbot: "Chat", voicebot: "Voice" }). Ignored if bot.typeLabel is set. */
13517
+ typeLabels?: Partial<Record<BotType, string>>;
13261
13518
  /** Called when Edit action is selected */
13262
13519
  onEdit?: (botId: string) => void;
13263
13520
  /** Called when Publish action is selected */
13264
13521
  onPublish?: (botId: string) => void;
13265
13522
  /** Called when Delete action is selected */
13266
13523
  onDelete?: (botId: string) => void;
13267
- className?: string;
13268
13524
  }
13269
13525
 
13270
13526
  export interface CreateBotModalProps {
13271
13527
  open: boolean;
13272
13528
  onOpenChange: (open: boolean) => void;
13273
- onSubmit?: (data: { name: string; type: BotType }) => void;
13529
+ /** Called with name and BOT_TYPE (CHAT = 1, VOICE = 2) when user submits */
13530
+ onSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13274
13531
  className?: string;
13275
13532
  }
13276
13533
 
13277
- export interface BotListProps {
13534
+ export interface BotListHeaderProps
13535
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
13536
+ /** Page title */
13537
+ title?: string;
13538
+ /** Optional subtitle below the title */
13539
+ subtitle?: string;
13540
+ }
13541
+
13542
+ export interface BotListSearchProps
13543
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
13544
+ /** Controlled value (use with onSearch) */
13545
+ value?: string;
13546
+ /** Placeholder text */
13547
+ placeholder?: string;
13548
+ /** Called when the search value changes */
13549
+ onSearch?: (query: string) => void;
13550
+ /** Uncontrolled: default value */
13551
+ defaultValue?: string;
13552
+ }
13553
+
13554
+ export interface BotListCreateCardProps
13555
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
13556
+ /** Label for the create card (e.g. "Create new bot") */
13557
+ label?: string;
13558
+ }
13559
+
13560
+ export interface BotListGridProps
13561
+ extends React.HTMLAttributes<HTMLDivElement> {
13562
+ children: React.ReactNode;
13563
+ }
13564
+
13565
+ export interface BotListActionProps
13566
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
13567
+ /** Called when Edit is selected */
13568
+ onEdit?: () => void;
13569
+ /** Called when Delete is selected */
13570
+ onDelete?: () => void;
13571
+ /** Custom trigger element; defaults to three-dot icon button */
13572
+ trigger?: React.ReactNode;
13573
+ /** Content alignment relative to trigger */
13574
+ align?: "start" | "center" | "end";
13575
+ }
13576
+
13577
+ export interface BotListProps
13578
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "title" | "children"> {
13579
+ /** List of bots to display */
13278
13580
  bots?: Bot[];
13581
+ /** Override type badge labels for all cards (e.g. { chatbot: "Chat", voicebot: "Voice" }). Per-bot bot.typeLabel still wins. */
13582
+ typeLabels?: Partial<Record<BotType, string>>;
13279
13583
  /** Called when the "Create new bot" card is clicked (modal opens) */
13280
13584
  onCreateBot?: () => void;
13281
- /** Called when the Create Bot modal is submitted with the new bot data */
13282
- onCreateBotSubmit?: (data: { name: string; type: BotType }) => void;
13585
+ /** Called when the Create Bot modal is submitted with the new bot data (type is BOT_TYPE: CHAT = 1, VOICE = 2) */
13586
+ onCreateBotSubmit?: (data: { name: string; type: BOT_TYPE }) => void;
13587
+ /** Called when user selects Edit on a bot (card click or menu) */
13283
13588
  onBotEdit?: (botId: string) => void;
13589
+ /** Called when user selects Publish on a bot (menu; optional) */
13284
13590
  onBotPublish?: (botId: string) => void;
13591
+ /** Called when user selects Delete on a bot */
13285
13592
  onBotDelete?: (botId: string) => void;
13593
+ /** Called when the search query changes */
13286
13594
  onSearch?: (query: string) => void;
13595
+ /** Page title (default: "AI Bot") */
13287
13596
  title?: string;
13597
+ /** Page subtitle (default: "Create & manage AI bots") */
13288
13598
  subtitle?: string;
13289
- className?: string;
13599
+ /** Placeholder for the search input (default: "Search bot...") */
13600
+ searchPlaceholder?: string;
13601
+ /** Label for the create-new-bot card (default: "Create new bot") */
13602
+ createCardLabel?: string;
13290
13603
  }
13291
13604
  `, prefix)
13292
13605
  },
@@ -13299,7 +13612,23 @@ export { CreateBotModal } from "./create-bot-modal";
13299
13612
  export type { CreateBotModalProps } from "./types";
13300
13613
 
13301
13614
  export { BotList } from "./bot-list";
13302
- export type { BotListProps, Bot, BotType } from "./types";
13615
+ export { BotListHeader } from "./bot-list-header";
13616
+ export { BotListSearch } from "./bot-list-search";
13617
+ export { BotListCreateCard } from "./bot-list-create-card";
13618
+ export { BotListGrid } from "./bot-list-grid";
13619
+ export { BotListAction } from "./bot-list-action";
13620
+ export { BOT_TYPE } from "./types";
13621
+ export type {
13622
+ BotListProps,
13623
+ BotListHeaderProps,
13624
+ BotListSearchProps,
13625
+ BotListCreateCardProps,
13626
+ BotListGridProps,
13627
+ BotListActionProps,
13628
+ Bot,
13629
+ BotType,
13630
+ BotStatus,
13631
+ } from "./types";
13303
13632
  `, prefix)
13304
13633
  }
13305
13634
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myoperator-ui",
3
- "version": "0.0.211",
3
+ "version": "0.0.212",
4
4
  "description": "CLI for adding myOperator UI components to your project",
5
5
  "type": "module",
6
6
  "exports": "./dist/index.js",