omnira-ui 0.1.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +145 -0
  3. package/cli/omnira-init.mjs +260 -0
  4. package/cli/presets.mjs +386 -0
  5. package/components/ui/AppStoreButton/AppStoreButton.module.css +87 -0
  6. package/components/ui/AppStoreButton/AppStoreButton.tsx +114 -0
  7. package/components/ui/AppStoreButton/index.ts +2 -0
  8. package/components/ui/Avatar/Avatar.module.css +76 -0
  9. package/components/ui/Avatar/Avatar.tsx +102 -0
  10. package/components/ui/Avatar/index.ts +2 -0
  11. package/components/ui/Badge/Badge.module.css +120 -0
  12. package/components/ui/Badge/Badge.tsx +25 -0
  13. package/components/ui/Badge/index.ts +2 -0
  14. package/components/ui/BadgeGroup/BadgeGroup.module.css +214 -0
  15. package/components/ui/BadgeGroup/BadgeGroup.tsx +88 -0
  16. package/components/ui/BadgeGroup/index.ts +2 -0
  17. package/components/ui/Browser/Browser.module.css +105 -0
  18. package/components/ui/Browser/Browser.tsx +54 -0
  19. package/components/ui/Browser/index.ts +2 -0
  20. package/components/ui/Button/Button.module.css +188 -0
  21. package/components/ui/Button/Button.tsx +74 -0
  22. package/components/ui/Button/index.ts +2 -0
  23. package/components/ui/ButtonUtility/ButtonUtility.module.css +154 -0
  24. package/components/ui/ButtonUtility/ButtonUtility.tsx +65 -0
  25. package/components/ui/ButtonUtility/index.ts +2 -0
  26. package/components/ui/Card/Card.module.css +80 -0
  27. package/components/ui/Card/Card.tsx +39 -0
  28. package/components/ui/Card/index.ts +2 -0
  29. package/components/ui/Checkbox/Checkbox.module.css +92 -0
  30. package/components/ui/Checkbox/Checkbox.tsx +95 -0
  31. package/components/ui/Checkbox/index.ts +2 -0
  32. package/components/ui/Collapse/Collapse.module.css +60 -0
  33. package/components/ui/Collapse/Collapse.tsx +100 -0
  34. package/components/ui/Collapse/index.ts +2 -0
  35. package/components/ui/CreditCard/CreditCard.module.css +117 -0
  36. package/components/ui/CreditCard/CreditCard.tsx +86 -0
  37. package/components/ui/CreditCard/index.ts +2 -0
  38. package/components/ui/Dropdown/Dropdown.module.css +269 -0
  39. package/components/ui/Dropdown/Dropdown.tsx +419 -0
  40. package/components/ui/Dropdown/index.ts +31 -0
  41. package/components/ui/GridLines/GridLines.module.css +95 -0
  42. package/components/ui/GridLines/GridLines.tsx +10 -0
  43. package/components/ui/GridLines/index.ts +1 -0
  44. package/components/ui/Illustration/Illustration.module.css +10 -0
  45. package/components/ui/Illustration/Illustration.tsx +96 -0
  46. package/components/ui/Illustration/index.ts +2 -0
  47. package/components/ui/Input/Input.module.css +217 -0
  48. package/components/ui/Input/Input.tsx +93 -0
  49. package/components/ui/Input/index.ts +2 -0
  50. package/components/ui/Phone/Phone.module.css +104 -0
  51. package/components/ui/Phone/Phone.tsx +54 -0
  52. package/components/ui/Phone/index.ts +2 -0
  53. package/components/ui/PinInput/PinInput.module.css +104 -0
  54. package/components/ui/PinInput/PinInput.tsx +210 -0
  55. package/components/ui/PinInput/index.ts +1 -0
  56. package/components/ui/ProgressBar/ProgressBar.module.css +180 -0
  57. package/components/ui/ProgressBar/ProgressBar.tsx +206 -0
  58. package/components/ui/ProgressBar/index.ts +2 -0
  59. package/components/ui/QRCode/QRCode.module.css +13 -0
  60. package/components/ui/QRCode/QRCode.tsx +128 -0
  61. package/components/ui/QRCode/index.ts +2 -0
  62. package/components/ui/RadioButton/RadioButton.module.css +92 -0
  63. package/components/ui/RadioButton/RadioButton.tsx +134 -0
  64. package/components/ui/RadioButton/index.ts +2 -0
  65. package/components/ui/RadioGroup/RadioGroup.module.css +382 -0
  66. package/components/ui/RadioGroup/RadioGroup.tsx +292 -0
  67. package/components/ui/RadioGroup/index.ts +1 -0
  68. package/components/ui/Rating/Rating.module.css +99 -0
  69. package/components/ui/Rating/Rating.tsx +123 -0
  70. package/components/ui/Rating/index.ts +2 -0
  71. package/components/ui/Select/Select.module.css +371 -0
  72. package/components/ui/Select/Select.tsx +507 -0
  73. package/components/ui/Select/index.ts +2 -0
  74. package/components/ui/SidebarNavigation/SidebarDual.tsx +71 -0
  75. package/components/ui/SidebarNavigation/SidebarFeatureCard.module.css +351 -0
  76. package/components/ui/SidebarNavigation/SidebarFeatureCard.tsx +388 -0
  77. package/components/ui/SidebarNavigation/SidebarNavigation.module.css +610 -0
  78. package/components/ui/SidebarNavigation/SidebarParts.tsx +215 -0
  79. package/components/ui/SidebarNavigation/SidebarSectionDividers.tsx +46 -0
  80. package/components/ui/SidebarNavigation/SidebarSectionHeadings.tsx +52 -0
  81. package/components/ui/SidebarNavigation/SidebarSimple.tsx +46 -0
  82. package/components/ui/SidebarNavigation/SidebarSlim.tsx +48 -0
  83. package/components/ui/SidebarNavigation/index.ts +29 -0
  84. package/components/ui/SidebarNavigation/types.ts +27 -0
  85. package/components/ui/Slider/Slider.module.css +93 -0
  86. package/components/ui/Slider/Slider.tsx +118 -0
  87. package/components/ui/Slider/index.ts +2 -0
  88. package/components/ui/SocialButton/SocialButton.module.css +223 -0
  89. package/components/ui/SocialButton/SocialButton.tsx +185 -0
  90. package/components/ui/SocialButton/index.ts +2 -0
  91. package/components/ui/Tag/Tag.module.css +203 -0
  92. package/components/ui/Tag/Tag.tsx +161 -0
  93. package/components/ui/Tag/index.ts +2 -0
  94. package/components/ui/TextEditor/TextEditor.module.css +182 -0
  95. package/components/ui/TextEditor/TextEditor.tsx +323 -0
  96. package/components/ui/TextEditor/index.ts +1 -0
  97. package/components/ui/Textarea/Textarea.module.css +75 -0
  98. package/components/ui/Textarea/Textarea.tsx +59 -0
  99. package/components/ui/Textarea/index.ts +2 -0
  100. package/components/ui/Toggle/Toggle.module.css +118 -0
  101. package/components/ui/Toggle/Toggle.tsx +77 -0
  102. package/components/ui/Toggle/index.ts +2 -0
  103. package/components/ui/Tooltip/Tooltip.module.css +95 -0
  104. package/components/ui/Tooltip/Tooltip.tsx +55 -0
  105. package/components/ui/Tooltip/index.ts +2 -0
  106. package/components/ui/VideoPlayer/VideoPlayer.module.css +120 -0
  107. package/components/ui/VideoPlayer/VideoPlayer.tsx +229 -0
  108. package/components/ui/VideoPlayer/index.ts +2 -0
  109. package/lib/cn.ts +3 -0
  110. package/lib/copy-to-clipboard.ts +21 -0
  111. package/package.json +61 -0
