@oalacea/chaosui 0.1.0 → 0.5.0

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 (139) hide show
  1. package/bin/cli.js +105 -13
  2. package/components/backgrounds/glow-orbs/index.tsx +1 -1
  3. package/components/buttons/chaos-button/chaos-button.module.css +3 -2
  4. package/components/buttons/cta-brutal/cta-brutal.module.css +81 -0
  5. package/components/buttons/cta-brutal/index.tsx +56 -0
  6. package/components/buttons/dead-button/dead-button.module.css +111 -0
  7. package/components/buttons/dead-button/index.tsx +47 -0
  8. package/components/buttons/deeper-button/deeper-button.module.css +76 -0
  9. package/components/buttons/deeper-button/index.tsx +51 -0
  10. package/components/buttons/dual-choice/dual-choice.module.css +90 -0
  11. package/components/buttons/dual-choice/index.tsx +54 -0
  12. package/components/buttons/glitch-button/glitch-button.module.css +7 -7
  13. package/components/buttons/tension-bar/index.tsx +79 -0
  14. package/components/buttons/tension-bar/tension-bar.module.css +105 -0
  15. package/components/chaos-vars.css +27 -0
  16. package/components/cyber/cyber-avatar/css/cyber-avatar.module.css +60 -0
  17. package/components/cyber/cyber-avatar/css/index.tsx +28 -0
  18. package/components/cyber/cyber-avatar/tailwind/index.tsx +46 -0
  19. package/components/cyber/cyber-input/css/cyber-input.module.css +87 -0
  20. package/components/cyber/cyber-input/css/index.tsx +49 -0
  21. package/components/cyber/cyber-input/tailwind/index.tsx +55 -0
  22. package/components/cyber/cyber-loader/css/cyber-loader.module.css +102 -0
  23. package/components/cyber/cyber-loader/css/index.tsx +58 -0
  24. package/components/cyber/cyber-loader/tailwind/index.tsx +63 -0
  25. package/components/cyber/cyber-modal/css/cyber-modal.module.css +124 -0
  26. package/components/cyber/cyber-modal/css/index.tsx +75 -0
  27. package/components/cyber/cyber-modal/tailwind/index.tsx +87 -0
  28. package/components/cyber/cyber-slider/css/cyber-slider.module.css +61 -0
  29. package/components/cyber/cyber-slider/css/index.tsx +41 -0
  30. package/components/cyber/cyber-slider/tailwind/index.tsx +51 -0
  31. package/components/cyber/cyber-tooltip/css/cyber-tooltip.module.css +67 -0
  32. package/components/cyber/cyber-tooltip/css/index.tsx +36 -0
  33. package/components/cyber/cyber-tooltip/tailwind/index.tsx +48 -0
  34. package/components/decorative/coffee-stain/coffee-stain.module.css +24 -0
  35. package/components/decorative/coffee-stain/index.tsx +55 -0
  36. package/components/decorative/ornaments/index.tsx +51 -0
  37. package/components/decorative/ornaments/ornaments.module.css +33 -0
  38. package/components/decorative/rune-symbols/index.tsx +55 -0
  39. package/components/decorative/rune-symbols/rune-symbols.module.css +22 -0
  40. package/components/effects/glitch-image/css/glitch-image.module.css +64 -0
  41. package/components/effects/glitch-image/css/index.tsx +25 -0
  42. package/components/effects/glitch-image/tailwind/index.tsx +49 -0
  43. package/components/effects/glowing-border/css/glowing-border.module.css +73 -0
  44. package/components/effects/glowing-border/css/index.tsx +45 -0
  45. package/components/effects/glowing-border/tailwind/index.tsx +40 -0
  46. package/components/effects/screen-distortion/screen-distortion.module.css +2 -2
  47. package/components/effects/warning-tape/index.tsx +4 -4
  48. package/components/effects/warning-tape/warning-tape.module.css +2 -0
  49. package/components/layout/data-grid/css/data-grid.module.css +52 -0
  50. package/components/layout/data-grid/css/index.tsx +76 -0
  51. package/components/layout/data-grid/tailwind/index.tsx +74 -0
  52. package/components/layout/hologram-card/css/hologram-card.module.css +102 -0
  53. package/components/layout/hologram-card/css/index.tsx +46 -0
  54. package/components/layout/hologram-card/tailwind/index.tsx +61 -0
  55. package/components/layout/horizontal-scroll/horizontal-scroll.module.css +30 -0
  56. package/components/layout/horizontal-scroll/index.tsx +78 -0
  57. package/components/layout/spec-grid/index.tsx +56 -0
  58. package/components/layout/spec-grid/spec-grid.module.css +21 -0
  59. package/components/layout/tower-pricing/index.tsx +56 -0
  60. package/components/layout/tower-pricing/tower-pricing.module.css +27 -0
  61. package/components/layout/tracklist/index.tsx +45 -0
  62. package/components/layout/tracklist/tracklist.module.css +24 -0
  63. package/components/layout/void-frame/index.tsx +32 -0
  64. package/components/layout/void-frame/void-frame.module.css +38 -0
  65. package/components/navigation/brutal-nav/brutal-nav.module.css +85 -0
  66. package/components/navigation/brutal-nav/index.tsx +71 -0
  67. package/components/navigation/hexagon-menu/css/hexagon-menu.module.css +55 -0
  68. package/components/navigation/hexagon-menu/css/index.tsx +35 -0
  69. package/components/navigation/hexagon-menu/tailwind/index.tsx +53 -0
  70. package/components/navigation/progress-dots/index.tsx +55 -0
  71. package/components/navigation/progress-dots/progress-dots.module.css +91 -0
  72. package/components/navigation/scattered-nav/index.tsx +59 -0
  73. package/components/navigation/scattered-nav/scattered-nav.module.css +113 -0
  74. package/components/navigation/scroll-indicator/index.tsx +58 -0
  75. package/components/navigation/scroll-indicator/scroll-indicator.module.css +82 -0
  76. package/components/navigation/vertical-nav/index.tsx +59 -0
  77. package/components/navigation/vertical-nav/vertical-nav.module.css +98 -0
  78. package/components/neon/neon-alert/css/index.tsx +53 -0
  79. package/components/neon/neon-alert/css/neon-alert.module.css +60 -0
  80. package/components/neon/neon-alert/tailwind/index.tsx +59 -0
  81. package/components/neon/neon-badge/css/index.tsx +49 -0
  82. package/components/neon/neon-badge/css/neon-badge.module.css +53 -0
  83. package/components/neon/neon-badge/tailwind/index.tsx +50 -0
  84. package/components/neon/neon-button/css/index.tsx +54 -0
  85. package/components/neon/neon-button/css/neon-button.module.css +114 -0
  86. package/components/neon/neon-button/tailwind/index.tsx +51 -0
  87. package/components/neon/neon-divider/css/index.tsx +26 -0
  88. package/components/neon/neon-divider/css/neon-divider.module.css +43 -0
  89. package/components/neon/neon-divider/tailwind/index.tsx +36 -0
  90. package/components/neon/neon-progress/css/index.tsx +65 -0
  91. package/components/neon/neon-progress/css/neon-progress.module.css +88 -0
  92. package/components/neon/neon-progress/tailwind/index.tsx +46 -0
  93. package/components/neon/neon-tabs/css/index.tsx +41 -0
  94. package/components/neon/neon-tabs/css/neon-tabs.module.css +45 -0
  95. package/components/neon/neon-tabs/tailwind/index.tsx +53 -0
  96. package/components/neon/neon-toggle/css/index.tsx +58 -0
  97. package/components/neon/neon-toggle/css/neon-toggle.module.css +79 -0
  98. package/components/neon/neon-toggle/tailwind/index.tsx +57 -0
  99. package/components/text/ascii-art/css/ascii-art.module.css +173 -0
  100. package/components/text/ascii-art/css/index.tsx +116 -0
  101. package/components/text/ascii-art/tailwind/index.tsx +124 -0
  102. package/components/text/blood-drip/css/blood-drip.module.css +142 -0
  103. package/components/text/blood-drip/css/index.tsx +113 -0
  104. package/components/text/blood-drip/tailwind/index.tsx +133 -0
  105. package/components/text/char-glitch/css/char-glitch.module.css +124 -0
  106. package/components/text/char-glitch/css/index.tsx +153 -0
  107. package/components/text/char-glitch/tailwind/index.tsx +126 -0
  108. package/components/text/countdown-display/css/countdown-display.module.css +179 -0
  109. package/components/text/countdown-display/css/index.tsx +190 -0
  110. package/components/text/countdown-display/tailwind/index.tsx +155 -0
  111. package/components/text/giant-layers/css/giant-layers.module.css +156 -0
  112. package/components/text/giant-layers/css/index.tsx +97 -0
  113. package/components/text/giant-layers/tailwind/index.tsx +111 -0
  114. package/components/text/glitch-text/glitch-text.module.css +2 -2
  115. package/components/text/reveal-text/css/index.tsx +180 -0
  116. package/components/text/reveal-text/css/reveal-text.module.css +129 -0
  117. package/components/text/reveal-text/tailwind/index.tsx +135 -0
  118. package/components/text/rotate-text/css/index.tsx +139 -0
  119. package/components/text/rotate-text/css/rotate-text.module.css +162 -0
  120. package/components/text/rotate-text/tailwind/index.tsx +127 -0
  121. package/components/text/strike-reveal/css/index.tsx +124 -0
  122. package/components/text/strike-reveal/css/strike-reveal.module.css +139 -0
  123. package/components/text/strike-reveal/tailwind/index.tsx +138 -0
  124. package/components/text/terminal-output/css/index.tsx +179 -0
  125. package/components/text/terminal-output/css/terminal-output.module.css +203 -0
  126. package/components/text/terminal-output/tailwind/index.tsx +174 -0
  127. package/components/text/typing-text/css/index.tsx +115 -0
  128. package/components/text/typing-text/css/typing-text.module.css +84 -0
  129. package/components/text/typing-text/tailwind/index.tsx +126 -0
  130. package/package.json +1 -1
  131. package/components/glow-orbs/glow-orbs.module.css +0 -31
  132. package/components/glow-orbs/index.tsx +0 -87
  133. package/components/light-beams/index.tsx +0 -80
  134. package/components/light-beams/light-beams.module.css +0 -27
  135. package/components/noise-canvas/index.tsx +0 -113
  136. package/components/noise-canvas/noise-canvas.module.css +0 -8
  137. package/components/package.json +0 -13
  138. package/components/particle-field/index.tsx +0 -81
  139. package/components/particle-field/particle-field.module.css +0 -31
