@catalystsoftware/ui 1.0.15 → 1.0.16

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.
@@ -1,4 +1,340 @@
1
1
  // @dev app/components/catalyst-ui/data/chat-data.tsx:12
2
+ export interface Character {
3
+ id?: string | number;
4
+ emoji: string;
5
+ name: string;
6
+ online: boolean;
7
+ backgroundColor?: string;
8
+ gradientFrom?: string;
9
+ gradientTo?: string;
10
+ gradientColors?: string;
11
+ avatar?: string;
12
+ }
13
+ export interface MessageDockProps {
14
+ characters?: Character[];
15
+ onMessageSend?: (message: string, character: Character, characterIndex: number) => void;
16
+ onCharacterSelect?: (character: Character, characterIndex: number) => void;
17
+ onDockToggle?: (isExpanded: boolean) => void;
18
+ className?: string;
19
+ expandedWidth?: number;
20
+ position?: "bottom" | "top";
21
+ showSparkleButton?: boolean;
22
+ showMenuButton?: boolean;
23
+ enableAnimations?: boolean;
24
+ animationDuration?: number;
25
+ placeholder?: (characterName: string) => string;
26
+ theme?: "light" | "dark" | "auto";
27
+ autoFocus?: boolean;
28
+ closeOnClickOutside?: boolean;
29
+ closeOnEscape?: boolean;
30
+ closeOnSend?: boolean;
31
+ }
32
+ const defaultCharacters: Character[] = [
33
+ { emoji: "✨", name: "Sparkle", online: false },
34
+ {
35
+ emoji: "🧙‍♂️",
36
+ name: "Wizard",
37
+ online: true,
38
+ backgroundColor: "bg-emerald-200 dark:bg-emerald-300",
39
+ gradientFrom: "from-emerald-200",
40
+ gradientTo: "to-emerald-50",
41
+ gradientColors: "#a7f3d0, #ecfdf5"
42
+ },
43
+ {
44
+ emoji: "🦄",
45
+ name: "Unicorn",
46
+ online: true,
47
+ backgroundColor: "bg-violet-200 dark:bg-violet-300",
48
+ gradientFrom: "from-violet-200",
49
+ gradientTo: "to-violet-50",
50
+ gradientColors: "#c4b5fd, #f5f3ff"
51
+ },
52
+ {
53
+ emoji: "🐵",
54
+ name: "Monkey",
55
+ online: true,
56
+ backgroundColor: "bg-amber-200 dark:bg-amber-300",
57
+ gradientFrom: "from-amber-200",
58
+ gradientTo: "to-amber-50",
59
+ gradientColors: "#fde68a, #fffbeb"
60
+ },
61
+ {
62
+ emoji: "🤖",
63
+ name: "Robot",
64
+ online: false,
65
+ backgroundColor: "bg-rose-200 dark:bg-rose-300",
66
+ gradientFrom: "from-rose-200",
67
+ gradientTo: "to-rose-50",
68
+ gradientColors: "#fecaca, #fef2f2"
69
+ },
70
+ ];
71
+ const getGradientColors = (character: Character) => {
72
+ return character.gradientColors || "#86efac, #dcfce7";
73
+ };
74
+ export function MessageDock({ characters = defaultCharacters, onMessageSend, onCharacterSelect, onDockToggle, className, expandedWidth = 448, position = "bottom", showSparkleButton = true, showMenuButton = true, enableAnimations = true, animationDuration = 1, placeholder = (name: string) => `Message ${name}...`, theme = "light", autoFocus = true, closeOnClickOutside = true, closeOnEscape = true, closeOnSend = true, }: MessageDockProps) {
75
+ const shouldReduceMotion = useReducedMotion();
76
+ const [expandedCharacter, setExpandedCharacter] = useState<number | null>(null);
77
+ const [messageInput, setMessageInput] = useState("");
78
+ const dockRef = useRef<HTMLDivElement>(null);
79
+ const [collapsedWidth, setCollapsedWidth] = useState<number>(266);
80
+ const [hasInitialized, setHasInitialized] = useState(false);
81
+ useEffect(() => {
82
+ if (dockRef.current && !hasInitialized) {
83
+ const width = dockRef.current.offsetWidth;
84
+ if (width > 0) {
85
+ setCollapsedWidth(width);
86
+ setHasInitialized(true);
87
+ }
88
+ }
89
+ }, [hasInitialized]);
90
+ useEffect(() => {
91
+ if (!closeOnClickOutside)
92
+ return;
93
+ const handleClickOutside = (event: MouseEvent) => {
94
+ if (dockRef.current && !dockRef.current.contains(event.target as Node)) {
95
+ setExpandedCharacter(null);
96
+ setMessageInput("");
97
+ onDockToggle?.(false);
98
+ }
99
+ };
100
+ document.addEventListener("mousedown", handleClickOutside);
101
+ return () => {
102
+ document.removeEventListener("mousedown", handleClickOutside);
103
+ };
104
+ }, [closeOnClickOutside, onDockToggle]);
105
+ const containerVariants = {
106
+ hidden: {
107
+ opacity: 0,
108
+ y: 100,
109
+ scale: 0.8,
110
+ },
111
+ visible: {
112
+ opacity: 1,
113
+ y: 0,
114
+ scale: 1,
115
+ transition: {
116
+ type: "spring" as const,
117
+ stiffness: 300,
118
+ damping: 30,
119
+ mass: 0.8,
120
+ staggerChildren: 0.1,
121
+ delayChildren: 0.2,
122
+ },
123
+ }
124
+ };
125
+ const hoverAnimation = shouldReduceMotion
126
+ ? { scale: 1.02 }
127
+ : {
128
+ scale: 1.05,
129
+ y: -8,
130
+ transition: {
131
+ type: "spring",
132
+ stiffness: 400,
133
+ damping: 25,
134
+ }
135
+ };
136
+ const handleCharacterClick = (index: number) => {
137
+ const character = characters[index];
138
+ if (expandedCharacter === index) {
139
+ setExpandedCharacter(null);
140
+ setMessageInput("");
141
+ onDockToggle?.(false);
142
+ }
143
+ else {
144
+ setExpandedCharacter(index);
145
+ onCharacterSelect?.(character, index);
146
+ onDockToggle?.(true);
147
+ }
148
+ };
149
+ const handleSendMessage = () => {
150
+ if (messageInput.trim() && expandedCharacter !== null) {
151
+ const character = characters[expandedCharacter];
152
+ onMessageSend?.(messageInput, character, expandedCharacter);
153
+ setMessageInput("");
154
+ if (closeOnSend) {
155
+ setExpandedCharacter(null);
156
+ onDockToggle?.(false);
157
+ }
158
+ }
159
+ };
160
+ const selectedCharacter = expandedCharacter !== null ? characters[expandedCharacter] : null;
161
+ const isExpanded = expandedCharacter !== null;
162
+ const defaultPositionClasses = position === "top"
163
+ ? "fixed top-6 left-1/2 -translate-x-1/2 z-50"
164
+ : "fixed bottom-6 left-1/2 -translate-x-1/2 z-50";
165
+ return (<motion.div ref={dockRef} className={cn(
166
+ // Only apply default positioning if not overridden by className
167
+ !className?.includes('relative') && !className?.includes('absolute') ? defaultPositionClasses : '', className)} initial={enableAnimations ? "hidden" : "visible"} animate="visible" variants={enableAnimations ? containerVariants : {}}>
168
+ <motion.div className="rounded-full px-4 py-2 shadow-2xl border border-border bg-background/95 backdrop-blur-md" animate={{
169
+ width: isExpanded ? expandedWidth : collapsedWidth,
170
+ background: isExpanded && selectedCharacter
171
+ ? `linear-gradient(to right, ${getGradientColors(selectedCharacter)})`
172
+ : "hsl(var(--background))"
173
+ }} transition={enableAnimations ? {
174
+ type: "spring",
175
+ stiffness: isExpanded ? 300 : 500,
176
+ damping: isExpanded ? 30 : 35,
177
+ mass: isExpanded ? 0.8 : 0.6,
178
+ background: {
179
+ duration: 0.2 * animationDuration,
180
+ ease: "easeInOut"
181
+ }
182
+ } : { duration: 0 }}>
183
+ <div className="flex items-center gap-2 relative">
184
+ {showSparkleButton && (<motion.div className="flex items-center justify-center" animate={{
185
+ opacity: isExpanded ? 0 : 1,
186
+ x: isExpanded ? -20 : 0,
187
+ scale: isExpanded ? 0.8 : 1
188
+ }} transition={{
189
+ type: "spring",
190
+ stiffness: 400,
191
+ damping: 30,
192
+ delay: isExpanded ? 0 : 0
193
+ }}>
194
+ <motion.button className="w-12 h-12 flex items-center justify-center cursor-pointer" whileHover={!isExpanded
195
+ ? {
196
+ scale: 1.02,
197
+ y: -2,
198
+ transition: {
199
+ type: "spring",
200
+ stiffness: 400,
201
+ damping: 25,
202
+ }
203
+ }
204
+ : undefined} whileTap={{ scale: 0.95 }} aria-label="Sparkle">
205
+ <span className="text-2xl">✨</span>
206
+ </motion.button>
207
+ </motion.div>)}
208
+ <motion.div className="w-px h-6 bg-gray-300 mr-2 -ml-2" animate={{
209
+ opacity: isExpanded ? 0 : 1,
210
+ scaleY: isExpanded ? 0 : 1
211
+ }} transition={{
212
+ type: "spring",
213
+ stiffness: 300,
214
+ damping: 30,
215
+ delay: isExpanded ? 0 : 0.3
216
+ }}/>
217
+ {characters.slice(1, -1).map((character, index) => {
218
+ const actualIndex = index + 1;
219
+ const isSelected = expandedCharacter === actualIndex;
220
+ return (<motion.div key={character.name} className={cn("relative", isSelected && isExpanded && "absolute left-1 top-1 z-20")} style={{
221
+ width: isSelected && isExpanded ? 0 : "auto",
222
+ minWidth: isSelected && isExpanded ? 0 : "auto",
223
+ overflow: "visible"
224
+ }} animate={{
225
+ opacity: isExpanded && !isSelected ? 0 : 1,
226
+ y: isExpanded && !isSelected ? 60 : 0,
227
+ scale: isExpanded && !isSelected ? 0.8 : 1,
228
+ x: isSelected && isExpanded ? 0 : 0
229
+ }} transition={{
230
+ type: "spring",
231
+ stiffness: 400,
232
+ damping: 30,
233
+ delay: isExpanded && !isSelected
234
+ ? index * 0.05
235
+ : isExpanded
236
+ ? 0.1
237
+ : 0
238
+ }}>
239
+ <motion.button className={cn("relative w-10 h-10 rounded-full flex items-center justify-center text-xl cursor-pointer", isSelected && isExpanded
240
+ ? "bg-white/90"
241
+ : character.backgroundColor)} onClick={() => handleCharacterClick(actualIndex)} whileHover={!isExpanded ? hoverAnimation : { scale: 1.05 }} whileTap={{ scale: 0.95 }} aria-label={`Message ${character.name}`}>
242
+ <span className="text-2xl">{character.emoji}</span>
243
+ {character.online && (<motion.div className="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-white rounded-full" initial={{ scale: 0 }} animate={{ scale: isExpanded && !isSelected ? 0 : 1 }} transition={{
244
+ delay: isExpanded
245
+ ? isSelected
246
+ ? 0.3
247
+ : 0
248
+ : (index + 1) * 0.1 + 0.5,
249
+ type: "spring",
250
+ stiffness: 500,
251
+ damping: 30
252
+ }}/>)}
253
+ </motion.button>
254
+ </motion.div>);
255
+ })}
256
+ <AnimatePresence>
257
+ {isExpanded && (<motion.input type="text" value={messageInput} onChange={(e) => setMessageInput(e.target.value)} onKeyDown={(e) => {
258
+ if (e.key === "Enter") {
259
+ handleSendMessage();
260
+ }
261
+ if (e.key === "Escape" && closeOnEscape) {
262
+ setExpandedCharacter(null);
263
+ setMessageInput("");
264
+ onDockToggle?.(false);
265
+ }
266
+ }} placeholder={placeholder(selectedCharacter?.name || "")} className="w-[300px] absolute left-14 right-0 bg-transparent border-none outline-none text-sm font-medium z-50 text-foreground placeholder-muted-foreground" autoFocus={autoFocus} initial={{ opacity: 0, x: 20 }} animate={{
267
+ opacity: 1,
268
+ x: 0,
269
+ transition: {
270
+ delay: 0.2,
271
+ type: "spring",
272
+ stiffness: 400,
273
+ damping: 30,
274
+ }
275
+ }} exit={{
276
+ opacity: 0,
277
+ transition: {
278
+ duration: 0.1,
279
+ ease: "easeOut"
280
+ }
281
+ }}/>)}
282
+ </AnimatePresence>
283
+ <motion.div className="w-px h-6 bg-gray-300 ml-2 -mr-2" animate={{
284
+ opacity: isExpanded ? 0 : 1,
285
+ scaleY: isExpanded ? 0 : 1
286
+ }} transition={{
287
+ type: "spring",
288
+ stiffness: 300,
289
+ damping: 30,
290
+ delay: isExpanded ? 0 : 0
291
+ }}/>
292
+ {showMenuButton && (<motion.div className={cn("flex items-center justify-center z-20", isExpanded && "absolute right-0")} transition={{ type: "spring", stiffness: 400, damping: 30 }}>
293
+ <AnimatePresence mode="wait">
294
+ {!isExpanded ? (<motion.button key="menu" className="w-12 h-12 flex items-center justify-center cursor-pointer" whileHover={{
295
+ scale: 1.02,
296
+ y: -2,
297
+ transition: {
298
+ type: "spring",
299
+ stiffness: 400,
300
+ damping: 25,
301
+ }
302
+ }} whileTap={{ scale: 0.95 }} aria-label="Menu" initial={{ opacity: 0, rotate: -90 }} animate={{ opacity: 1, rotate: 0 }} exit={{ opacity: 0, rotate: 90 }} transition={{ type: "spring", stiffness: 400, damping: 30 }}>
303
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-muted-foreground">
304
+ <line x1="3" y1="6" x2="21" y2="6"/>
305
+ <line x1="3" y1="12" x2="21" y2="12"/>
306
+ <line x1="3" y1="18" x2="21" y2="18"/>
307
+ </svg>
308
+ </motion.button>) : (<motion.button key="send" onClick={handleSendMessage} className="w-10 h-10 flex items-center justify-center rounded-full bg-background/90 hover:bg-background transition-colors disabled:opacity-50 cursor-pointer relative z-30 border border-border/50" whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} disabled={!messageInput.trim()} initial={{ opacity: 0, scale: 0, rotate: -90 }} animate={{
309
+ opacity: 1,
310
+ scale: 1,
311
+ rotate: 0,
312
+ transition: {
313
+ delay: 0.25,
314
+ type: "spring",
315
+ stiffness: 400,
316
+ damping: 30,
317
+ }
318
+ }} exit={{
319
+ opacity: 0,
320
+ scale: 0,
321
+ rotate: 90,
322
+ transition: {
323
+ duration: 0.1,
324
+ ease: "easeIn"
325
+ }
326
+ }}>
327
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-muted-foreground">
328
+ <path d="m22 2-7 20-4-9-9-4z"/>
329
+ <path d="M22 2 11 13"/>
330
+ </svg>
331
+ </motion.button>)}
332
+ </AnimatePresence>
333
+ </motion.div>)}
334
+ </div>
335
+ </motion.div>
336
+ </motion.div>);
337
+ }
2
338
  export const chatData = [
3
339
  {
4
340
  name: "message dock",
@@ -289,80 +289,7 @@ export function TerminalDemo() {
289
289
  </AnimatedSpan>
290
290
 
291
291
  <AnimatedSpan className="text-green-500">
292
- ✔ Validating alias.
293
- </AnimatedSpan>
294
-
295
- <AnimatedSpan className="text-green-500">
296
- ✔ Writing components.json.
297
- </AnimatedSpan>
298
-
299
- <AnimatedSpan className="text-green-500">
300
- ✔ Checking registry.
301
- </AnimatedSpan>
302
-
303
- <AnimatedSpan className="text-green-500">
304
- ✔ Updating tailwind.config.ts
305
- </AnimatedSpan>
306
-
307
- <AnimatedSpan className="text-green-500">
308
- ✔ Updating app/globals.css
309
- </AnimatedSpan>
310
-
311
- <AnimatedSpan className="text-green-500">
312
- ✔ Installing dependencies.
313
- </AnimatedSpan>
314
-
315
- <AnimatedSpan className="text-blue-500">
316
- <span> Updated 1 file:</span>
317
- <span className="pl-2">- lib/utils.ts</span>
318
- </AnimatedSpan>
319
-
320
- <TypingAnimation className="text-muted-foreground">
321
- Success! Project initialization completed.
322
- </TypingAnimation>
323
-
324
- <TypingAnimation className="text-muted-foreground">
325
- You may now add components.
326
- </TypingAnimation>
327
- </Terminal>
328
- )
329
- }
330
- `,
331
- premium: true,
332
- category: "Code",
333
- tags: ["animation", "typing", "terminal", "sequence"],
334
- features: ["Typing Animation", "Sequenced", "Interactive", "TypeScript"],
335
- dependencies: ["motion", "react"],
336
- props: {
337
- "Terminal": [
338
- { name: "className", type: "string", default: "null" },
339
- { name: "sequence", type: "boolean", default: "true" },
340
- { name: "startOnView", type: "boolean", default: "true" }
341
- ],
342
- "AnimatedSpan": [
343
- { name: "className", type: "string", default: "null" },
344
- { name: "delay", type: "number", default: "0" },
345
- { name: "startOnView", type: "boolean", default: "false" }
346
- ],
347
- "TypingAnimation": [
348
- { name: "className", type: "string", default: "null" },
349
- { name: "duration", type: "number", default: "60" },
350
- { name: "delay", type: "number", default: "0" },
351
- { name: "as", type: "React.ElementType", default: "span" },
352
- { name: "startOnView", type: "boolean", default: "true" }
353
- ]
354
- },
355
- desc: null,
356
- status: null,
357
- lastUpdated: null
358
- },
359
- {
360
- name: "Sandbox",
361
- value: "sandbox",
362
- importPath: "~/components/catalyst-ui/code/sandbox",
363
- multiImport: "SandboxTabs, SandboxTabsList, SandboxTabsTrigger, SandboxTabsContent, SandboxCodeEditor, SandboxConsole, SandboxFileExplorer, SandboxLayout, SandboxPreview, SandboxProvider",
364
- basicusage: `
365
-
292
+ ✔ Validating
366
293
 
367
294
 
368
295
  const Example = () => (