omnira-ui 0.1.1 → 0.2.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.
- package/README.md +43 -15
- package/app/globals.css +347 -0
- package/cli/omnira-init.mjs +154 -46
- package/components/ui/AppStoreButton/AppStoreButton.tsx +1 -1
- package/components/ui/Avatar/Avatar.tsx +1 -1
- package/components/ui/Badge/Badge.tsx +1 -1
- package/components/ui/BadgeGroup/BadgeGroup.tsx +1 -1
- package/components/ui/Browser/Browser.tsx +1 -1
- package/components/ui/Button/Button.tsx +1 -1
- package/components/ui/ButtonUtility/ButtonUtility.tsx +1 -1
- package/components/ui/Card/Card.tsx +1 -1
- package/components/ui/Checkbox/Checkbox.tsx +1 -1
- package/components/ui/Collapse/Collapse.tsx +1 -1
- package/components/ui/CreditCard/CreditCard.tsx +1 -1
- package/components/ui/Dropdown/Dropdown.tsx +1 -1
- package/components/ui/Illustration/Illustration.tsx +1 -1
- package/components/ui/Input/Input.tsx +1 -1
- package/components/ui/Phone/Phone.tsx +1 -1
- package/components/ui/PinInput/PinInput.tsx +1 -1
- package/components/ui/ProgressBar/ProgressBar.tsx +1 -1
- package/components/ui/QRCode/QRCode.tsx +1 -1
- package/components/ui/RadioButton/RadioButton.tsx +1 -1
- package/components/ui/RadioGroup/RadioGroup.tsx +1 -1
- package/components/ui/Rating/Rating.tsx +1 -1
- package/components/ui/Select/Select.tsx +1 -1
- package/components/ui/SidebarNavigation/SidebarDual.tsx +1 -1
- package/components/ui/SidebarNavigation/SidebarFeatureCard.tsx +4 -4
- package/components/ui/SidebarNavigation/SidebarParts.tsx +1 -1
- package/components/ui/SidebarNavigation/SidebarSectionDividers.tsx +1 -1
- package/components/ui/SidebarNavigation/SidebarSectionHeadings.tsx +1 -1
- package/components/ui/SidebarNavigation/SidebarSimple.tsx +1 -1
- package/components/ui/SidebarNavigation/SidebarSlim.tsx +1 -1
- package/components/ui/Slider/Slider.tsx +1 -1
- package/components/ui/SocialButton/SocialButton.tsx +1 -1
- package/components/ui/Tag/Tag.tsx +1 -1
- package/components/ui/TextEditor/TextEditor.tsx +1 -1
- package/components/ui/Textarea/Textarea.tsx +1 -1
- package/components/ui/Toggle/Toggle.tsx +1 -1
- package/components/ui/Tooltip/Tooltip.tsx +1 -1
- package/components/ui/VideoPlayer/VideoPlayer.tsx +1 -1
- package/lib/theme-context.tsx +79 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -10,33 +10,55 @@ A premium glassmorphism design system — dark-first, glass-forward, no compromi
|
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
13
|
-
### 1.
|
|
13
|
+
### 1. Scaffold your project
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
17
|
-
# or
|
|
18
|
-
pnpm add omnira-ui
|
|
16
|
+
npx omnira-ui init
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
The CLI scaffolds the full design system into your project:
|
|
20
|
+
|
|
21
|
+
- **32 base components** → `components/ui/`
|
|
22
|
+
- **Utility helpers** → `lib/cn.ts`, `lib/copy-to-clipboard.ts`
|
|
23
|
+
- **Theme provider** → `lib/theme-context.tsx` (system preference detection + toggle)
|
|
24
|
+
- **Design tokens** → `app/globals.css` (all CSS custom properties)
|
|
25
|
+
- **Accent overrides** → `omnira-overrides.css` (if non-default color)
|
|
26
|
+
- **Config file** → `omnira.config.ts`
|
|
27
|
+
|
|
28
|
+
You choose:
|
|
29
|
+
- **Accent color** — 10 presets: Lime (default), Blue, Cyan, Green, Orange, Pink, Purple, Red, Teal, Yellow
|
|
30
|
+
- **Theme mode** — Dark-first or Light-first (respects system preference)
|
|
31
|
+
|
|
32
|
+
### 2. Install peer dependencies
|
|
22
33
|
|
|
23
34
|
```bash
|
|
24
|
-
|
|
35
|
+
npm install iconsax-react
|
|
25
36
|
```
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
### 3. Set up your layout
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// app/layout.tsx
|
|
42
|
+
import "./globals.css";
|
|
43
|
+
import { Providers } from "./providers";
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
export default function RootLayout({ children }) {
|
|
46
|
+
return (
|
|
47
|
+
<html data-theme="dark">
|
|
48
|
+
<body>
|
|
49
|
+
<Providers>{children}</Providers>
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
33
55
|
|
|
34
|
-
###
|
|
56
|
+
### 4. Use components
|
|
35
57
|
|
|
36
58
|
```tsx
|
|
37
|
-
import { Button } from "
|
|
38
|
-
import { Badge } from "
|
|
39
|
-
import { Input } from "
|
|
59
|
+
import { Button } from "@/components/ui/Button";
|
|
60
|
+
import { Badge } from "@/components/ui/Badge";
|
|
61
|
+
import { Input } from "@/components/ui/Input";
|
|
40
62
|
|
|
41
63
|
export default function MyPage() {
|
|
42
64
|
return (
|
|
@@ -49,6 +71,12 @@ export default function MyPage() {
|
|
|
49
71
|
}
|
|
50
72
|
```
|
|
51
73
|
|
|
74
|
+
### 5. Need more components?
|
|
75
|
+
|
|
76
|
+
Browse the full library and copy advanced components (Sidebar, Feature Cards, etc.) directly from the docs:
|
|
77
|
+
|
|
78
|
+
**[ui.omnira.space →](https://ui.omnira.space)**
|
|
79
|
+
|
|
52
80
|
---
|
|
53
81
|
|
|
54
82
|
## Components
|
package/app/globals.css
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Omnira UI — Design System CSS Variables
|
|
3
|
+
============================================ */
|
|
4
|
+
|
|
5
|
+
/* --- Font Faces --- */
|
|
6
|
+
@font-face {
|
|
7
|
+
font-family: "Host Grotesk";
|
|
8
|
+
src: url("/fonts/HostGrotesk-Bold.woff2") format("woff2");
|
|
9
|
+
font-weight: 700;
|
|
10
|
+
font-style: normal;
|
|
11
|
+
font-display: swap;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* --- CSS Custom Properties: Dark Mode (Default) --- */
|
|
15
|
+
[data-theme="dark"] {
|
|
16
|
+
--color-lime: #D2FE17;
|
|
17
|
+
--color-lime-hover: #c0e616;
|
|
18
|
+
--color-lime-gradient: #ABC928;
|
|
19
|
+
--color-lime-text: #121212;
|
|
20
|
+
|
|
21
|
+
--color-bg-primary: #202020;
|
|
22
|
+
--color-bg-secondary: #1a1a1a;
|
|
23
|
+
--color-bg-card: rgba(248, 248, 248, 0.03);
|
|
24
|
+
--color-bg-elevated: rgba(248, 248, 248, 0.06);
|
|
25
|
+
--color-bg-input: rgba(248, 248, 248, 0.04);
|
|
26
|
+
--color-bg-hover: rgba(248, 248, 248, 0.05);
|
|
27
|
+
--color-bg-overlay: rgba(10, 10, 10, 0.97);
|
|
28
|
+
--color-bg-sidebar: #1a1a1a;
|
|
29
|
+
|
|
30
|
+
--color-text-primary: rgba(248, 248, 248, 0.95);
|
|
31
|
+
--color-text-secondary: rgba(248, 248, 248, 0.70);
|
|
32
|
+
--color-text-tertiary: rgba(248, 248, 248, 0.50);
|
|
33
|
+
|
|
34
|
+
--color-border-subtle: rgba(255, 255, 255, 0.05);
|
|
35
|
+
--color-border-standard: rgba(255, 255, 255, 0.06);
|
|
36
|
+
--color-border-medium: rgba(255, 255, 255, 0.08);
|
|
37
|
+
--color-border-strong: rgba(255, 255, 255, 0.15);
|
|
38
|
+
|
|
39
|
+
--color-border-lime-subtle: rgba(210, 254, 23, 0.1);
|
|
40
|
+
--color-border-lime-medium: rgba(210, 254, 23, 0.15);
|
|
41
|
+
--color-border-lime-strong: rgba(210, 254, 23, 0.3);
|
|
42
|
+
|
|
43
|
+
--color-bg-lime-subtle: rgba(210, 254, 23, 0.06);
|
|
44
|
+
--color-bg-lime-medium: rgba(210, 254, 23, 0.08);
|
|
45
|
+
--color-bg-lime-strong: rgba(210, 254, 23, 0.12);
|
|
46
|
+
|
|
47
|
+
--color-error: #ef4444;
|
|
48
|
+
--color-warning: #ffbd2e;
|
|
49
|
+
--color-success: #28c840;
|
|
50
|
+
--color-info: #3b82f6;
|
|
51
|
+
|
|
52
|
+
--color-error-bg: rgba(239, 68, 68, 0.08);
|
|
53
|
+
--color-error-border: rgba(239, 68, 68, 0.15);
|
|
54
|
+
--color-warning-bg: rgba(255, 189, 46, 0.08);
|
|
55
|
+
--color-warning-border: rgba(255, 189, 46, 0.15);
|
|
56
|
+
--color-success-bg: rgba(40, 200, 64, 0.08);
|
|
57
|
+
--color-success-border: rgba(40, 200, 64, 0.15);
|
|
58
|
+
--color-info-bg: rgba(59, 130, 246, 0.08);
|
|
59
|
+
--color-info-border: rgba(59, 130, 246, 0.15);
|
|
60
|
+
|
|
61
|
+
--shadow-card: inset 2px 4px 16px rgba(248, 248, 248, 0.06), 0px 8px 32px rgba(0, 0, 0, 0.2);
|
|
62
|
+
--shadow-card-light: inset 1px 2px 12px rgba(248, 248, 248, 0.03), 0px 8px 28px rgba(0, 0, 0, 0.12);
|
|
63
|
+
--shadow-card-accent: inset 2px 4px 16px rgba(210, 254, 23, 0.04), 0px 16px 48px rgba(0, 0, 0, 0.2);
|
|
64
|
+
--shadow-card-hover: inset 2px 4px 16px rgba(248, 248, 248, 0.08), 0px 12px 40px rgba(0, 0, 0, 0.28);
|
|
65
|
+
--shadow-btn-primary: 0 8px 24px rgba(210, 254, 23, 0.3);
|
|
66
|
+
--shadow-glow-lime: 0 0 8px rgba(210, 254, 23, 0.6), 0 0 16px rgba(210, 254, 23, 0.3);
|
|
67
|
+
|
|
68
|
+
--gradient-text: linear-gradient(93deg, rgba(248, 248, 248, 0.9), rgba(248, 248, 248, 0.5));
|
|
69
|
+
--gradient-framework: linear-gradient(124deg, rgba(248, 248, 248, 0.04) 0%, rgba(248, 248, 248, 0.01) 46.5%), linear-gradient(180deg, rgba(248, 248, 248, 0.03) 0%, rgba(248, 248, 248, 0.00) 100%);
|
|
70
|
+
--gradient-progress: linear-gradient(90deg, #D2FE17, #e5ff54);
|
|
71
|
+
|
|
72
|
+
--blur-standard: blur(50px);
|
|
73
|
+
--radius-sm: 8px;
|
|
74
|
+
--radius-md: 14px;
|
|
75
|
+
--radius-lg: 20px;
|
|
76
|
+
--radius-xl: 28px;
|
|
77
|
+
--radius-2xl: 32px;
|
|
78
|
+
--radius-3xl: 40px;
|
|
79
|
+
--radius-full: 100px;
|
|
80
|
+
|
|
81
|
+
--toggle-track-off: rgba(248, 248, 248, 0.1);
|
|
82
|
+
--color-grid-line: rgba(248, 248, 248, 0.05);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* --- CSS Custom Properties: Light Mode --- */
|
|
86
|
+
[data-theme="light"] {
|
|
87
|
+
--color-lime: #8AB400;
|
|
88
|
+
--color-lime-hover: #7DA310;
|
|
89
|
+
--color-lime-gradient: #6B9A00;
|
|
90
|
+
--color-lime-text: #ffffff;
|
|
91
|
+
|
|
92
|
+
--color-bg-primary: #f5f5f7;
|
|
93
|
+
--color-bg-secondary: #ebebed;
|
|
94
|
+
--color-bg-card: rgba(255, 255, 255, 0.7);
|
|
95
|
+
--color-bg-elevated: rgba(255, 255, 255, 0.9);
|
|
96
|
+
--color-bg-input: rgba(0, 0, 0, 0.03);
|
|
97
|
+
--color-bg-hover: rgba(0, 0, 0, 0.04);
|
|
98
|
+
--color-bg-overlay: rgba(255, 255, 255, 0.97);
|
|
99
|
+
--color-bg-sidebar: #ffffff;
|
|
100
|
+
|
|
101
|
+
--color-text-primary: rgba(18, 18, 18, 0.95);
|
|
102
|
+
--color-text-secondary: rgba(18, 18, 18, 0.65);
|
|
103
|
+
--color-text-tertiary: rgba(18, 18, 18, 0.45);
|
|
104
|
+
|
|
105
|
+
--color-border-subtle: rgba(0, 0, 0, 0.06);
|
|
106
|
+
--color-border-standard: rgba(0, 0, 0, 0.08);
|
|
107
|
+
--color-border-medium: rgba(0, 0, 0, 0.10);
|
|
108
|
+
--color-border-strong: rgba(0, 0, 0, 0.18);
|
|
109
|
+
|
|
110
|
+
--color-border-lime-subtle: rgba(138, 180, 0, 0.15);
|
|
111
|
+
--color-border-lime-medium: rgba(138, 180, 0, 0.25);
|
|
112
|
+
--color-border-lime-strong: rgba(138, 180, 0, 0.4);
|
|
113
|
+
|
|
114
|
+
--color-bg-lime-subtle: rgba(138, 180, 0, 0.06);
|
|
115
|
+
--color-bg-lime-medium: rgba(138, 180, 0, 0.10);
|
|
116
|
+
--color-bg-lime-strong: rgba(138, 180, 0, 0.15);
|
|
117
|
+
|
|
118
|
+
--color-error: #dc2626;
|
|
119
|
+
--color-warning: #d97706;
|
|
120
|
+
--color-success: #16a34a;
|
|
121
|
+
--color-info: #2563eb;
|
|
122
|
+
|
|
123
|
+
--color-error-bg: rgba(220, 38, 38, 0.06);
|
|
124
|
+
--color-error-border: rgba(220, 38, 38, 0.15);
|
|
125
|
+
--color-warning-bg: rgba(217, 119, 6, 0.06);
|
|
126
|
+
--color-warning-border: rgba(217, 119, 6, 0.15);
|
|
127
|
+
--color-success-bg: rgba(22, 163, 74, 0.06);
|
|
128
|
+
--color-success-border: rgba(22, 163, 74, 0.15);
|
|
129
|
+
--color-info-bg: rgba(37, 99, 235, 0.06);
|
|
130
|
+
--color-info-border: rgba(37, 99, 235, 0.15);
|
|
131
|
+
|
|
132
|
+
--shadow-card: 0px 1px 3px rgba(0, 0, 0, 0.04), 0px 6px 24px rgba(0, 0, 0, 0.06);
|
|
133
|
+
--shadow-card-light: 0px 1px 2px rgba(0, 0, 0, 0.03), 0px 4px 16px rgba(0, 0, 0, 0.04);
|
|
134
|
+
--shadow-card-accent: 0px 2px 8px rgba(138, 180, 0, 0.08), 0px 12px 36px rgba(0, 0, 0, 0.06);
|
|
135
|
+
--shadow-card-hover: 0px 2px 6px rgba(0, 0, 0, 0.06), 0px 12px 36px rgba(0, 0, 0, 0.1);
|
|
136
|
+
--shadow-btn-primary: 0 6px 20px rgba(138, 180, 0, 0.25);
|
|
137
|
+
--shadow-glow-lime: 0 0 6px rgba(138, 180, 0, 0.4), 0 0 12px rgba(138, 180, 0, 0.2);
|
|
138
|
+
|
|
139
|
+
--gradient-text: linear-gradient(93deg, rgba(18, 18, 18, 0.95), rgba(18, 18, 18, 0.55));
|
|
140
|
+
--gradient-framework: linear-gradient(124deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.4) 46.5%), linear-gradient(180deg, rgba(255, 255, 255, 0.6) 0%, rgba(255, 255, 255, 0.2) 100%);
|
|
141
|
+
--gradient-progress: linear-gradient(90deg, #8AB400, #a5d600);
|
|
142
|
+
|
|
143
|
+
--blur-standard: blur(50px);
|
|
144
|
+
--radius-sm: 8px;
|
|
145
|
+
--radius-md: 14px;
|
|
146
|
+
--radius-lg: 20px;
|
|
147
|
+
--radius-xl: 28px;
|
|
148
|
+
--radius-2xl: 32px;
|
|
149
|
+
--radius-3xl: 40px;
|
|
150
|
+
--radius-full: 100px;
|
|
151
|
+
|
|
152
|
+
--toggle-track-off: rgba(0, 0, 0, 0.12);
|
|
153
|
+
--color-grid-line: rgba(0, 0, 0, 0.06);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* --- Font Variables --- */
|
|
157
|
+
:root {
|
|
158
|
+
--font-display: "Host Grotesk", sans-serif;
|
|
159
|
+
--font-body: "Rubik", sans-serif;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* --- Global Reset & Base Styles --- */
|
|
163
|
+
*,
|
|
164
|
+
*::before,
|
|
165
|
+
*::after {
|
|
166
|
+
box-sizing: border-box;
|
|
167
|
+
padding: 0;
|
|
168
|
+
margin: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
html,
|
|
172
|
+
body {
|
|
173
|
+
max-width: 100vw;
|
|
174
|
+
overflow-x: hidden;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
html {
|
|
178
|
+
scroll-behavior: smooth;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
body {
|
|
182
|
+
font-family: var(--font-body);
|
|
183
|
+
font-size: 16px;
|
|
184
|
+
font-weight: 400;
|
|
185
|
+
line-height: 1.6;
|
|
186
|
+
color: var(--color-text-primary);
|
|
187
|
+
background: var(--color-bg-primary);
|
|
188
|
+
-webkit-font-smoothing: antialiased;
|
|
189
|
+
-moz-osx-font-smoothing: grayscale;
|
|
190
|
+
transition: background 0.4s ease, color 0.4s ease;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
a {
|
|
194
|
+
color: inherit;
|
|
195
|
+
text-decoration: none;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
button {
|
|
199
|
+
font-family: inherit;
|
|
200
|
+
cursor: pointer;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
input,
|
|
204
|
+
textarea,
|
|
205
|
+
select {
|
|
206
|
+
font-family: inherit;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
img {
|
|
210
|
+
max-width: 100%;
|
|
211
|
+
display: block;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* --- Typography Utility Classes --- */
|
|
215
|
+
.gradient-text {
|
|
216
|
+
background-image: var(--gradient-text);
|
|
217
|
+
-webkit-background-clip: text;
|
|
218
|
+
-webkit-text-fill-color: transparent;
|
|
219
|
+
background-clip: text;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.lime-text {
|
|
223
|
+
color: var(--color-lime);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.font-display {
|
|
227
|
+
font-family: var(--font-display);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.font-body {
|
|
231
|
+
font-family: var(--font-body);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* --- Glass Card Utility --- */
|
|
235
|
+
.glass-card {
|
|
236
|
+
border-radius: var(--radius-2xl);
|
|
237
|
+
background: var(--color-bg-card);
|
|
238
|
+
border: 1.5px solid var(--color-border-standard);
|
|
239
|
+
box-shadow: var(--shadow-card);
|
|
240
|
+
backdrop-filter: var(--blur-standard);
|
|
241
|
+
transition: all 0.35s ease;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.glass-card:hover {
|
|
245
|
+
transform: translateY(-4px);
|
|
246
|
+
box-shadow: var(--shadow-card-hover);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/* --- Icon Box Utility --- */
|
|
250
|
+
.icon-box {
|
|
251
|
+
display: flex;
|
|
252
|
+
justify-content: center;
|
|
253
|
+
align-items: center;
|
|
254
|
+
width: 48px;
|
|
255
|
+
height: 48px;
|
|
256
|
+
border-radius: var(--radius-md);
|
|
257
|
+
background: var(--color-bg-lime-subtle);
|
|
258
|
+
border: 1px solid var(--color-border-lime-subtle);
|
|
259
|
+
transition: all 0.3s;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* --- Status Dots --- */
|
|
263
|
+
.dot-active {
|
|
264
|
+
background: var(--color-lime);
|
|
265
|
+
box-shadow: var(--shadow-glow-lime);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.dot-warning {
|
|
269
|
+
background: var(--color-warning);
|
|
270
|
+
box-shadow: 0 0 8px rgba(255, 189, 46, 0.5);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.dot-error {
|
|
274
|
+
background: var(--color-error);
|
|
275
|
+
box-shadow: 0 0 8px rgba(239, 68, 68, 0.5);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* --- Animations --- */
|
|
279
|
+
@keyframes fadeInUp {
|
|
280
|
+
from {
|
|
281
|
+
opacity: 0;
|
|
282
|
+
transform: translateY(24px);
|
|
283
|
+
}
|
|
284
|
+
to {
|
|
285
|
+
opacity: 1;
|
|
286
|
+
transform: translateY(0);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@keyframes scaleIn {
|
|
291
|
+
from {
|
|
292
|
+
opacity: 0;
|
|
293
|
+
transform: scale(0.5);
|
|
294
|
+
}
|
|
295
|
+
to {
|
|
296
|
+
opacity: 1;
|
|
297
|
+
transform: scale(1);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
@keyframes livePulse {
|
|
302
|
+
0%, 100% {
|
|
303
|
+
opacity: 1;
|
|
304
|
+
box-shadow: 0 0 0 0 rgba(210, 254, 23, 0.4);
|
|
305
|
+
}
|
|
306
|
+
50% {
|
|
307
|
+
opacity: 0.7;
|
|
308
|
+
box-shadow: 0 0 0 6px rgba(210, 254, 23, 0);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
@keyframes pulse {
|
|
313
|
+
0%, 100% {
|
|
314
|
+
transform: scale(1);
|
|
315
|
+
opacity: 1;
|
|
316
|
+
}
|
|
317
|
+
50% {
|
|
318
|
+
transform: scale(1.06);
|
|
319
|
+
opacity: 0.85;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@keyframes button-circle {
|
|
324
|
+
0% { transform: translateX(0) rotate(0); }
|
|
325
|
+
25% { transform: translateX(-30px) rotate(90deg); }
|
|
326
|
+
50% { transform: translateX(0) rotate(180deg); }
|
|
327
|
+
75% { transform: translateX(30px) rotate(270deg); }
|
|
328
|
+
100% { transform: translateX(0) rotate(360deg); }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* --- Scrollbar Styling --- */
|
|
332
|
+
::-webkit-scrollbar {
|
|
333
|
+
width: 6px;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
::-webkit-scrollbar-track {
|
|
337
|
+
background: transparent;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
::-webkit-scrollbar-thumb {
|
|
341
|
+
background: var(--color-border-medium);
|
|
342
|
+
border-radius: 3px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
::-webkit-scrollbar-thumb:hover {
|
|
346
|
+
background: var(--color-border-strong);
|
|
347
|
+
}
|
package/cli/omnira-init.mjs
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Omnira UI —
|
|
4
|
+
* Omnira UI — Project Scaffolding CLI
|
|
5
5
|
*
|
|
6
6
|
* Usage: npx omnira-ui init
|
|
7
|
-
* node cli/omnira-init.mjs
|
|
8
7
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Scaffolds the full Omnira UI design system into your project:
|
|
9
|
+
* - All base components → components/ui/
|
|
10
|
+
* - Utility helpers → lib/
|
|
11
|
+
* - Theme provider → lib/theme-context.tsx
|
|
12
|
+
* - Design tokens CSS → app/globals.css
|
|
13
|
+
* - Accent overrides → omnira-overrides.css (if non-default)
|
|
14
|
+
* - Config file → omnira.config.ts
|
|
15
|
+
*
|
|
16
|
+
* Advanced components (Sidebar, Feature Cards, etc.) can be copied
|
|
17
|
+
* from the documentation site: https://ui.omnira.space
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
20
|
import * as readline from "node:readline";
|
|
15
21
|
import * as fs from "node:fs";
|
|
16
22
|
import * as path from "node:path";
|
|
17
|
-
import {
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
import { COLOR_PRESETS, DEFAULT_COLOR, DEFAULT_THEME } from "./presets.mjs";
|
|
25
|
+
|
|
26
|
+
// ── Resolve package root (where this CLI lives inside node_modules) ──
|
|
18
27
|
|
|
19
|
-
|
|
28
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
+
const __dirname = path.dirname(__filename);
|
|
30
|
+
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
31
|
+
|
|
32
|
+
// ── ANSI helpers ─────────────────────────────────────────────────────
|
|
20
33
|
|
|
21
34
|
const RESET = "\x1b[0m";
|
|
22
35
|
const BOLD = "\x1b[1m";
|
|
@@ -26,6 +39,7 @@ const CYAN = "\x1b[36m";
|
|
|
26
39
|
const YELLOW = "\x1b[33m";
|
|
27
40
|
const MAGENTA = "\x1b[35m";
|
|
28
41
|
const WHITE = "\x1b[97m";
|
|
42
|
+
const RED = "\x1b[31m";
|
|
29
43
|
|
|
30
44
|
function colorize(hex) {
|
|
31
45
|
const r = parseInt(hex.slice(1, 3), 16);
|
|
@@ -63,7 +77,7 @@ function createPrompt() {
|
|
|
63
77
|
return { ask, close };
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
// ── Color picker
|
|
80
|
+
// ── Color picker ─────────────────────────────────────────────────────
|
|
67
81
|
|
|
68
82
|
async function pickColor(promptFn) {
|
|
69
83
|
const keys = Object.keys(COLOR_PRESETS);
|
|
@@ -86,17 +100,12 @@ async function pickColor(promptFn) {
|
|
|
86
100
|
|
|
87
101
|
if (!answer) return DEFAULT_COLOR;
|
|
88
102
|
|
|
89
|
-
// Match by number
|
|
90
103
|
const num = parseInt(answer, 10);
|
|
91
|
-
if (!isNaN(num) && num >= 1 && num <= keys.length)
|
|
92
|
-
return keys[num - 1];
|
|
93
|
-
}
|
|
104
|
+
if (!isNaN(num) && num >= 1 && num <= keys.length) return keys[num - 1];
|
|
94
105
|
|
|
95
|
-
// Match by name (case-insensitive)
|
|
96
106
|
const lower = answer.toLowerCase();
|
|
97
107
|
if (COLOR_PRESETS[lower]) return lower;
|
|
98
108
|
|
|
99
|
-
// Fuzzy match
|
|
100
109
|
const match = keys.find((k) => k.startsWith(lower) || COLOR_PRESETS[k].label.toLowerCase().startsWith(lower));
|
|
101
110
|
if (match) return match;
|
|
102
111
|
|
|
@@ -104,13 +113,13 @@ async function pickColor(promptFn) {
|
|
|
104
113
|
return DEFAULT_COLOR;
|
|
105
114
|
}
|
|
106
115
|
|
|
107
|
-
// ── Theme mode picker
|
|
116
|
+
// ── Theme mode picker ────────────────────────────────────────────────
|
|
108
117
|
|
|
109
118
|
async function pickTheme(promptFn) {
|
|
110
119
|
blank();
|
|
111
|
-
log(`${BOLD}${WHITE}
|
|
120
|
+
log(`${BOLD}${WHITE} Default theme mode:${RESET}`);
|
|
112
121
|
blank();
|
|
113
|
-
log(` ${DIM} 1.${RESET} Dark-first ${DIM}(default)${RESET}`);
|
|
122
|
+
log(` ${DIM} 1.${RESET} Dark-first ${DIM}(default — follows system preference)${RESET}`);
|
|
114
123
|
log(` ${DIM} 2.${RESET} Light-first`);
|
|
115
124
|
blank();
|
|
116
125
|
|
|
@@ -123,15 +132,43 @@ async function pickTheme(promptFn) {
|
|
|
123
132
|
return DEFAULT_THEME;
|
|
124
133
|
}
|
|
125
134
|
|
|
126
|
-
// ── File
|
|
135
|
+
// ── File copy utilities ──────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
function copyDirRecursive(src, dest) {
|
|
138
|
+
let count = 0;
|
|
139
|
+
if (!fs.existsSync(src)) return count;
|
|
140
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
141
|
+
|
|
142
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
143
|
+
const srcPath = path.join(src, entry.name);
|
|
144
|
+
const destPath = path.join(dest, entry.name);
|
|
145
|
+
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
count += copyDirRecursive(srcPath, destPath);
|
|
148
|
+
} else {
|
|
149
|
+
fs.copyFileSync(srcPath, destPath);
|
|
150
|
+
count++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return count;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function copyFile(src, dest) {
|
|
157
|
+
if (!fs.existsSync(src)) return false;
|
|
158
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
159
|
+
fs.copyFileSync(src, dest);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── File generators ──────────────────────────────────────────────────
|
|
127
164
|
|
|
128
165
|
function generateConfig(projectName, colorKey, themeMode) {
|
|
129
166
|
return `/**
|
|
130
167
|
* Omnira UI Configuration
|
|
131
168
|
* Generated by: npx omnira-ui init
|
|
132
169
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
170
|
+
* Accent: ${colorKey}
|
|
171
|
+
* Theme: ${themeMode}
|
|
135
172
|
*/
|
|
136
173
|
|
|
137
174
|
const omniraConfig = {
|
|
@@ -148,16 +185,10 @@ function generateCSS(colorKey, themeMode) {
|
|
|
148
185
|
const preset = COLOR_PRESETS[colorKey];
|
|
149
186
|
if (!preset) return "";
|
|
150
187
|
|
|
151
|
-
// If lime (default), no overrides needed
|
|
152
188
|
if (colorKey === "lime") {
|
|
153
189
|
return `/**
|
|
154
190
|
* Omnira UI — Theme Overrides
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* Accent: ${preset.label} (${preset.hex})
|
|
158
|
-
* Theme: ${themeMode}
|
|
159
|
-
*
|
|
160
|
-
* Using default Lime preset — no overrides needed.
|
|
191
|
+
* Accent: ${preset.label} (${preset.hex}) — default, no overrides needed.
|
|
161
192
|
* Import this file after globals.css in your root layout.
|
|
162
193
|
*/
|
|
163
194
|
`;
|
|
@@ -165,8 +196,6 @@ function generateCSS(colorKey, themeMode) {
|
|
|
165
196
|
|
|
166
197
|
let css = `/**
|
|
167
198
|
* Omnira UI — Theme Overrides
|
|
168
|
-
* Generated by: npx omnira-ui init
|
|
169
|
-
*
|
|
170
199
|
* Accent: ${preset.label} (${preset.hex})
|
|
171
200
|
* Theme: ${themeMode}
|
|
172
201
|
*
|
|
@@ -176,14 +205,12 @@ function generateCSS(colorKey, themeMode) {
|
|
|
176
205
|
|
|
177
206
|
`;
|
|
178
207
|
|
|
179
|
-
// Dark overrides
|
|
180
208
|
css += `[data-theme="dark"] {\n`;
|
|
181
209
|
for (const [prop, value] of Object.entries(preset.dark)) {
|
|
182
210
|
css += ` ${prop}: ${value};\n`;
|
|
183
211
|
}
|
|
184
212
|
css += `}\n\n`;
|
|
185
213
|
|
|
186
|
-
// Light overrides
|
|
187
214
|
css += `[data-theme="light"] {\n`;
|
|
188
215
|
for (const [prop, value] of Object.entries(preset.light)) {
|
|
189
216
|
css += ` ${prop}: ${value};\n`;
|
|
@@ -193,13 +220,25 @@ function generateCSS(colorKey, themeMode) {
|
|
|
193
220
|
return css;
|
|
194
221
|
}
|
|
195
222
|
|
|
223
|
+
function generateProviders(themeMode) {
|
|
224
|
+
const defaultTheme = themeMode === "light-first" ? "light" : "dark";
|
|
225
|
+
return `"use client";
|
|
226
|
+
|
|
227
|
+
import { ThemeProvider } from "@/lib/theme-context";
|
|
228
|
+
|
|
229
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
230
|
+
return <ThemeProvider defaultTheme="${defaultTheme}">{children}</ThemeProvider>;
|
|
231
|
+
}
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
|
|
196
235
|
// ── Main ─────────────────────────────────────────────────────────────
|
|
197
236
|
|
|
198
237
|
async function main() {
|
|
199
238
|
const { ask, close } = createPrompt();
|
|
200
239
|
|
|
201
240
|
blank();
|
|
202
|
-
log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI
|
|
241
|
+
log(` ${BOLD}${GREEN}✦${RESET} ${BOLD}${WHITE}Omnira UI — Project Scaffolding${RESET}`);
|
|
203
242
|
log(` ${DIM}The premium glassmorphism design system${RESET}`);
|
|
204
243
|
blank();
|
|
205
244
|
|
|
@@ -226,31 +265,100 @@ async function main() {
|
|
|
226
265
|
log(` ${DIM}Theme:${RESET} ${WHITE}${themeMode === "dark-first" ? "Dark-first" : "Light-first"}${RESET}`);
|
|
227
266
|
blank();
|
|
228
267
|
|
|
229
|
-
|
|
268
|
+
log(` ${DIM}Scaffolding...${RESET}`);
|
|
269
|
+
blank();
|
|
270
|
+
|
|
230
271
|
const cwd = process.cwd();
|
|
231
|
-
const configPath = path.join(cwd, "omnira.config.ts");
|
|
232
|
-
const cssPath = path.join(cwd, "omnira-overrides.css");
|
|
233
272
|
|
|
234
|
-
//
|
|
273
|
+
// ── 1. Copy all base components ──
|
|
274
|
+
const componentsSrc = path.join(PKG_ROOT, "components", "ui");
|
|
275
|
+
const componentsDest = path.join(cwd, "components", "ui");
|
|
276
|
+
|
|
277
|
+
if (fs.existsSync(componentsSrc)) {
|
|
278
|
+
const count = copyDirRecursive(componentsSrc, componentsDest);
|
|
279
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}${count} files${RESET} → ${DIM}components/ui/${RESET}`);
|
|
280
|
+
} else {
|
|
281
|
+
log(` ${RED}✗${RESET} Components source not found at ${componentsSrc}`);
|
|
282
|
+
log(` ${DIM}This may happen when running locally. Components are bundled in the npm package.${RESET}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ── 2. Copy lib utilities ──
|
|
286
|
+
const libFiles = ["cn.ts", "copy-to-clipboard.ts", "theme-context.tsx"];
|
|
287
|
+
const libDest = path.join(cwd, "lib");
|
|
288
|
+
fs.mkdirSync(libDest, { recursive: true });
|
|
289
|
+
|
|
290
|
+
for (const file of libFiles) {
|
|
291
|
+
const src = path.join(PKG_ROOT, "lib", file);
|
|
292
|
+
const dest = path.join(libDest, file);
|
|
293
|
+
if (copyFile(src, dest)) {
|
|
294
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}lib/${file}${RESET}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── 3. Copy globals.css (design system tokens) ──
|
|
299
|
+
const globalsSrc = path.join(PKG_ROOT, "app", "globals.css");
|
|
300
|
+
const appDir = path.join(cwd, "app");
|
|
301
|
+
fs.mkdirSync(appDir, { recursive: true });
|
|
302
|
+
|
|
303
|
+
if (copyFile(globalsSrc, path.join(appDir, "globals.css"))) {
|
|
304
|
+
log(` ${GREEN}✓${RESET} Copied ${BOLD}app/globals.css${RESET} ${DIM}(design system tokens)${RESET}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── 4. Generate omnira.config.ts ──
|
|
308
|
+
const configPath = path.join(cwd, "omnira.config.ts");
|
|
235
309
|
fs.writeFileSync(configPath, generateConfig(projectName, colorKey, themeMode), "utf-8");
|
|
236
310
|
log(` ${GREEN}✓${RESET} Created ${BOLD}omnira.config.ts${RESET}`);
|
|
237
311
|
|
|
238
|
-
// CSS
|
|
239
|
-
const
|
|
240
|
-
fs.writeFileSync(cssPath,
|
|
312
|
+
// ── 5. Generate accent overrides CSS ──
|
|
313
|
+
const cssPath = path.join(cwd, "omnira-overrides.css");
|
|
314
|
+
fs.writeFileSync(cssPath, generateCSS(colorKey, themeMode), "utf-8");
|
|
241
315
|
log(` ${GREEN}✓${RESET} Created ${BOLD}omnira-overrides.css${RESET}`);
|
|
242
316
|
|
|
317
|
+
// ── 6. Generate providers.tsx ──
|
|
318
|
+
const providersPath = path.join(appDir, "providers.tsx");
|
|
319
|
+
if (!fs.existsSync(providersPath)) {
|
|
320
|
+
fs.writeFileSync(providersPath, generateProviders(themeMode), "utf-8");
|
|
321
|
+
log(` ${GREEN}✓${RESET} Created ${BOLD}app/providers.tsx${RESET} ${DIM}(ThemeProvider wrapper)${RESET}`);
|
|
322
|
+
} else {
|
|
323
|
+
log(` ${YELLOW}~${RESET} Skipped ${BOLD}app/providers.tsx${RESET} ${DIM}(already exists)${RESET}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ── Done ──
|
|
327
|
+
blank();
|
|
328
|
+
log(` ${DIM}─────────────────────────────────────${RESET}`);
|
|
329
|
+
blank();
|
|
330
|
+
log(` ${GREEN}✓${RESET} ${BOLD}${WHITE}Omnira UI scaffolded successfully!${RESET}`);
|
|
243
331
|
blank();
|
|
244
332
|
|
|
333
|
+
log(` ${BOLD}${WHITE}Next steps:${RESET}`);
|
|
334
|
+
blank();
|
|
335
|
+
log(` ${DIM}1.${RESET} Install peer dependencies:`);
|
|
336
|
+
blank();
|
|
337
|
+
log(` ${CYAN}npm install iconsax-react clsx${RESET}`);
|
|
338
|
+
blank();
|
|
339
|
+
log(` ${DIM}2.${RESET} Wrap your root layout with the ThemeProvider:`);
|
|
340
|
+
blank();
|
|
341
|
+
log(` ${DIM}// app/layout.tsx${RESET}`);
|
|
342
|
+
log(` ${MAGENTA}import${RESET} ${WHITE}"./globals.css"${RESET};`);
|
|
245
343
|
if (colorKey !== "lime") {
|
|
246
|
-
log(`
|
|
247
|
-
blank();
|
|
248
|
-
log(` ${MAGENTA}import${RESET} ${WHITE}"./globals.css"${RESET};`);
|
|
249
|
-
log(` ${MAGENTA}import${RESET} ${WHITE}"./omnira-overrides.css"${RESET};`);
|
|
250
|
-
blank();
|
|
344
|
+
log(` ${MAGENTA}import${RESET} ${WHITE}"../omnira-overrides.css"${RESET};`);
|
|
251
345
|
}
|
|
252
|
-
|
|
253
|
-
|
|
346
|
+
log(` ${MAGENTA}import${RESET} { Providers } ${MAGENTA}from${RESET} ${WHITE}"./providers"${RESET};`);
|
|
347
|
+
blank();
|
|
348
|
+
log(` ${DIM}export default function RootLayout({ children }) {${RESET}`);
|
|
349
|
+
log(` ${DIM} return (${RESET}`);
|
|
350
|
+
log(` ${DIM} <html data-theme="${themeMode === "light-first" ? "light" : "dark"}">${RESET}`);
|
|
351
|
+
log(` ${DIM} <body><Providers>{children}</Providers></body>${RESET}`);
|
|
352
|
+
log(` ${DIM} </html>${RESET}`);
|
|
353
|
+
log(` ${DIM} );${RESET}`);
|
|
354
|
+
log(` ${DIM}}${RESET}`);
|
|
355
|
+
blank();
|
|
356
|
+
log(` ${DIM}3.${RESET} Use components:`);
|
|
357
|
+
blank();
|
|
358
|
+
log(` ${MAGENTA}import${RESET} { Button } ${MAGENTA}from${RESET} ${WHITE}"@/components/ui/Button"${RESET};`);
|
|
359
|
+
blank();
|
|
360
|
+
log(` ${DIM}4.${RESET} Browse all components & copy advanced ones:`);
|
|
361
|
+
log(` ${CYAN}https://ui.omnira.space${RESET}`);
|
|
254
362
|
blank();
|
|
255
363
|
}
|
|
256
364
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback, createContext, useContext, forwardRef } from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
5
|
import styles from "./Collapse.module.css";
|
|
6
6
|
|
|
7
7
|
/* ══════════════════════════════════════
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
forwardRef,
|
|
11
11
|
} from "react";
|
|
12
12
|
import { TickSquare, MinusSquare, Record as RadioIcon, ArrowRight2 } from "iconsax-react";
|
|
13
|
-
import { cn } from "
|
|
13
|
+
import { cn } from "@/lib/cn";
|
|
14
14
|
import styles from "./Dropdown.module.css";
|
|
15
15
|
|
|
16
16
|
/* ── Context ── */
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
useCallback,
|
|
8
8
|
} from "react";
|
|
9
9
|
import { ArrowDown2, SearchNormal1, CloseCircle, TickCircle } from "iconsax-react";
|
|
10
|
-
import { cn } from "
|
|
10
|
+
import { cn } from "@/lib/cn";
|
|
11
11
|
import styles from "./Select.module.css";
|
|
12
12
|
|
|
13
13
|
/* ── Types ── */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
5
|
import type { NavItemType, NavItemDividerType } from "./types";
|
|
6
6
|
import { isDivider, NavRow, SidebarSearchBar, SidebarUserCard } from "./SidebarParts";
|
|
7
7
|
import type { UserCardProps } from "./SidebarParts";
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
4
|
import { CloseCircle, Copy, TickCircle, Calendar, Star1, Flash, MessageText1, Scan, Gift, Headphones, Crown1, Clock } from "iconsax-react";
|
|
5
|
-
import { cn } from "
|
|
6
|
-
import { Button } from "
|
|
7
|
-
import { Badge } from "
|
|
8
|
-
import { copyToClipboard } from "
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
|
+
import { Button } from "@/components/ui/Button";
|
|
7
|
+
import { Badge } from "@/components/ui/Badge";
|
|
8
|
+
import { copyToClipboard } from "@/lib/copy-to-clipboard";
|
|
9
9
|
import styles from "./SidebarFeatureCard.module.css";
|
|
10
10
|
|
|
11
11
|
/* ── 1. Progress ── */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
4
4
|
import { ArrowDown2, SearchNormal1, ExportSquare, ArrowUp2, User, Setting2, Book1, LogoutCurve, AddCircle } from "iconsax-react";
|
|
5
|
-
import { cn } from "
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
6
|
import type { NavItemType, NavItemDividerType, NavSubItem } from "./types";
|
|
7
7
|
import styles from "./SidebarNavigation.module.css";
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
4
|
import type { NavItemType, NavItemDividerType } from "./types";
|
|
5
5
|
import { isDivider, NavRow, SidebarSearchBar, SidebarUserCard } from "./SidebarParts";
|
|
6
6
|
import type { UserCardProps } from "./SidebarParts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
4
|
import type { NavSectionType } from "./types";
|
|
5
5
|
import { isDivider, NavRow, SidebarSearchBar, SidebarUserCard } from "./SidebarParts";
|
|
6
6
|
import type { UserCardProps } from "./SidebarParts";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { cn } from "
|
|
3
|
+
import { cn } from "@/lib/cn";
|
|
4
4
|
import type { NavItemType, NavItemDividerType } from "./types";
|
|
5
5
|
import { isDivider, NavRow, SidebarSearchBar, SidebarUserCard } from "./SidebarParts";
|
|
6
6
|
import type { UserCardProps } from "./SidebarParts";
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import { CloseCircle, TickSquare, MinusSquare } from "iconsax-react";
|
|
5
|
-
import { cn } from "
|
|
5
|
+
import { cn } from "@/lib/cn";
|
|
6
6
|
import styles from "./Tag.module.css";
|
|
7
7
|
|
|
8
8
|
export type TagSize = "sm" | "md" | "lg";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
|
-
import { cn } from "
|
|
4
|
+
import { cn } from "@/lib/cn";
|
|
5
5
|
import styles from "./Textarea.module.css";
|
|
6
6
|
|
|
7
7
|
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useEffect, useState, useCallback } from "react";
|
|
4
|
+
|
|
5
|
+
type Theme = "dark" | "light";
|
|
6
|
+
|
|
7
|
+
interface ThemeContextType {
|
|
8
|
+
theme: Theme;
|
|
9
|
+
toggleTheme: () => void;
|
|
10
|
+
setTheme: (theme: Theme) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ThemeProviderProps {
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
defaultTheme?: Theme;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
|
19
|
+
|
|
20
|
+
const STORAGE_KEY = "omnira-ui-theme";
|
|
21
|
+
|
|
22
|
+
function getSystemTheme(): Theme {
|
|
23
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
24
|
+
return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
25
|
+
}
|
|
26
|
+
return "dark";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function ThemeProvider({ children, defaultTheme }: ThemeProviderProps) {
|
|
30
|
+
const [theme, setThemeState] = useState<Theme>(defaultTheme ?? "dark");
|
|
31
|
+
const [mounted, setMounted] = useState(false);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
|
|
35
|
+
if (stored === "dark" || stored === "light") {
|
|
36
|
+
setThemeState(stored);
|
|
37
|
+
} else {
|
|
38
|
+
setThemeState(defaultTheme ?? getSystemTheme());
|
|
39
|
+
}
|
|
40
|
+
setMounted(true);
|
|
41
|
+
}, [defaultTheme]);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (mounted) {
|
|
45
|
+
document.documentElement.setAttribute("data-theme", theme);
|
|
46
|
+
localStorage.setItem(STORAGE_KEY, theme);
|
|
47
|
+
}
|
|
48
|
+
}, [theme, mounted]);
|
|
49
|
+
|
|
50
|
+
const toggleTheme = useCallback(() => {
|
|
51
|
+
setThemeState((prev) => (prev === "dark" ? "light" : "dark"));
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const setTheme = useCallback((newTheme: Theme) => {
|
|
55
|
+
setThemeState(newTheme);
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
if (!mounted) {
|
|
59
|
+
return <>{children}</>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
|
|
64
|
+
{children}
|
|
65
|
+
</ThemeContext.Provider>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function useTheme(): ThemeContextType {
|
|
70
|
+
const context = useContext(ThemeContext);
|
|
71
|
+
if (!context) {
|
|
72
|
+
return {
|
|
73
|
+
theme: "dark",
|
|
74
|
+
toggleTheme: () => {},
|
|
75
|
+
setTheme: () => {},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return context;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnira-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A premium glassmorphism design system — dark-first, glass-forward. 30+ base components, sidebar navigation, feature cards, and a CLI to scaffold your project.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"components/ui/",
|
|
30
30
|
"lib/cn.ts",
|
|
31
31
|
"lib/copy-to-clipboard.ts",
|
|
32
|
+
"lib/theme-context.tsx",
|
|
33
|
+
"app/globals.css",
|
|
32
34
|
"README.md",
|
|
33
35
|
"LICENSE"
|
|
34
36
|
],
|