@@ -0,0 +1,203 @@
1
+ .container {
2
+ font-family: 'Courier New', 'Monaco', 'Consolas', monospace;
3
+ background: #0a0a0a;
4
+ border: 1px solid #222;
5
+ border-radius: 4px;
6
+ overflow: hidden;
7
+ }
8
+
9
+ /* Header bar */
10
+ .header {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: 0.5rem;
14
+ padding: 0.75rem 1rem;
15
+ background: #111;
16
+ border-bottom: 1px solid #222;
17
+ }
18
+
19
+ .dots {
20
+ display: flex;
21
+ gap: 6px;
22
+ }
23
+
24
+ .dot {
25
+ width: 12px;
26
+ height: 12px;
27
+ border-radius: 50%;
28
+ }
29
+
30
+ .dot.red { background: #ff5f56; }
31
+ .dot.yellow { background: #ffbd2e; }
32
+ .dot.green { background: #27c93f; }
33
+
34
+ .title {
35
+ font-size: 0.75rem;
36
+ color: #666;
37
+ margin-left: auto;
38
+ letter-spacing: 0.05em;
39
+ }
40
+
41
+ /* Content area */
42
+ .content {
43
+ padding: 1rem;
44
+ max-height: 400px;
45
+ overflow-y: auto;
46
+ scrollbar-width: thin;
47
+ scrollbar-color: #333 #0a0a0a;
48
+ }
49
+
50
+ .content::-webkit-scrollbar {
51
+ width: 6px;
52
+ }
53
+
54
+ .content::-webkit-scrollbar-track {
55
+ background: #0a0a0a;
56
+ }
57
+
58
+ .content::-webkit-scrollbar-thumb {
59
+ background: #333;
60
+ border-radius: 3px;
61
+ }
62
+
63
+ /* Lines */
64
+ .line {
65
+ display: flex;
66
+ gap: 0.5rem;
67
+ margin-bottom: 0.25rem;
68
+ line-height: 1.5;
69
+ font-size: 0.875rem;
70
+ }
71
+
72
+ .prompt {
73
+ color: #00ff00;
74
+ white-space: nowrap;
75
+ user-select: none;
76
+ }
77
+
78
+ .command {
79
+ color: #fafafa;
80
+ }
81
+
82
+ .output {
83
+ color: #888;
84
+ white-space: pre-wrap;
85
+ word-break: break-all;
86
+ }
87
+
88
+ /* Line types */
89
+ .line.error .output { color: #ff0040; }
90
+ .line.success .output { color: #00ff00; }
91
+ .line.warning .output { color: #ffaa00; }
92
+ .line.info .output { color: #00ffff; }
93
+
94
+ /* Typing animation */
95
+ .typing .command::after {
96
+ content: '█';
97
+ animation: blink 1s step-end infinite;
98
+ margin-left: 2px;
99
+ }
100
+
101
+ @keyframes blink {
102
+ 0%, 100% { opacity: 1; }
103
+ 50% { opacity: 0; }
104
+ }
105
+
106
+ /* Animated output */
107
+ .animated .line {
108
+ opacity: 0;
109
+ animation: lineIn 0.3s forwards;
110
+ }
111
+
112
+ @keyframes lineIn {
113
+ from { opacity: 0; transform: translateY(5px); }
114
+ to { opacity: 1; transform: translateY(0); }
115
+ }
116
+
117
+ /* Variants */
118
+ .hacker {
119
+ border-color: #00ff00;
120
+ }
121
+
122
+ .hacker .header {
123
+ background: #001100;
124
+ border-color: #00ff0040;
125
+ }
126
+
127
+ .hacker .prompt { color: #00ff00; }
128
+ .hacker .command { color: #00ff00; }
129
+ .hacker .output { color: #00aa00; }
130
+
131
+ .blood {
132
+ border-color: #ff0040;
133
+ }
134
+
135
+ .blood .header {
136
+ background: #110000;
137
+ border-color: #ff004040;
138
+ }
139
+
140
+ .blood .prompt { color: #ff0040; }
141
+ .blood .command { color: #ff6666; }
142
+ .blood .output { color: #aa4444; }
143
+
144
+ .cyber {
145
+ border-color: #00ffff;
146
+ }
147
+
148
+ .cyber .header {
149
+ background: #001111;
150
+ border-color: #00ffff40;
151
+ }
152
+
153
+ .cyber .prompt { color: #00ffff; }
154
+ .cyber .command { color: #88ffff; }
155
+ .cyber .output { color: #00aaaa; }
156
+
157
+ /* Scanlines overlay */
158
+ .scanlines::after {
159
+ content: '';
160
+ position: absolute;
161
+ top: 0;
162
+ left: 0;
163
+ right: 0;
164
+ bottom: 0;
165
+ background: repeating-linear-gradient(
166
+ 0deg,
167
+ transparent,
168
+ transparent 2px,
169
+ rgba(0, 0, 0, 0.1) 2px,
170
+ rgba(0, 0, 0, 0.1) 4px
171
+ );
172
+ pointer-events: none;
173
+ }
174
+
175
+ /* Glowing effect */
176
+ .glowing .prompt,
177
+ .glowing .command {
178
+ text-shadow: 0 0 5px currentColor;
179
+ }
180
+
181
+ /* Input line */
182
+ .inputLine {
183
+ display: flex;
184
+ gap: 0.5rem;
185
+ margin-top: 0.5rem;
186
+ padding-top: 0.5rem;
187
+ border-top: 1px solid #222;
188
+ }
189
+
190
+ .input {
191
+ flex: 1;
192
+ background: transparent;
193
+ border: none;
194
+ color: #fafafa;
195
+ font-family: inherit;
196
+ font-size: 0.875rem;
197
+ outline: none;
198
+ caret-color: #00ff00;
199
+ }
200
+
201
+ .input::placeholder {
202
+ color: #444;
203
+ }
@@ -0,0 +1,174 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useRef } from 'react';
4
+
5
+ export interface TerminalLine {
6
+ type: 'command' | 'output' | 'error' | 'success' | 'warning' | 'info';
7
+ content: string;
8
+ prompt?: string;
9
+ delay?: number;
10
+ }
11
+
12
+ export interface TerminalOutputProps extends HTMLAttributes<HTMLDivElement> {
13
+ lines: TerminalLine[];
14
+ prompt?: string;
15
+ variant?: 'default' | 'hacker' | 'blood' | 'cyber';
16
+ showHeader?: boolean;
17
+ title?: string;
18
+ animated?: boolean;
19
+ animationDelay?: number;
20
+ typingCursor?: boolean;
21
+ scanlines?: boolean;
22
+ glowing?: boolean;
23
+ showInput?: boolean;
24
+ inputPlaceholder?: string;
25
+ onCommand?: (command: string) => void;
26
+ autoScroll?: boolean;
27
+ }
28
+
29
+ const variantStyles = {
30
+ default: { border: 'border-gray-800', header: 'bg-gray-900', prompt: 'text-green-400', command: 'text-gray-100', output: 'text-gray-500' },
31
+ hacker: { border: 'border-green-500', header: 'bg-green-950', prompt: 'text-green-400', command: 'text-green-400', output: 'text-green-700' },
32
+ blood: { border: 'border-rose-500', header: 'bg-rose-950', prompt: 'text-rose-500', command: 'text-rose-300', output: 'text-rose-700' },
33
+ cyber: { border: 'border-cyan-500', header: 'bg-cyan-950', prompt: 'text-cyan-400', command: 'text-cyan-200', output: 'text-cyan-700' },
34
+ };
35
+
36
+ const typeColors = { error: 'text-rose-500', success: 'text-green-400', warning: 'text-amber-500', info: 'text-cyan-400' };
37
+
38
+ export const TerminalOutput = forwardRef<HTMLDivElement, TerminalOutputProps>(
39
+ (
40
+ {
41
+ lines,
42
+ prompt = '❯',
43
+ variant = 'default',
44
+ showHeader = true,
45
+ title = 'terminal',
46
+ animated = false,
47
+ animationDelay = 100,
48
+ typingCursor = false,
49
+ scanlines = false,
50
+ glowing = false,
51
+ showInput = false,
52
+ inputPlaceholder = 'Type a command...',
53
+ onCommand,
54
+ autoScroll = true,
55
+ className = '',
56
+ ...props
57
+ },
58
+ ref
59
+ ) => {
60
+ const [visibleLines, setVisibleLines] = useState<number>(animated ? 0 : lines.length);
61
+ const [inputValue, setInputValue] = useState('');
62
+ const contentRef = useRef<HTMLDivElement>(null);
63
+ const styles = variantStyles[variant];
64
+
65
+ useEffect(() => {
66
+ if (animated) {
67
+ setVisibleLines(0);
68
+ let index = 0;
69
+
70
+ const showLine = () => {
71
+ if (index < lines.length) {
72
+ const line = lines[index];
73
+ setTimeout(() => {
74
+ setVisibleLines(index + 1);
75
+ index++;
76
+ showLine();
77
+ }, line.delay ?? animationDelay);
78
+ }
79
+ };
80
+ showLine();
81
+ } else {
82
+ setVisibleLines(lines.length);
83
+ }
84
+ }, [lines, animated, animationDelay]);
85
+
86
+ useEffect(() => {
87
+ if (autoScroll && contentRef.current) {
88
+ contentRef.current.scrollTop = contentRef.current.scrollHeight;
89
+ }
90
+ }, [visibleLines, autoScroll]);
91
+
92
+ const handleSubmit = (e: React.FormEvent) => {
93
+ e.preventDefault();
94
+ if (inputValue.trim()) {
95
+ onCommand?.(inputValue);
96
+ setInputValue('');
97
+ }
98
+ };
99
+
100
+ return (
101
+ <div
102
+ ref={ref}
103
+ className={`font-mono bg-[#0a0a0a] border ${styles.border} rounded overflow-hidden relative ${
104
+ scanlines ? 'after:absolute after:inset-0 after:bg-[repeating-linear-gradient(0deg,transparent,transparent_2px,rgba(0,0,0,0.1)_2px,rgba(0,0,0,0.1)_4px)] after:pointer-events-none' : ''
105
+ } ${className}`}
106
+ {...props}
107
+ >
108
+ {showHeader && (
109
+ <div className={`flex items-center gap-2 px-4 py-3 ${styles.header} border-b border-gray-800`}>
110
+ <div className="flex gap-1.5">
111
+ <span className="w-3 h-3 rounded-full bg-red-500" />
112
+ <span className="w-3 h-3 rounded-full bg-yellow-500" />
113
+ <span className="w-3 h-3 rounded-full bg-green-500" />
114
+ </div>
115
+ <span className="ml-auto text-xs text-gray-600 tracking-wide">{title}</span>
116
+ </div>
117
+ )}
118
+
119
+ <div ref={contentRef} className="p-4 max-h-[400px] overflow-y-auto scrollbar-thin scrollbar-track-[#0a0a0a] scrollbar-thumb-gray-700">
120
+ {lines.slice(0, visibleLines).map((line, i) => {
121
+ const isLastCommand = i === visibleLines - 1 && line.type === 'command' && typingCursor;
122
+ const outputColor = line.type !== 'command' && line.type !== 'output' ? typeColors[line.type] : styles.output;
123
+
124
+ return (
125
+ <div
126
+ key={i}
127
+ className={`flex gap-2 mb-1 leading-relaxed text-sm ${animated ? 'animate-[fadeInUp_0.3s_ease-out_forwards]' : ''}`}
128
+ style={animated ? { animationDelay: `${i * 50}ms`, opacity: 0 } : undefined}
129
+ >
130
+ {line.type === 'command' ? (
131
+ <>
132
+ <span className={`${styles.prompt} whitespace-nowrap select-none ${glowing ? 'drop-shadow-[0_0_5px_currentColor]' : ''}`}>
133
+ {line.prompt || prompt}
134
+ </span>
135
+ <span className={`${styles.command} ${glowing ? 'drop-shadow-[0_0_5px_currentColor]' : ''}`}>
136
+ {line.content}
137
+ {isLastCommand && <span className="animate-pulse ml-0.5">█</span>}
138
+ </span>
139
+ </>
140
+ ) : (
141
+ <span className={`${outputColor} whitespace-pre-wrap break-all`}>{line.content}</span>
142
+ )}
143
+ </div>
144
+ );
145
+ })}
146
+
147
+ {showInput && (
148
+ <form onSubmit={handleSubmit} className="flex gap-2 mt-2 pt-2 border-t border-gray-800">
149
+ <span className={`${styles.prompt} ${glowing ? 'drop-shadow-[0_0_5px_currentColor]' : ''}`}>{prompt}</span>
150
+ <input
151
+ type="text"
152
+ value={inputValue}
153
+ onChange={(e) => setInputValue(e.target.value)}
154
+ placeholder={inputPlaceholder}
155
+ className="flex-1 bg-transparent border-none text-gray-100 text-sm outline-none caret-green-400 placeholder:text-gray-700"
156
+ autoFocus
157
+ />
158
+ </form>
159
+ )}
160
+ </div>
161
+
162
+ <style>{`
163
+ @keyframes fadeInUp {
164
+ from { opacity: 0; transform: translateY(5px); }
165
+ to { opacity: 1; transform: translateY(0); }
166
+ }
167
+ `}</style>
168
+ </div>
169
+ );
170
+ }
171
+ );
172
+
173
+ TerminalOutput.displayName = 'TerminalOutput';
174
+ export default TerminalOutput;
@@ -0,0 +1,115 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useCallback } from 'react';
4
+ import styles from './typing-text.module.css';
5
+
6
+ export interface TypingTextProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
7
+ /** Text to type out */
8
+ text: string;
9
+ /** Typing speed in ms per character */
10
+ speed?: number;
11
+ /** Delay before starting in ms */
12
+ delay?: number;
13
+ /** Show cursor */
14
+ showCursor?: boolean;
15
+ /** Cursor style */
16
+ cursorStyle?: 'block' | 'line' | 'underscore';
17
+ /** Visual variant */
18
+ variant?: 'default' | 'terminal' | 'hacker' | 'cyber' | 'ghost';
19
+ /** Loop the animation */
20
+ loop?: boolean;
21
+ /** Pause between loops in ms */
22
+ loopDelay?: number;
23
+ /** Delete speed when looping */
24
+ deleteSpeed?: number;
25
+ /** Callback when typing completes */
26
+ onComplete?: () => void;
27
+ }
28
+
29
+ export const TypingText = forwardRef<HTMLSpanElement, TypingTextProps>(
30
+ (
31
+ {
32
+ text,
33
+ speed = 50,
34
+ delay = 0,
35
+ showCursor = true,
36
+ cursorStyle = 'block',
37
+ variant = 'default',
38
+ loop = false,
39
+ loopDelay = 2000,
40
+ deleteSpeed = 30,
41
+ onComplete,
42
+ className,
43
+ ...props
44
+ },
45
+ ref
46
+ ) => {
47
+ const [displayText, setDisplayText] = useState('');
48
+ const [isTyping, setIsTyping] = useState(false);
49
+ const [isDeleting, setIsDeleting] = useState(false);
50
+
51
+ const typeText = useCallback(() => {
52
+ setIsTyping(true);
53
+ let index = 0;
54
+
55
+ const typeInterval = setInterval(() => {
56
+ if (index < text.length) {
57
+ setDisplayText(text.slice(0, index + 1));
58
+ index++;
59
+ } else {
60
+ clearInterval(typeInterval);
61
+ setIsTyping(false);
62
+
63
+ if (loop) {
64
+ setTimeout(() => {
65
+ setIsDeleting(true);
66
+ let deleteIndex = text.length;
67
+
68
+ const deleteInterval = setInterval(() => {
69
+ if (deleteIndex > 0) {
70
+ setDisplayText(text.slice(0, deleteIndex - 1));
71
+ deleteIndex--;
72
+ } else {
73
+ clearInterval(deleteInterval);
74
+ setIsDeleting(false);
75
+ setTimeout(typeText, delay);
76
+ }
77
+ }, deleteSpeed);
78
+ }, loopDelay);
79
+ } else {
80
+ onComplete?.();
81
+ }
82
+ }
83
+ }, speed);
84
+
85
+ return () => clearInterval(typeInterval);
86
+ }, [text, speed, loop, loopDelay, deleteSpeed, delay, onComplete]);
87
+
88
+ useEffect(() => {
89
+ const timeout = setTimeout(typeText, delay);
90
+ return () => clearTimeout(timeout);
91
+ }, [typeText, delay]);
92
+
93
+ const containerClasses = [
94
+ styles.container,
95
+ variant !== 'default' && styles[variant],
96
+ className
97
+ ].filter(Boolean).join(' ');
98
+
99
+ const cursorClasses = [
100
+ styles.cursor,
101
+ styles[cursorStyle],
102
+ !isTyping && !isDeleting && !loop && styles.cursorHidden
103
+ ].filter(Boolean).join(' ');
104
+
105
+ return (
106
+ <span ref={ref} className={containerClasses} {...props}>
107
+ <span className={styles.text}>{displayText}</span>
108
+ {showCursor && <span className={cursorClasses} />}
109
+ </span>
110
+ );
111
+ }
112
+ );
113
+
114
+ TypingText.displayName = 'TypingText';
115
+ export default TypingText;
@@ -0,0 +1,84 @@
1
+ .container {
2
+ display: inline-block;
3
+ font-family: 'Courier New', 'Monaco', monospace;
4
+ position: relative;
5
+ }
6
+
7
+ .text {
8
+ display: inline;
9
+ white-space: pre-wrap;
10
+ }
11
+
12
+ .cursor {
13
+ display: inline-block;
14
+ width: 0.6em;
15
+ height: 1.1em;
16
+ background: currentColor;
17
+ margin-left: 2px;
18
+ vertical-align: text-bottom;
19
+ animation: blink 1s step-end infinite;
20
+ }
21
+
22
+ .cursor.block {
23
+ width: 0.6em;
24
+ }
25
+
26
+ .cursor.line {
27
+ width: 2px;
28
+ }
29
+
30
+ .cursor.underscore {
31
+ width: 0.6em;
32
+ height: 2px;
33
+ vertical-align: baseline;
34
+ }
35
+
36
+ .cursorHidden {
37
+ opacity: 0;
38
+ }
39
+
40
+ /* Variants */
41
+ .terminal {
42
+ color: #00ff00;
43
+ text-shadow: 0 0 5px #00ff00, 0 0 10px #00ff0080;
44
+ }
45
+
46
+ .terminal .cursor {
47
+ background: #00ff00;
48
+ box-shadow: 0 0 5px #00ff00, 0 0 10px #00ff0080;
49
+ }
50
+
51
+ .hacker {
52
+ color: #ff0040;
53
+ text-shadow: 0 0 5px #ff0040, 0 0 15px #ff004080;
54
+ }
55
+
56
+ .hacker .cursor {
57
+ background: #ff0040;
58
+ box-shadow: 0 0 5px #ff0040;
59
+ }
60
+
61
+ .cyber {
62
+ color: #00ffff;
63
+ text-shadow: 0 0 5px #00ffff, 0 0 10px #00ffff80;
64
+ }
65
+
66
+ .cyber .cursor {
67
+ background: #00ffff;
68
+ box-shadow: 0 0 5px #00ffff;
69
+ }
70
+
71
+ .ghost {
72
+ color: #888;
73
+ opacity: 0.8;
74
+ }
75
+
76
+ .ghost .cursor {
77
+ background: #888;
78
+ animation: blink 2s step-end infinite;
79
+ }
80
+
81
+ @keyframes blink {
82
+ 0%, 100% { opacity: 1; }
83
+ 50% { opacity: 0; }
84
+ }
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useEffect, useState, useCallback } from 'react';
4
+
5
+ export interface TypingTextProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
6
+ text: string;
7
+ speed?: number;
8
+ delay?: number;
9
+ showCursor?: boolean;
10
+ cursorStyle?: 'block' | 'line' | 'underscore';
11
+ variant?: 'default' | 'terminal' | 'hacker' | 'cyber' | 'ghost';
12
+ loop?: boolean;
13
+ loopDelay?: number;
14
+ deleteSpeed?: number;
15
+ onComplete?: () => void;
16
+ }
17
+
18
+ const variantStyles = {
19
+ default: '',
20
+ terminal: 'text-green-400 drop-shadow-[0_0_5px_#00ff00]',
21
+ hacker: 'text-rose-500 drop-shadow-[0_0_5px_#ff0040]',
22
+ cyber: 'text-cyan-400 drop-shadow-[0_0_5px_#00ffff]',
23
+ ghost: 'text-gray-500 opacity-80',
24
+ };
25
+
26
+ const cursorVariants = {
27
+ default: 'bg-current',
28
+ terminal: 'bg-green-400 shadow-[0_0_5px_#00ff00]',
29
+ hacker: 'bg-rose-500 shadow-[0_0_5px_#ff0040]',
30
+ cyber: 'bg-cyan-400 shadow-[0_0_5px_#00ffff]',
31
+ ghost: 'bg-gray-500',
32
+ };
33
+
34
+ const cursorSizes = {
35
+ block: 'w-[0.6em] h-[1.1em]',
36
+ line: 'w-0.5 h-[1.1em]',
37
+ underscore: 'w-[0.6em] h-0.5',
38
+ };
39
+
40
+ export const TypingText = forwardRef<HTMLSpanElement, TypingTextProps>(
41
+ (
42
+ {
43
+ text,
44
+ speed = 50,
45
+ delay = 0,
46
+ showCursor = true,
47
+ cursorStyle = 'block',
48
+ variant = 'default',
49
+ loop = false,
50
+ loopDelay = 2000,
51
+ deleteSpeed = 30,
52
+ onComplete,
53
+ className = '',
54
+ ...props
55
+ },
56
+ ref
57
+ ) => {
58
+ const [displayText, setDisplayText] = useState('');
59
+ const [isTyping, setIsTyping] = useState(false);
60
+ const [isDeleting, setIsDeleting] = useState(false);
61
+
62
+ const typeText = useCallback(() => {
63
+ setIsTyping(true);
64
+ let index = 0;
65
+
66
+ const typeInterval = setInterval(() => {
67
+ if (index < text.length) {
68
+ setDisplayText(text.slice(0, index + 1));
69
+ index++;
70
+ } else {
71
+ clearInterval(typeInterval);
72
+ setIsTyping(false);
73
+
74
+ if (loop) {
75
+ setTimeout(() => {
76
+ setIsDeleting(true);
77
+ let deleteIndex = text.length;
78
+
79
+ const deleteInterval = setInterval(() => {
80
+ if (deleteIndex > 0) {
81
+ setDisplayText(text.slice(0, deleteIndex - 1));
82
+ deleteIndex--;
83
+ } else {
84
+ clearInterval(deleteInterval);
85
+ setIsDeleting(false);
86
+ setTimeout(typeText, delay);
87
+ }
88
+ }, deleteSpeed);
89
+ }, loopDelay);
90
+ } else {
91
+ onComplete?.();
92
+ }
93
+ }
94
+ }, speed);
95
+
96
+ return () => clearInterval(typeInterval);
97
+ }, [text, speed, loop, loopDelay, deleteSpeed, delay, onComplete]);
98
+
99
+ useEffect(() => {
100
+ const timeout = setTimeout(typeText, delay);
101
+ return () => clearTimeout(timeout);
102
+ }, [typeText, delay]);
103
+
104
+ const showBlinkingCursor = isTyping || isDeleting || loop;
105
+
106
+ return (
107
+ <span
108
+ ref={ref}
109
+ className={`inline-block font-mono ${variantStyles[variant]} ${className}`}
110
+ {...props}
111
+ >
112
+ <span className="whitespace-pre-wrap">{displayText}</span>
113
+ {showCursor && (
114
+ <span
115
+ className={`inline-block ml-0.5 align-text-bottom ${cursorSizes[cursorStyle]} ${cursorVariants[variant]} ${
116
+ showBlinkingCursor ? 'animate-pulse' : 'opacity-0'
117
+ }`}
118
+ />
119
+ )}
120
+ </span>
121
+ );
122
+ }
123
+ );
124
+
125
+ TypingText.displayName = 'TypingText';
126
+ export default TypingText;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oalacea/chaosui",
3
- "version": "0.1.0",
3
+ "version": "0.5.0",
4
4
  "description": "Glitch, noise, and distortion components for React. Copy-paste like shadcn.",
5
5
  "type": "module",
6
6
  "bin": {