@@ -0,0 +1,54 @@
1
+ "use client";
2
+
3
+ import { forwardRef } from "react";
4
+ import { cn } from "@/lib/cn";
5
+ import styles from "./Browser.module.css";
6
+
7
+ export interface BrowserProps {
8
+ address?: string;
9
+ children?: React.ReactNode;
10
+ className?: string;
11
+ }
12
+
13
+ export const Browser = forwardRef<HTMLDivElement, BrowserProps>(
14
+ ({ address = "example.com", children, className }, ref) => {
15
+ return (
16
+ <div ref={ref} className={cn(styles.browser, className)}>
17
+ {/* Chrome bar */}
18
+ <div className={styles.chrome}>
19
+ <div className={styles.trafficLights}>
20
+ <span className={cn(styles.dot, styles.dotRed)} />
21
+ <span className={cn(styles.dot, styles.dotYellow)} />
22
+ <span className={cn(styles.dot, styles.dotGreen)} />
23
+ </div>
24
+
25
+ <div className={styles.nav}>
26
+ <button className={styles.navBtn} aria-label="Back">
27
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M9 3L5 7L9 11" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
28
+ </button>
29
+ <button className={styles.navBtn} aria-label="Forward">
30
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M5 3L9 7L5 11" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
31
+ </button>
32
+ <button className={styles.navBtn} aria-label="Reload">
33
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none"><path d="M2.5 7A4.5 4.5 0 1 1 4 10.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"/><path d="M2 8.5L4 10.5L6 8.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
34
+ </button>
35
+ </div>
36
+
37
+ <div className={styles.addressBar}>
38
+ <span className={styles.addressText}>{address}</span>
39
+ <button className={styles.copyBtn} aria-label="Copy URL">
40
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none"><rect x="4" y="4" width="7" height="7" rx="1.5" stroke="currentColor" strokeWidth="1.2"/><path d="M8 4V2.5A1.5 1.5 0 0 0 6.5 1H2.5A1.5 1.5 0 0 0 1 2.5V6.5A1.5 1.5 0 0 0 2.5 8H4" stroke="currentColor" strokeWidth="1.2"/></svg>
41
+ </button>
42
+ </div>
43
+ </div>
44
+
45
+ {/* Content area */}
46
+ <div className={styles.content}>
47
+ {children}
48
+ </div>
49
+ </div>
50
+ );
51
+ },
52
+ );
53
+
54
+ Browser.displayName = "Browser";
@@ -0,0 +1,2 @@
1
+ export { Browser } from "./Browser";
2
+ export type { BrowserProps } from "./Browser";
@@ -0,0 +1,188 @@
1
+ .button {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 8px;
6
+ font-family: inherit;
7
+ cursor: pointer;
8
+ transition: all 0.3s;
9
+ border: none;
10
+ outline: none;
11
+ white-space: nowrap;
12
+ position: relative;
13
+ }
14
+
15
+ .button:disabled {
16
+ opacity: 0.5;
17
+ cursor: not-allowed;
18
+ }
19
+
20
+ /* --- Variants --- */
21
+
22
+ /* Primary: solid lime background */
23
+ .primary {
24
+ color: var(--color-lime-text);
25
+ background: var(--color-lime);
26
+ border-radius: var(--radius-full);
27
+ font-weight: 700;
28
+ }
29
+
30
+ .primary:hover:not(:disabled) {
31
+ background: var(--color-lime-hover);
32
+ box-shadow: var(--shadow-btn-primary);
33
+ }
34
+
35
+ /* Secondary: bordered with subtle background */
36
+ .secondary {
37
+ color: var(--color-text-secondary);
38
+ background: var(--color-bg-card);
39
+ border: 1.5px solid var(--color-border-standard);
40
+ box-shadow: var(--shadow-card-light);
41
+ backdrop-filter: var(--blur-standard);
42
+ border-radius: var(--radius-full);
43
+ font-weight: 600;
44
+ }
45
+
46
+ .secondary:hover:not(:disabled) {
47
+ color: var(--color-text-primary);
48
+ background: var(--color-bg-elevated);
49
+ border-color: var(--color-border-medium);
50
+ }
51
+
52
+ /* Tertiary: text only, no background, no border */
53
+ .tertiary {
54
+ color: var(--color-text-secondary);
55
+ background: transparent;
56
+ border: 1.5px solid transparent;
57
+ border-radius: var(--radius-full);
58
+ font-weight: 600;
59
+ }
60
+
61
+ .tertiary:hover:not(:disabled) {
62
+ color: var(--color-text-primary);
63
+ background: var(--color-bg-hover);
64
+ }
65
+
66
+ /* Ghost: outlined */
67
+ .ghost {
68
+ color: var(--color-text-secondary);
69
+ background: transparent;
70
+ border: 1.5px solid var(--color-border-medium);
71
+ border-radius: var(--radius-full);
72
+ font-weight: 600;
73
+ }
74
+
75
+ .ghost:hover:not(:disabled) {
76
+ border-color: var(--color-border-strong);
77
+ color: var(--color-text-primary);
78
+ }
79
+
80
+ /* Accent: lime tinted */
81
+ .accent {
82
+ color: var(--color-lime);
83
+ background: var(--color-bg-lime-strong);
84
+ border: 1px solid var(--color-border-lime-medium);
85
+ border-radius: var(--radius-full);
86
+ font-weight: 600;
87
+ }
88
+
89
+ .accent:hover:not(:disabled) {
90
+ background: rgba(210, 254, 23, 0.2);
91
+ box-shadow: 0 4px 12px rgba(210, 254, 23, 0.15);
92
+ }
93
+
94
+ /* --- Sizes --- */
95
+ .sm {
96
+ height: 36px;
97
+ padding: 0 16px;
98
+ font-size: 13px;
99
+ }
100
+
101
+ .md {
102
+ height: 44px;
103
+ padding: 0 24px;
104
+ font-size: 14px;
105
+ }
106
+
107
+ .lg {
108
+ height: 52px;
109
+ padding: 0 32px;
110
+ font-size: 16px;
111
+ }
112
+
113
+ .xl {
114
+ height: 60px;
115
+ padding: 0 40px;
116
+ font-size: 18px;
117
+ }
118
+
119
+ /* --- Full width --- */
120
+ .fullWidth {
121
+ width: 100%;
122
+ }
123
+
124
+ /* --- Icon wrapper --- */
125
+ .iconWrapper {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ flex-shrink: 0;
130
+ }
131
+
132
+ .label {
133
+ display: inline-flex;
134
+ align-items: center;
135
+ }
136
+
137
+ /* --- Icon only --- */
138
+ .iconOnly {
139
+ padding: 0;
140
+ }
141
+
142
+ .iconOnly.sm {
143
+ width: 36px;
144
+ }
145
+
146
+ .iconOnly.md {
147
+ width: 44px;
148
+ }
149
+
150
+ .iconOnly.lg {
151
+ width: 52px;
152
+ }
153
+
154
+ .iconOnly.xl {
155
+ width: 60px;
156
+ }
157
+
158
+ /* --- Loading state --- */
159
+ .loading {
160
+ pointer-events: none;
161
+ }
162
+
163
+ .spinner {
164
+ width: 16px;
165
+ height: 16px;
166
+ border: 2px solid currentColor;
167
+ border-top-color: transparent;
168
+ border-radius: 50%;
169
+ animation: spin 0.6s linear infinite;
170
+ flex-shrink: 0;
171
+ }
172
+
173
+ .sm .spinner {
174
+ width: 14px;
175
+ height: 14px;
176
+ }
177
+
178
+ .lg .spinner,
179
+ .xl .spinner {
180
+ width: 18px;
181
+ height: 18px;
182
+ }
183
+
184
+ @keyframes spin {
185
+ to {
186
+ transform: rotate(360deg);
187
+ }
188
+ }
@@ -0,0 +1,74 @@
1
+ import { forwardRef } from "react";
2
+ import { cn } from "@/lib/cn";
3
+ import styles from "./Button.module.css";
4
+
5
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
6
+ variant?: "primary" | "secondary" | "tertiary" | "ghost" | "accent";
7
+ size?: "sm" | "md" | "lg" | "xl";
8
+ fullWidth?: boolean;
9
+ icon?: React.ReactNode;
10
+ iconPosition?: "leading" | "trailing";
11
+ iconOnly?: boolean;
12
+ isLoading?: boolean;
13
+ showTextWhileLoading?: boolean;
14
+ }
15
+
16
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
17
+ (
18
+ {
19
+ variant = "primary",
20
+ size = "md",
21
+ fullWidth,
22
+ icon,
23
+ iconPosition = "leading",
24
+ iconOnly,
25
+ isLoading,
26
+ showTextWhileLoading,
27
+ disabled,
28
+ className,
29
+ children,
30
+ ...props
31
+ },
32
+ ref
33
+ ) => {
34
+ const isDisabled = disabled || isLoading;
35
+
36
+ return (
37
+ <button
38
+ ref={ref}
39
+ className={cn(
40
+ styles.button,
41
+ styles[variant],
42
+ styles[size],
43
+ fullWidth ? styles.fullWidth : undefined,
44
+ iconOnly ? styles.iconOnly : undefined,
45
+ isLoading ? styles.loading : undefined,
46
+ className
47
+ )}
48
+ disabled={isDisabled}
49
+ {...props}
50
+ >
51
+ {isLoading && (
52
+ <span className={styles.spinner} />
53
+ )}
54
+ {isLoading && !showTextWhileLoading ? null : (
55
+ <>
56
+ {icon && iconPosition === "leading" && !iconOnly && (
57
+ <span className={styles.iconWrapper}>{icon}</span>
58
+ )}
59
+ {iconOnly ? (
60
+ <span className={styles.iconWrapper}>{icon}</span>
61
+ ) : (
62
+ <span className={styles.label}>{children}</span>
63
+ )}
64
+ {icon && iconPosition === "trailing" && !iconOnly && (
65
+ <span className={styles.iconWrapper}>{icon}</span>
66
+ )}
67
+ </>
68
+ )}
69
+ </button>
70
+ );
71
+ }
72
+ );
73
+
74
+ Button.displayName = "Button";
@@ -0,0 +1,2 @@
1
+ export { Button } from "./Button";
2
+ export type { ButtonProps } from "./Button";
@@ -0,0 +1,154 @@
1
+ .wrapper {
2
+ position: relative;
3
+ display: inline-flex;
4
+ }
5
+
6
+ /* --- Base button --- */
7
+ .utilityButton {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ border: 1.5px solid transparent;
12
+ cursor: pointer;
13
+ transition: all 0.2s;
14
+ outline: none;
15
+ font-family: inherit;
16
+ flex-shrink: 0;
17
+ }
18
+
19
+ .utilityButton:disabled {
20
+ opacity: 0.4;
21
+ cursor: not-allowed;
22
+ }
23
+
24
+ /* --- Icon wrapper --- */
25
+ .iconWrap {
26
+ display: inline-flex;
27
+ align-items: center;
28
+ justify-content: center;
29
+ line-height: 0;
30
+ }
31
+
32
+ /* --- Sizes --- */
33
+ .size-xs {
34
+ width: 28px;
35
+ height: 28px;
36
+ border-radius: 6px;
37
+ }
38
+
39
+ .size-xs .iconWrap {
40
+ font-size: 14px;
41
+ }
42
+
43
+ .size-sm {
44
+ width: 36px;
45
+ height: 36px;
46
+ border-radius: 8px;
47
+ }
48
+
49
+ .size-sm .iconWrap {
50
+ font-size: 16px;
51
+ }
52
+
53
+ .size-md {
54
+ width: 44px;
55
+ height: 44px;
56
+ border-radius: 10px;
57
+ }
58
+
59
+ .size-md .iconWrap {
60
+ font-size: 18px;
61
+ }
62
+
63
+ .size-lg {
64
+ width: 52px;
65
+ height: 52px;
66
+ border-radius: 12px;
67
+ }
68
+
69
+ .size-lg .iconWrap {
70
+ font-size: 20px;
71
+ }
72
+
73
+ /* --- Color: Primary --- */
74
+ .color-primary {
75
+ background: var(--color-lime);
76
+ color: var(--color-lime-text);
77
+ border-color: transparent;
78
+ }
79
+
80
+ .color-primary:hover:not(:disabled) {
81
+ background: var(--color-lime-hover);
82
+ box-shadow: var(--shadow-btn-primary);
83
+ }
84
+
85
+ /* --- Color: Secondary --- */
86
+ .color-secondary {
87
+ background: var(--color-bg-card);
88
+ color: var(--color-text-secondary);
89
+ border-color: var(--color-border-standard);
90
+ backdrop-filter: var(--blur-standard);
91
+ }
92
+
93
+ .color-secondary:hover:not(:disabled) {
94
+ background: var(--color-bg-elevated);
95
+ color: var(--color-text-primary);
96
+ border-color: var(--color-border-medium);
97
+ box-shadow: var(--shadow-card-light);
98
+ }
99
+
100
+ /* --- Color: Tertiary --- */
101
+ .color-tertiary {
102
+ background: transparent;
103
+ color: var(--color-text-tertiary);
104
+ border-color: transparent;
105
+ }
106
+
107
+ .color-tertiary:hover:not(:disabled) {
108
+ background: var(--color-bg-hover);
109
+ color: var(--color-text-primary);
110
+ }
111
+
112
+ /* --- Color: Destructive --- */
113
+ .color-destructive {
114
+ background: transparent;
115
+ color: var(--color-text-secondary);
116
+ border-color: var(--color-border-standard);
117
+ }
118
+
119
+ .color-destructive:hover:not(:disabled) {
120
+ background: rgba(239, 68, 68, 0.1);
121
+ color: #ef4444;
122
+ border-color: rgba(239, 68, 68, 0.4);
123
+ }
124
+
125
+ /* --- Tooltip --- */
126
+ .tooltip {
127
+ position: absolute;
128
+ bottom: calc(100% + 8px);
129
+ left: 50%;
130
+ transform: translateX(-50%);
131
+ background: var(--color-bg-elevated);
132
+ color: var(--color-text-primary);
133
+ border: 1px solid var(--color-border-standard);
134
+ border-radius: 8px;
135
+ padding: 4px 10px;
136
+ font-size: 12px;
137
+ font-weight: 500;
138
+ white-space: nowrap;
139
+ pointer-events: none;
140
+ z-index: 50;
141
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
142
+ animation: tooltipIn 0.15s ease-out;
143
+ }
144
+
145
+ @keyframes tooltipIn {
146
+ from {
147
+ opacity: 0;
148
+ transform: translateX(-50%) translateY(4px);
149
+ }
150
+ to {
151
+ opacity: 1;
152
+ transform: translateX(-50%) translateY(0);
153
+ }
154
+ }
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import { forwardRef, useState, useRef, useEffect } from "react";
4
+ import { cn } from "@/lib/cn";
5
+ import styles from "./ButtonUtility.module.css";
6
+
7
+ export type UtilitySize = "xs" | "sm" | "md" | "lg";
8
+ export type UtilityColor = "primary" | "secondary" | "tertiary" | "destructive";
9
+
10
+ export interface ButtonUtilityProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
11
+ icon: React.ReactNode;
12
+ size?: UtilitySize;
13
+ color?: UtilityColor;
14
+ tooltip?: string;
15
+ }
16
+
17
+ export const ButtonUtility = forwardRef<HTMLButtonElement, ButtonUtilityProps>(
18
+ ({ icon, size = "sm", color = "secondary", tooltip, disabled, className, ...props }, ref) => {
19
+ const [showTooltip, setShowTooltip] = useState(false);
20
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
21
+
22
+ const handleMouseEnter = () => {
23
+ if (!tooltip || disabled) return;
24
+ timeoutRef.current = setTimeout(() => setShowTooltip(true), 300);
25
+ };
26
+
27
+ const handleMouseLeave = () => {
28
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
29
+ setShowTooltip(false);
30
+ };
31
+
32
+ useEffect(() => {
33
+ return () => {
34
+ if (timeoutRef.current) clearTimeout(timeoutRef.current);
35
+ };
36
+ }, []);
37
+
38
+ return (
39
+ <div
40
+ className={styles.wrapper}
41
+ onMouseEnter={handleMouseEnter}
42
+ onMouseLeave={handleMouseLeave}
43
+ >
44
+ <button
45
+ ref={ref}
46
+ className={cn(
47
+ styles.utilityButton,
48
+ styles[`size-${size}`],
49
+ styles[`color-${color}`],
50
+ className
51
+ )}
52
+ disabled={disabled}
53
+ {...props}
54
+ >
55
+ <span className={styles.iconWrap}>{icon}</span>
56
+ </button>
57
+ {tooltip && showTooltip && (
58
+ <span className={styles.tooltip}>{tooltip}</span>
59
+ )}
60
+ </div>
61
+ );
62
+ }
63
+ );
64
+
65
+ ButtonUtility.displayName = "ButtonUtility";
@@ -0,0 +1,2 @@
1
+ export { ButtonUtility } from "./ButtonUtility";
2
+ export type { ButtonUtilityProps, UtilitySize, UtilityColor } from "./ButtonUtility";
@@ -0,0 +1,80 @@
1
+ .card {
2
+ position: relative;
3
+ border-radius: var(--radius-2xl);
4
+ background: var(--color-bg-card);
5
+ border: 1.5px solid var(--color-border-standard);
6
+ box-shadow: var(--shadow-card);
7
+ backdrop-filter: var(--blur-standard);
8
+ transition: all 0.35s ease;
9
+ overflow: hidden;
10
+ }
11
+
12
+ .card.hoverable:hover {
13
+ transform: translateY(-4px);
14
+ box-shadow: var(--shadow-card-hover);
15
+ }
16
+
17
+ /* Variants */
18
+ .standard {
19
+ border-radius: var(--radius-2xl);
20
+ }
21
+
22
+ .light {
23
+ border-radius: var(--radius-xl);
24
+ background: rgba(248, 248, 248, 0.02);
25
+ border-width: 1px;
26
+ border-color: var(--color-border-subtle);
27
+ box-shadow: var(--shadow-card-light);
28
+ }
29
+
30
+ .accent {
31
+ background: var(--color-bg-lime-subtle);
32
+ border-color: var(--color-border-lime-medium);
33
+ box-shadow: var(--shadow-card-accent);
34
+ }
35
+
36
+ .framework {
37
+ border-radius: 48px;
38
+ background: var(--gradient-framework);
39
+ box-shadow: 0px 16px 48px rgba(0, 0, 0, 0.2);
40
+ }
41
+
42
+ /* Padding sizes */
43
+ .paddingNone {
44
+ padding: 0;
45
+ }
46
+
47
+ .paddingSm {
48
+ padding: 16px;
49
+ }
50
+
51
+ .paddingMd {
52
+ padding: 24px;
53
+ }
54
+
55
+ .paddingLg {
56
+ padding: 32px;
57
+ }
58
+
59
+ .paddingXl {
60
+ padding: 48px;
61
+ }
62
+
63
+ /* Responsive */
64
+ @media (max-width: 767px) {
65
+ .card {
66
+ border-radius: var(--radius-xl);
67
+ }
68
+
69
+ .framework {
70
+ border-radius: var(--radius-2xl);
71
+ }
72
+
73
+ .paddingLg {
74
+ padding: 24px;
75
+ }
76
+
77
+ .paddingXl {
78
+ padding: 32px 20px;
79
+ }
80
+ }
@@ -0,0 +1,39 @@
1
+ import { forwardRef } from "react";
2
+ import { cn } from "@/lib/cn";
3
+ import styles from "./Card.module.css";
4
+
5
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
6
+ variant?: "standard" | "light" | "accent" | "framework";
7
+ padding?: "none" | "sm" | "md" | "lg" | "xl";
8
+ hoverable?: boolean;
9
+ }
10
+
11
+ export const Card = forwardRef<HTMLDivElement, CardProps>(
12
+ ({ variant = "standard", padding = "lg", hoverable = false, className, children, ...props }, ref) => {
13
+ const paddingMap: Record<string, string> = {
14
+ none: styles.paddingNone,
15
+ sm: styles.paddingSm,
16
+ md: styles.paddingMd,
17
+ lg: styles.paddingLg,
18
+ xl: styles.paddingXl,
19
+ };
20
+
21
+ return (
22
+ <div
23
+ ref={ref}
24
+ className={cn(
25
+ styles.card,
26
+ styles[variant],
27
+ paddingMap[padding],
28
+ hoverable ? styles.hoverable : undefined,
29
+ className
30
+ )}
31
+ {...props}
32
+ >
33
+ {children}
34
+ </div>
35
+ );
36
+ }
37
+ );
38
+
39
+ Card.displayName = "Card";
@@ -0,0 +1,2 @@
1
+ export { Card } from "./Card";
2
+ export type { CardProps } from "./Card";