@oalacea/chaosui 0.1.0 → 0.4.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 (73) hide show
  1. package/bin/cli.js +75 -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/glitch-button/glitch-button.module.css +7 -7
  5. package/components/chaos-vars.css +27 -0
  6. package/components/cyber/cyber-avatar/css/cyber-avatar.module.css +60 -0
  7. package/components/cyber/cyber-avatar/css/index.tsx +28 -0
  8. package/components/cyber/cyber-avatar/tailwind/index.tsx +46 -0
  9. package/components/cyber/cyber-input/css/cyber-input.module.css +87 -0
  10. package/components/cyber/cyber-input/css/index.tsx +49 -0
  11. package/components/cyber/cyber-input/tailwind/index.tsx +55 -0
  12. package/components/cyber/cyber-loader/css/cyber-loader.module.css +102 -0
  13. package/components/cyber/cyber-loader/css/index.tsx +58 -0
  14. package/components/cyber/cyber-loader/tailwind/index.tsx +63 -0
  15. package/components/cyber/cyber-modal/css/cyber-modal.module.css +124 -0
  16. package/components/cyber/cyber-modal/css/index.tsx +75 -0
  17. package/components/cyber/cyber-modal/tailwind/index.tsx +87 -0
  18. package/components/cyber/cyber-slider/css/cyber-slider.module.css +61 -0
  19. package/components/cyber/cyber-slider/css/index.tsx +41 -0
  20. package/components/cyber/cyber-slider/tailwind/index.tsx +51 -0
  21. package/components/cyber/cyber-tooltip/css/cyber-tooltip.module.css +67 -0
  22. package/components/cyber/cyber-tooltip/css/index.tsx +36 -0
  23. package/components/cyber/cyber-tooltip/tailwind/index.tsx +48 -0
  24. package/components/effects/glitch-image/css/glitch-image.module.css +64 -0
  25. package/components/effects/glitch-image/css/index.tsx +25 -0
  26. package/components/effects/glitch-image/tailwind/index.tsx +49 -0
  27. package/components/effects/glowing-border/css/glowing-border.module.css +73 -0
  28. package/components/effects/glowing-border/css/index.tsx +45 -0
  29. package/components/effects/glowing-border/tailwind/index.tsx +40 -0
  30. package/components/effects/screen-distortion/screen-distortion.module.css +2 -2
  31. package/components/effects/warning-tape/index.tsx +4 -4
  32. package/components/effects/warning-tape/warning-tape.module.css +2 -0
  33. package/components/layout/data-grid/css/data-grid.module.css +52 -0
  34. package/components/layout/data-grid/css/index.tsx +76 -0
  35. package/components/layout/data-grid/tailwind/index.tsx +74 -0
  36. package/components/layout/hologram-card/css/hologram-card.module.css +102 -0
  37. package/components/layout/hologram-card/css/index.tsx +46 -0
  38. package/components/layout/hologram-card/tailwind/index.tsx +61 -0
  39. package/components/navigation/hexagon-menu/css/hexagon-menu.module.css +55 -0
  40. package/components/navigation/hexagon-menu/css/index.tsx +35 -0
  41. package/components/navigation/hexagon-menu/tailwind/index.tsx +53 -0
  42. package/components/neon/neon-alert/css/index.tsx +53 -0
  43. package/components/neon/neon-alert/css/neon-alert.module.css +60 -0
  44. package/components/neon/neon-alert/tailwind/index.tsx +59 -0
  45. package/components/neon/neon-badge/css/index.tsx +49 -0
  46. package/components/neon/neon-badge/css/neon-badge.module.css +53 -0
  47. package/components/neon/neon-badge/tailwind/index.tsx +50 -0
  48. package/components/neon/neon-button/css/index.tsx +54 -0
  49. package/components/neon/neon-button/css/neon-button.module.css +114 -0
  50. package/components/neon/neon-button/tailwind/index.tsx +51 -0
  51. package/components/neon/neon-divider/css/index.tsx +26 -0
  52. package/components/neon/neon-divider/css/neon-divider.module.css +43 -0
  53. package/components/neon/neon-divider/tailwind/index.tsx +36 -0
  54. package/components/neon/neon-progress/css/index.tsx +65 -0
  55. package/components/neon/neon-progress/css/neon-progress.module.css +88 -0
  56. package/components/neon/neon-progress/tailwind/index.tsx +46 -0
  57. package/components/neon/neon-tabs/css/index.tsx +41 -0
  58. package/components/neon/neon-tabs/css/neon-tabs.module.css +45 -0
  59. package/components/neon/neon-tabs/tailwind/index.tsx +53 -0
  60. package/components/neon/neon-toggle/css/index.tsx +58 -0
  61. package/components/neon/neon-toggle/css/neon-toggle.module.css +79 -0
  62. package/components/neon/neon-toggle/tailwind/index.tsx +57 -0
  63. package/components/text/glitch-text/glitch-text.module.css +2 -2
  64. package/package.json +1 -1
  65. package/components/glow-orbs/glow-orbs.module.css +0 -31
  66. package/components/glow-orbs/index.tsx +0 -87
  67. package/components/light-beams/index.tsx +0 -80
  68. package/components/light-beams/light-beams.module.css +0 -27
  69. package/components/noise-canvas/index.tsx +0 -113
  70. package/components/noise-canvas/noise-canvas.module.css +0 -8
  71. package/components/package.json +0 -13
  72. package/components/particle-field/index.tsx +0 -81
  73. package/components/particle-field/particle-field.module.css +0 -31
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes, useState } from 'react';
4
+
5
+ export interface CyberTooltipProps extends HTMLAttributes<HTMLDivElement> {
6
+ content: React.ReactNode;
7
+ position?: 'top' | 'bottom';
8
+ variant?: 'cyan' | 'pink' | 'green';
9
+ }
10
+
11
+ const variantStyles = {
12
+ cyan: 'border-cyan-400 shadow-[0_0_10px_#00f0ff]',
13
+ pink: 'border-fuchsia-500 shadow-[0_0_10px_#ff00ff]',
14
+ green: 'border-emerald-400 shadow-[0_0_10px_#00ff88]',
15
+ };
16
+
17
+ export const CyberTooltip = forwardRef<HTMLDivElement, CyberTooltipProps>(
18
+ ({ children, content, position = 'top', variant = 'cyan', className = '', ...props }, ref) => {
19
+ const [visible, setVisible] = useState(false);
20
+ return (
21
+ <div
22
+ ref={ref}
23
+ className={`relative inline-block ${className}`}
24
+ onMouseEnter={() => setVisible(true)}
25
+ onMouseLeave={() => setVisible(false)}
26
+ {...props}
27
+ >
28
+ {children}
29
+ {visible && (
30
+ <div
31
+ className={`
32
+ absolute z-50 px-3 py-2
33
+ font-['Share_Tech_Mono',monospace] text-xs text-white whitespace-nowrap
34
+ bg-[#0a0a0f]/95 border ${variantStyles[variant]}
35
+ animate-[fadeIn_0.2s_ease-out]
36
+ ${position === 'top' ? 'bottom-full left-1/2 -translate-x-1/2 mb-2' : 'top-full left-1/2 -translate-x-1/2 mt-2'}
37
+ `}
38
+ >
39
+ {content}
40
+ </div>
41
+ )}
42
+ </div>
43
+ );
44
+ }
45
+ );
46
+
47
+ CyberTooltip.displayName = 'CyberTooltip';
48
+ export default CyberTooltip;
@@ -0,0 +1,64 @@
1
+ .container {
2
+ position: relative;
3
+ overflow: hidden;
4
+ }
5
+
6
+ .base {
7
+ display: block;
8
+ width: 100%;
9
+ height: auto;
10
+ }
11
+
12
+ .layer {
13
+ position: absolute;
14
+ top: 0;
15
+ left: 0;
16
+ width: 100%;
17
+ height: 100%;
18
+ object-fit: cover;
19
+ opacity: 0;
20
+ mix-blend-mode: screen;
21
+ }
22
+
23
+ .red { filter: hue-rotate(-50deg) saturate(200%); }
24
+ .blue { filter: hue-rotate(50deg) saturate(200%); }
25
+
26
+ .container:hover .red {
27
+ opacity: 0.8;
28
+ animation: glitch-r 0.3s steps(2) infinite;
29
+ }
30
+
31
+ .container:hover .blue {
32
+ opacity: 0.8;
33
+ animation: glitch-b 0.3s steps(2) infinite;
34
+ }
35
+
36
+ .continuous .red {
37
+ opacity: 0.5;
38
+ animation: glitch-r 2s steps(3) infinite;
39
+ }
40
+
41
+ .continuous .blue {
42
+ opacity: 0.5;
43
+ animation: glitch-b 2s steps(3) infinite;
44
+ }
45
+
46
+ .low .layer { opacity: 0.3 !important; }
47
+ .high .layer { opacity: 0.9 !important; }
48
+ .high:hover .red, .high:hover .blue { animation-duration: 0.1s; }
49
+
50
+ @keyframes glitch-r {
51
+ 0% { transform: translate(0); clip-path: inset(20% 0 40% 0); }
52
+ 25% { transform: translate(-5px, 5px); clip-path: inset(50% 0 20% 0); }
53
+ 50% { transform: translate(5px, -5px); clip-path: inset(10% 0 60% 0); }
54
+ 75% { transform: translate(-3px, 3px); clip-path: inset(70% 0 10% 0); }
55
+ 100% { transform: translate(0); clip-path: inset(20% 0 40% 0); }
56
+ }
57
+
58
+ @keyframes glitch-b {
59
+ 0% { transform: translate(0); clip-path: inset(60% 0 10% 0); }
60
+ 25% { transform: translate(5px, -5px); clip-path: inset(20% 0 50% 0); }
61
+ 50% { transform: translate(-5px, 5px); clip-path: inset(80% 0 5% 0); }
62
+ 75% { transform: translate(3px, -3px); clip-path: inset(5% 0 70% 0); }
63
+ 100% { transform: translate(0); clip-path: inset(60% 0 10% 0); }
64
+ }
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, ImgHTMLAttributes } from 'react';
4
+ import styles from './glitch-image.module.css';
5
+
6
+ export interface GlitchImageProps extends ImgHTMLAttributes<HTMLImageElement> {
7
+ intensity?: 'low' | 'medium' | 'high';
8
+ continuous?: boolean;
9
+ }
10
+
11
+ export const GlitchImage = forwardRef<HTMLDivElement, GlitchImageProps>(
12
+ ({ src, alt = 'Image', intensity = 'medium', continuous = false, className, ...props }, ref) => {
13
+ const classes = [styles.container, styles[intensity], continuous && styles.continuous, className].filter(Boolean).join(' ');
14
+ return (
15
+ <div ref={ref} className={classes}>
16
+ <img src={src} alt={alt} className={styles.base} {...props} />
17
+ <img src={src} alt="" className={`${styles.layer} ${styles.red}`} aria-hidden="true" />
18
+ <img src={src} alt="" className={`${styles.layer} ${styles.blue}`} aria-hidden="true" />
19
+ </div>
20
+ );
21
+ }
22
+ );
23
+
24
+ GlitchImage.displayName = 'GlitchImage';
25
+ export default GlitchImage;
@@ -0,0 +1,49 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, ImgHTMLAttributes, useId } from 'react';
4
+
5
+ export interface GlitchImageProps extends ImgHTMLAttributes<HTMLImageElement> {}
6
+
7
+ export const GlitchImage = forwardRef<HTMLDivElement, GlitchImageProps>(
8
+ ({ src, alt = 'Image', className = '', ...props }, ref) => {
9
+ const id = useId();
10
+ return (
11
+ <>
12
+ <style>{`
13
+ @keyframes glitch-r-${id} {
14
+ 0%, 100% { transform: translate(0); clip-path: inset(20% 0 40% 0); }
15
+ 25% { transform: translate(-5px, 5px); clip-path: inset(50% 0 20% 0); }
16
+ 50% { transform: translate(5px, -5px); clip-path: inset(10% 0 60% 0); }
17
+ 75% { transform: translate(-3px, 3px); clip-path: inset(70% 0 10% 0); }
18
+ }
19
+ @keyframes glitch-b-${id} {
20
+ 0%, 100% { transform: translate(0); clip-path: inset(60% 0 10% 0); }
21
+ 25% { transform: translate(5px, -5px); clip-path: inset(20% 0 50% 0); }
22
+ 50% { transform: translate(-5px, 5px); clip-path: inset(80% 0 5% 0); }
23
+ 75% { transform: translate(3px, -3px); clip-path: inset(5% 0 70% 0); }
24
+ }
25
+ `}</style>
26
+ <div ref={ref} className={`relative overflow-hidden group ${className}`}>
27
+ <img src={src} alt={alt} className="block w-full h-auto" {...props} />
28
+ <img
29
+ src={src}
30
+ alt=""
31
+ className="absolute inset-0 w-full h-full object-cover opacity-0 mix-blend-screen group-hover:opacity-80"
32
+ style={{ filter: 'hue-rotate(-50deg) saturate(200%)', animation: `glitch-r-${id} 0.3s steps(2) infinite` }}
33
+ aria-hidden="true"
34
+ />
35
+ <img
36
+ src={src}
37
+ alt=""
38
+ className="absolute inset-0 w-full h-full object-cover opacity-0 mix-blend-screen group-hover:opacity-80"
39
+ style={{ filter: 'hue-rotate(50deg) saturate(200%)', animation: `glitch-b-${id} 0.3s steps(2) infinite` }}
40
+ aria-hidden="true"
41
+ />
42
+ </div>
43
+ </>
44
+ );
45
+ }
46
+ );
47
+
48
+ GlitchImage.displayName = 'GlitchImage';
49
+ export default GlitchImage;
@@ -0,0 +1,73 @@
1
+ .container {
2
+ --glow-color: #00f0ff;
3
+ --glow-size: 10px;
4
+
5
+ position: relative;
6
+ padding: 2px;
7
+ border-radius: 0.5rem;
8
+ background: var(--glow-color);
9
+ }
10
+
11
+ .cyan { --glow-color: #00f0ff; }
12
+ .pink { --glow-color: #ff00ff; }
13
+ .green { --glow-color: #00ff88; }
14
+ .purple { --glow-color: #a855f7; }
15
+
16
+ .rainbow {
17
+ background: linear-gradient(90deg, #00f0ff, #ff00ff, #00ff88, #00f0ff);
18
+ background-size: 300% 100%;
19
+ }
20
+
21
+ .low { --glow-size: 5px; }
22
+ .medium { --glow-size: 10px; }
23
+ .high { --glow-size: 20px; }
24
+
25
+ .content {
26
+ position: relative;
27
+ background: hsl(var(--background, 0 0% 4%));
28
+ border-radius: calc(0.5rem - 2px);
29
+ z-index: 1;
30
+ }
31
+
32
+ /* Glow effect */
33
+ .container::before {
34
+ content: '';
35
+ position: absolute;
36
+ top: 0;
37
+ left: 0;
38
+ width: 100%;
39
+ height: 100%;
40
+ border-radius: 0.5rem;
41
+ background: inherit;
42
+ filter: blur(var(--glow-size));
43
+ opacity: 0.7;
44
+ z-index: -1;
45
+ }
46
+
47
+ /* Animation */
48
+ .animated {
49
+ animation: glow-pulse 2s ease-in-out infinite;
50
+ }
51
+
52
+ .rainbow.animated {
53
+ animation: glow-pulse 2s ease-in-out infinite, rainbow-border 3s linear infinite;
54
+ }
55
+
56
+ @keyframes glow-pulse {
57
+ 0%, 100% { filter: brightness(1); }
58
+ 50% { filter: brightness(1.3); }
59
+ }
60
+
61
+ @keyframes rainbow-border {
62
+ 0% { background-position: 0% 50%; }
63
+ 100% { background-position: 300% 50%; }
64
+ }
65
+
66
+ .container:hover {
67
+ filter: brightness(1.2);
68
+ }
69
+
70
+ .container:hover::before {
71
+ filter: blur(calc(var(--glow-size) * 1.5));
72
+ opacity: 0.9;
73
+ }
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes } from 'react';
4
+ import styles from './glowing-border.module.css';
5
+
6
+ export interface GlowingBorderProps extends HTMLAttributes<HTMLDivElement> {
7
+ /** Neon color variant */
8
+ variant?: 'cyan' | 'pink' | 'green' | 'purple' | 'rainbow';
9
+ /** Pulse animation */
10
+ animated?: boolean;
11
+ /** Glow intensity */
12
+ intensity?: 'low' | 'medium' | 'high';
13
+ }
14
+
15
+ export const GlowingBorder = forwardRef<HTMLDivElement, GlowingBorderProps>(
16
+ (
17
+ {
18
+ children,
19
+ variant = 'cyan',
20
+ animated = true,
21
+ intensity = 'medium',
22
+ className,
23
+ ...props
24
+ },
25
+ ref
26
+ ) => {
27
+ const classes = [
28
+ styles.container,
29
+ styles[variant],
30
+ styles[intensity],
31
+ animated && styles.animated,
32
+ className,
33
+ ].filter(Boolean).join(' ');
34
+
35
+ return (
36
+ <div ref={ref} className={classes} {...props}>
37
+ <div className={styles.content}>{children}</div>
38
+ </div>
39
+ );
40
+ }
41
+ );
42
+
43
+ GlowingBorder.displayName = 'GlowingBorder';
44
+
45
+ export default GlowingBorder;
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, HTMLAttributes } from 'react';
4
+
5
+ export interface GlowingBorderProps extends HTMLAttributes<HTMLDivElement> {
6
+ variant?: 'cyan' | 'pink' | 'green' | 'purple';
7
+ animated?: boolean;
8
+ }
9
+
10
+ const variantStyles = {
11
+ cyan: 'bg-cyan-400 shadow-[0_0_15px_#00f0ff]',
12
+ pink: 'bg-fuchsia-500 shadow-[0_0_15px_#ff00ff]',
13
+ green: 'bg-emerald-400 shadow-[0_0_15px_#00ff88]',
14
+ purple: 'bg-purple-500 shadow-[0_0_15px_#a855f7]',
15
+ };
16
+
17
+ export const GlowingBorder = forwardRef<HTMLDivElement, GlowingBorderProps>(
18
+ ({ children, variant = 'cyan', animated = true, className = '', ...props }, ref) => {
19
+ return (
20
+ <div
21
+ ref={ref}
22
+ className={`
23
+ relative p-0.5 rounded-lg
24
+ ${variantStyles[variant]}
25
+ ${animated ? 'animate-pulse' : ''}
26
+ hover:brightness-125 transition-all duration-300
27
+ ${className}
28
+ `}
29
+ {...props}
30
+ >
31
+ <div className="relative bg-[#0a0a0f] rounded-md">
32
+ {children}
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+ );
38
+
39
+ GlowingBorder.displayName = 'GlowingBorder';
40
+ export default GlowingBorder;
@@ -46,14 +46,14 @@
46
46
 
47
47
  @keyframes glitch-distort-1 {
48
48
  0%, 85%, 100% { opacity: 0; clip-path: inset(0 0 100% 0); }
49
- 86% { opacity: 1; clip-path: inset(20% 0 60% 0); transform: translateX(var(--shift, -5px)); background: rgba(255, 0, 64, 0.1); }
49
+ 86% { opacity: 1; clip-path: inset(20% 0 60% 0); transform: translateX(var(--shift, -5px)); background: hsl(var(--primary, 347 100% 50%) / 0.1); }
50
50
  88% { opacity: 1; clip-path: inset(50% 0 30% 0); transform: translateX(var(--shift, 5px)); }
51
51
  90% { opacity: 0; }
52
52
  }
53
53
 
54
54
  @keyframes glitch-distort-2 {
55
55
  0%, 85%, 100% { opacity: 0; clip-path: inset(0 0 100% 0); }
56
- 87% { opacity: 1; clip-path: inset(40% 0 40% 0); transform: translateX(var(--shift, 3px)); background: rgba(0, 255, 255, 0.1); }
56
+ 87% { opacity: 1; clip-path: inset(40% 0 40% 0); transform: translateX(var(--shift, 3px)); background: hsl(var(--secondary, 180 100% 50%) / 0.1); }
57
57
  89% { opacity: 1; clip-path: inset(70% 0 10% 0); transform: translateX(var(--shift, -3px)); }
58
58
  91% { opacity: 0; }
59
59
  }
@@ -22,8 +22,8 @@ export const WarningTape = forwardRef<HTMLDivElement, WarningTapeProps>(
22
22
  (
23
23
  {
24
24
  children,
25
- bgColor = '#ff0040',
26
- textColor = '#000000',
25
+ bgColor,
26
+ textColor,
27
27
  duration = 20,
28
28
  rotation = -1,
29
29
  reverse = false,
@@ -41,8 +41,8 @@ export const WarningTape = forwardRef<HTMLDivElement, WarningTapeProps>(
41
41
  ref={ref}
42
42
  className={`${styles.tape} ${className || ''}`}
43
43
  style={{
44
- backgroundColor: bgColor,
45
- color: textColor,
44
+ ...(bgColor && { backgroundColor: bgColor }),
45
+ ...(textColor && { color: textColor }),
46
46
  transform: `rotate(${rotation}deg) scale(1.1)`,
47
47
  ...style,
48
48
  }}
@@ -7,6 +7,8 @@
7
7
  font-size: 0.75rem;
8
8
  letter-spacing: 0.1em;
9
9
  text-transform: uppercase;
10
+ background-color: hsl(var(--primary, 347 100% 50%));
11
+ color: hsl(var(--primary-foreground, 0 0% 100%));
10
12
  }
11
13
 
12
14
  .scroll {
@@ -0,0 +1,52 @@
1
+ .container {
2
+ --grid-color: #00f0ff;
3
+
4
+ width: 100%;
5
+ font-family: var(--font-mono, 'Share Tech Mono', monospace);
6
+ background: rgba(0, 0, 0, 0.5);
7
+ border: 1px solid var(--grid-color);
8
+ overflow: hidden;
9
+ }
10
+
11
+ .cyan { --grid-color: #00f0ff; }
12
+ .green { --grid-color: #00ff88; }
13
+ .amber { --grid-color: #ffaa00; }
14
+
15
+ .table {
16
+ width: 100%;
17
+ border-collapse: collapse;
18
+ }
19
+
20
+ .headerRow {
21
+ background: rgba(0, 240, 255, 0.1);
22
+ border-bottom: 1px solid var(--grid-color);
23
+ }
24
+
25
+ .header {
26
+ padding: 0.75rem 1rem;
27
+ font-size: 0.6875rem;
28
+ font-weight: 700;
29
+ text-align: left;
30
+ text-transform: uppercase;
31
+ letter-spacing: 0.125em;
32
+ color: var(--grid-color);
33
+ }
34
+
35
+ .row {
36
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
37
+ transition: background 0.2s ease;
38
+ }
39
+
40
+ .striped:nth-child(even) {
41
+ background: rgba(255, 255, 255, 0.02);
42
+ }
43
+
44
+ .hoverable:hover {
45
+ background: rgba(0, 240, 255, 0.1);
46
+ }
47
+
48
+ .cell {
49
+ padding: 0.625rem 1rem;
50
+ font-size: 0.8125rem;
51
+ color: rgba(255, 255, 255, 0.8);
52
+ }
@@ -0,0 +1,76 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, TableHTMLAttributes } from 'react';
4
+ import styles from './data-grid.module.css';
5
+
6
+ export interface DataGridColumn<T> {
7
+ key: keyof T;
8
+ header: string;
9
+ width?: string;
10
+ }
11
+
12
+ export interface DataGridProps<T extends Record<string, unknown>> extends Omit<TableHTMLAttributes<HTMLTableElement>, 'children'> {
13
+ /** Column definitions */
14
+ columns: DataGridColumn<T>[];
15
+ /** Data rows */
16
+ data: T[];
17
+ /** Color variant */
18
+ variant?: 'cyan' | 'green' | 'amber';
19
+ /** Striped rows */
20
+ striped?: boolean;
21
+ /** Hoverable rows */
22
+ hoverable?: boolean;
23
+ }
24
+
25
+ function DataGridInner<T extends Record<string, unknown>>(
26
+ {
27
+ columns,
28
+ data,
29
+ variant = 'cyan',
30
+ striped = true,
31
+ hoverable = true,
32
+ className,
33
+ ...props
34
+ }: DataGridProps<T>,
35
+ ref: React.ForwardedRef<HTMLTableElement>
36
+ ) {
37
+ return (
38
+ <div className={`${styles.container} ${styles[variant]} ${className || ''}`}>
39
+ <table ref={ref} className={styles.table} {...props}>
40
+ <thead>
41
+ <tr className={styles.headerRow}>
42
+ {columns.map((col) => (
43
+ <th
44
+ key={String(col.key)}
45
+ className={styles.header}
46
+ style={{ width: col.width }}
47
+ >
48
+ {col.header}
49
+ </th>
50
+ ))}
51
+ </tr>
52
+ </thead>
53
+ <tbody>
54
+ {data.map((row, i) => (
55
+ <tr
56
+ key={i}
57
+ className={`${styles.row} ${striped ? styles.striped : ''} ${hoverable ? styles.hoverable : ''}`}
58
+ >
59
+ {columns.map((col) => (
60
+ <td key={String(col.key)} className={styles.cell}>
61
+ {String(row[col.key])}
62
+ </td>
63
+ ))}
64
+ </tr>
65
+ ))}
66
+ </tbody>
67
+ </table>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ export const DataGrid = forwardRef(DataGridInner) as <T extends Record<string, unknown>>(
73
+ props: DataGridProps<T> & { ref?: React.ForwardedRef<HTMLTableElement> }
74
+ ) => JSX.Element;
75
+
76
+ export default DataGrid;
@@ -0,0 +1,74 @@
1
+ 'use client';
2
+
3
+ import { forwardRef, TableHTMLAttributes } from 'react';
4
+
5
+ export interface DataGridColumn<T> {
6
+ key: keyof T;
7
+ header: string;
8
+ width?: string;
9
+ }
10
+
11
+ export interface DataGridProps<T extends Record<string, unknown>> extends Omit<TableHTMLAttributes<HTMLTableElement>, 'children'> {
12
+ columns: DataGridColumn<T>[];
13
+ data: T[];
14
+ variant?: 'cyan' | 'green' | 'amber';
15
+ striped?: boolean;
16
+ hoverable?: boolean;
17
+ }
18
+
19
+ const variantStyles = {
20
+ cyan: { border: 'border-cyan-400', header: 'text-cyan-400 bg-cyan-400/10', hover: 'hover:bg-cyan-400/10' },
21
+ green: { border: 'border-emerald-400', header: 'text-emerald-400 bg-emerald-400/10', hover: 'hover:bg-emerald-400/10' },
22
+ amber: { border: 'border-amber-400', header: 'text-amber-400 bg-amber-400/10', hover: 'hover:bg-amber-400/10' },
23
+ };
24
+
25
+ function DataGridInner<T extends Record<string, unknown>>(
26
+ { columns, data, variant = 'cyan', striped = true, hoverable = true, className = '', ...props }: DataGridProps<T>,
27
+ ref: React.ForwardedRef<HTMLTableElement>
28
+ ) {
29
+ const colors = variantStyles[variant];
30
+
31
+ return (
32
+ <div className={`w-full font-['Share_Tech_Mono',monospace] bg-black/50 border ${colors.border} overflow-hidden ${className}`}>
33
+ <table ref={ref} className="w-full border-collapse" {...props}>
34
+ <thead>
35
+ <tr className={`${colors.header} border-b ${colors.border}`}>
36
+ {columns.map((col) => (
37
+ <th
38
+ key={String(col.key)}
39
+ className="px-4 py-3 text-left text-[11px] font-bold uppercase tracking-widest"
40
+ style={{ width: col.width }}
41
+ >
42
+ {col.header}
43
+ </th>
44
+ ))}
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ {data.map((row, i) => (
49
+ <tr
50
+ key={i}
51
+ className={`
52
+ border-b border-white/5 transition-colors duration-200
53
+ ${striped && i % 2 === 1 ? 'bg-white/[0.02]' : ''}
54
+ ${hoverable ? colors.hover : ''}
55
+ `}
56
+ >
57
+ {columns.map((col) => (
58
+ <td key={String(col.key)} className="px-4 py-2.5 text-[13px] text-white/80">
59
+ {String(row[col.key])}
60
+ </td>
61
+ ))}
62
+ </tr>
63
+ ))}
64
+ </tbody>
65
+ </table>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ export const DataGrid = forwardRef(DataGridInner) as <T extends Record<string, unknown>>(
71
+ props: DataGridProps<T> & { ref?: React.ForwardedRef<HTMLTableElement> }
72
+ ) => JSX.Element;
73
+
74
+ export default DataGrid;