@catalystsoftware/ui 1.0.15 → 1.0.17

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/README.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  A comprehensive React component library with 100+ production-ready components for building modern web applications.
4
4
 
5
+ ## UPDATE
6
+
7
+ Adding a new line option 'Configure Import Call'. Configures your package.json and tsconfig.json files to allow the use of '#icons' and '#catalyst' in-place of the traditional '~/components/catalyst-ui' and '@catalystsoftware/icons'
8
+
9
+ ```javascript
10
+ import { Command, CommandGroup, CommandItem, CommandList, cn } from '#catalyst'
11
+ import { X } from '#icons'
12
+ ```
13
+ Selecting the full installation will also configure your project for this use as well.
14
+
5
15
  ## Installation
6
16
 
7
17
  ### Quick Start
@@ -101,7 +111,7 @@ Catalyst UI automatically detects and uses your package manager:
101
111
 
102
112
  ### Config File (Optional)
103
113
 
104
- Create a `catalyst.config.jsonc` file in your project root to customize installation behavior:
114
+ Create a `config.catalyst` file in your project root to customize installation behavior:
105
115
 
106
116
  ```bash
107
117
  bunx @catalystsoftware/ui
@@ -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 = () => (